From c12031158e55c0454f14183608adf23bb6a9445d Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 15 Apr 2019 20:56:39 -0400 Subject: [PATCH 01/20] Implemented the signal viewer and enabled it in the menu. --- dbc/dbc_classes.cpp | 8 ++-- dbc/dbc_classes.h | 4 +- mainwindow.cpp | 7 +++- signalviewerwindow.cpp | 87 ++++++++++++++++++++++++++++++++++++---- signalviewerwindow.h | 7 +++- ui/signalviewerwindow.ui | 43 ++++++++++++-------- 6 files changed, 123 insertions(+), 33 deletions(-) diff --git a/dbc/dbc_classes.cpp b/dbc/dbc_classes.cpp index f9106e1..f46b4b7 100644 --- a/dbc/dbc_classes.cpp +++ b/dbc/dbc_classes.cpp @@ -36,7 +36,7 @@ s 5 47 46 45 44 43 42 41 40 So, the bits are 12, 11, 10, 9, 8, 23, 22, 21. Yes, that's confusing. They now go in reverse value order too. Bit 12 is worth 128, 11 is worth 64, etc until bit 21 is worth 1. */ -bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString) +bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool outputName) { int64_t result = 0; bool isSigned = false; @@ -95,16 +95,16 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString) endResult = (*((double *)(&result)) * factor) + bias; } - outString = makePrettyOutput(endResult, result); + outString = makePrettyOutput(endResult, result, outputName); cachedValue = endResult; return true; } -QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal) +QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outputName) { QString outputString; - outputString = name + ": "; + if (outputName) outputString = name + ": "; if (valList.count() > 0) //if this is a value list type then look it up and display the proper string { diff --git a/dbc/dbc_classes.h b/dbc/dbc_classes.h index 76fe85b..5d32b67 100644 --- a/dbc/dbc_classes.h +++ b/dbc/dbc_classes.h @@ -101,10 +101,10 @@ public: //TODO: this is sloppy. It shouldn't all be public! QList attributes; QList valList; - bool processAsText(const CANFrame &frame, QString &outString); + bool processAsText(const CANFrame &frame, QString &outString, bool outputName = true); bool processAsInt(const CANFrame &frame, int32_t &outValue); bool processAsDouble(const CANFrame &frame, double &outValue); - QString makePrettyOutput(double floatVal, int64_t intVal); + QString makePrettyOutput(double floatVal, int64_t intVal, bool outputName = true); DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name); DBC_ATTRIBUTE_VALUE *findAttrValByIdx(int idx); }; diff --git a/mainwindow.cpp b/mainwindow.cpp index 15181e1..1e8c7a5 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -186,7 +186,7 @@ MainWindow::MainWindow(QWidget *parent) : //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->actionSignal_Viewer->setVisible(false); + //ui->actionSignal_Viewer->setVisible(false); ui->actionSingle_Multi_State_2->setVisible(false); installEventFilter(this); @@ -1015,7 +1015,10 @@ void MainWindow::showSignalViewer() { if (!signalViewerWindow) { - signalViewerWindow = new SignalViewerWindow(); + if (!useFiltered) + signalViewerWindow = new SignalViewerWindow(model->getListReference()); + else + signalViewerWindow = new SignalViewerWindow(model->getFilteredListReference()); } signalViewerWindow->show(); } diff --git a/signalviewerwindow.cpp b/signalviewerwindow.cpp index 3c77f7e..a093bee 100644 --- a/signalviewerwindow.cpp +++ b/signalviewerwindow.cpp @@ -1,14 +1,18 @@ #include "signalviewerwindow.h" #include "ui_signalviewerwindow.h" #include "helpwindow.h" +#include "mainwindow.h" +#include -SignalViewerWindow::SignalViewerWindow(QWidget *parent) : +SignalViewerWindow::SignalViewerWindow(const QVector *frames, QWidget *parent) : QDialog(parent), ui(new Ui::SignalViewerWindow) { ui->setupUi(this); setWindowFlags(Qt::Window); + modelFrames = frames; + QStringList headers; headers << "Signal" << "Value"; ui->tableViewer->setHorizontalHeaderLabels(headers); @@ -21,6 +25,8 @@ SignalViewerWindow::SignalViewerWindow(QWidget *parent) : connect(ui->cbMessages, SIGNAL(currentIndexChanged(int)), this, SLOT(loadSignals(int))); connect(ui->btnAdd, SIGNAL(clicked(bool)), this, SLOT(addSignal())); + connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); + connect(ui->btnRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedSignal())); loadMessages(); } @@ -30,14 +36,79 @@ SignalViewerWindow::~SignalViewerWindow() delete ui; } +void SignalViewerWindow::updatedFrames(int numFrames) +{ + CANFrame thisFrame; + + if (numFrames == -1) //all frames deleted. Don't care + { + } + else if (numFrames == -2) //all new set of frames. Reset + { + for (int i = 0; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + processFrame(thisFrame); + } + } + else //just got some new frames. See if they are relevant. + { + if (numFrames > modelFrames->count()) return; + + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + processFrame(thisFrame); + } + } +} + +void SignalViewerWindow::processFrame(CANFrame &frame) +{ + QString sigString; + DBC_SIGNAL *sig; + for (int i = 0; i < signalList.count(); i++) + { + sig = signalList.at(i); + if (!sig) return; + if (sig->parentMessage->ID == frame.ID) + { + if (sig->processAsText(frame, sigString, false)) //if true we could interpret the signal so update it in the list + { + QTableWidgetItem *item = ui->tableViewer->item(i, 1); + if (!item) + { + item = new QTableWidgetItem(sigString); + ui->tableViewer->setItem(i, 1, item); + } + else item->setText(sigString); + } + } + } +} + +void SignalViewerWindow::removeSelectedSignal() +{ + int selRow = ui->tableViewer->currentRow(); + if (selRow < 0) return; //no selected row + signalList.removeAt(selRow); + ui->tableViewer->removeRow(selRow); +} + void SignalViewerWindow::loadMessages() { + int numFiles; ui->cbMessages->clear(); - if (dbcHandler == NULL) return; - if (dbcHandler->getFileCount() == 0) dbcHandler->createBlankFile(); - for (int x = 0; x < dbcHandler->getFileByIdx(0)->messageHandler->getCount(); x++) + if (dbcHandler == nullptr) return; + if ((numFiles = dbcHandler->getFileCount()) == 0) return; + qDebug() << numFiles; + for (int f = 0; f < numFiles; f++) { - ui->cbMessages->addItem(dbcHandler->getFileByIdx(0)->messageHandler->findMsgByIdx(x)->name); + qDebug() << dbcHandler->getFileByIdx(f)->messageHandler->getCount(); + for (int x = 0; x < dbcHandler->getFileByIdx(f)->messageHandler->getCount(); x++) + { + ui->cbMessages->addItem(dbcHandler->getFileByIdx(f)->messageHandler->findMsgByIdx(x)->name); + } } } @@ -48,9 +119,9 @@ void SignalViewerWindow::loadSignals(int idx) //in the data structure so it should have been possible to just //look it up based on index but by name is probably safer and this operation //is not time critical at all. - DBC_MESSAGE *msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByName(ui->cbMessages->currentText()); + DBC_MESSAGE *msg = dbcHandler->findMessage(ui->cbMessages->currentText()); - if (msg == NULL) return; + if (msg == nullptr) return; ui->cbSignals->clear(); for (int x = 0; x < msg->sigHandler->getCount(); x++) { @@ -60,7 +131,7 @@ void SignalViewerWindow::loadSignals(int idx) void SignalViewerWindow::addSignal() { - DBC_MESSAGE *msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByName(ui->cbMessages->currentText()); + DBC_MESSAGE *msg = dbcHandler->findMessage(ui->cbMessages->currentText()); if (!msg) return; DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(ui->cbSignals->currentText()); if (!sig) return; diff --git a/signalviewerwindow.h b/signalviewerwindow.h index 68a0553..17b1bde 100644 --- a/signalviewerwindow.h +++ b/signalviewerwindow.h @@ -13,19 +13,24 @@ class SignalViewerWindow : public QDialog Q_OBJECT public: - explicit SignalViewerWindow(QWidget *parent = 0); + explicit SignalViewerWindow(const QVector *frames, QWidget *parent = 0); ~SignalViewerWindow(); private slots: void loadMessages(); void loadSignals(int idx); void addSignal(); + void removeSelectedSignal(); + void updatedFrames(int); private: Ui::SignalViewerWindow *ui; DBCHandler *dbcHandler; QList signalList; + const QVector *modelFrames; + + void processFrame(CANFrame &frame); }; #endif // SIGNALVIEWERWINDOW_H diff --git a/ui/signalviewerwindow.ui b/ui/signalviewerwindow.ui index 84b642b..b5b5d5d 100644 --- a/ui/signalviewerwindow.ui +++ b/ui/signalviewerwindow.ui @@ -15,22 +15,33 @@ - - - 2 - - - 300 - - - true - - - false - - - - + + + + + 2 + + + 300 + + + true + + + false + + + + + + + + + Remove Selected Signal + + + + From 5f97e968e3342378c7985fcd15cb77cc8672d3b3 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Wed, 24 Apr 2019 22:10:03 -0400 Subject: [PATCH 02/20] Remove label for bus number and instead use tabs. The tabs are now numbered by the actual system bus number and not always based on 1 any longer. --- connections/connectionwindow.cpp | 8 +++++--- ui/connectionwindow.ui | 26 ++++++-------------------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/connections/connectionwindow.cpp b/connections/connectionwindow.cpp index 5155e5f..c14158a 100644 --- a/connections/connectionwindow.cpp +++ b/connections/connectionwindow.cpp @@ -277,8 +277,8 @@ void ConnectionWindow::populateBusDetails(int offset) return; } - int busBase = CANConManager::getInstance()->getBusBase(conn_p); - ui->lblBusNum->setText(QString::number(busBase + offset)); + //int busBase = CANConManager::getInstance()->getBusBase(conn_p); + //ui->lblBusNum->setText(QString::number(busBase + offset)); ui->ckListenOnly->setChecked(bus.isListenOnly()); ui->ckEnable->setChecked(bus.isActive()); @@ -328,7 +328,9 @@ void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QMode int numB = ui->tabBuses->count(); for (int i = 0; i < numB; i++) ui->tabBuses->removeTab(0); - if (numBuses > 1) for (int i = 0; i < numBuses; i++) ui->tabBuses->addTab(QString::number(i+1)); + int busBase = CANConManager::getInstance()->getBusBase(conn_p); + + /*if (numBuses > 1)*/ for (int i = 0; i < numBuses; i++) ui->tabBuses->addTab(QString::number(busBase + i)); populateBusDetails(0); if (ui->ckEnableConsole->isChecked()) diff --git a/ui/connectionwindow.ui b/ui/connectionwindow.ui index d132606..cbc6119 100644 --- a/ui/connectionwindow.ui +++ b/ui/connectionwindow.ui @@ -68,58 +68,44 @@ - - - CAN Bus Number: - - - - - - - - - - - Speed: - + - + Listen Only: - + - + Enable Bus: - + - + Save Bus Settings From 15d8ab62b6267d5e145f0cf92b15ebde54e7a5ad Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Wed, 1 May 2019 20:23:01 -0400 Subject: [PATCH 03/20] Remove setting of vertical header for main window. Broke some systems and doesnt seem to help anyone else. --- mainwindow.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index 1e8c7a5..ae91358 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -55,9 +55,9 @@ MainWindow::MainWindow(QWidget *parent) : readSettings(); - QHeaderView *verticalHeader = ui->canFramesView->verticalHeader(); - verticalHeader->setSectionResizeMode(QHeaderView::Fixed); - verticalHeader->setDefaultSectionSize(10); + //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))); @@ -186,7 +186,6 @@ MainWindow::MainWindow(QWidget *parent) : //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->actionSignal_Viewer->setVisible(false); ui->actionSingle_Multi_State_2->setVisible(false); installEventFilter(this); @@ -368,7 +367,8 @@ void MainWindow::updateConnectionSettings(QString connectionType, QString port, void MainWindow::headerClicked(int logicalIndex) { - ui->canFramesView->sortByColumn(logicalIndex); + //ui->canFramesView->sortByColumn(logicalIndex); + model->sortByColumn(logicalIndex); } void MainWindow::gridClicked(QModelIndex idx) From c363059951c21cced8f9c980aae3b59a981ae548 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Wed, 1 May 2019 20:23:32 -0400 Subject: [PATCH 04/20] Now doing custom sorting for main display. It now actually sorts the frame list itself which will sort for all windows. Much faster, and sorts properly for all columns. Also re-added custom display for timestamp. Still needs to be fixed in Windows. --- canframemodel.cpp | 124 ++++++++++++++++++++++++++++++++++++++++------ canframemodel.h | 19 +++++++ config.h | 2 +- 3 files changed, 130 insertions(+), 15 deletions(-) diff --git a/canframemodel.cpp b/canframemodel.cpp index 5865268..645b70b 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -6,19 +6,6 @@ #include #include "utility.h" -enum class Column { - TimeStamp = 0, ///< The timestamp when the frame was transmitted or received - FrameId = 1, ///< The frames CAN identifier (Standard: 11 or Extended: 29 bit) - Extended = 2, ///< True if the frames CAN identifier is 29 bit - Remote = 3, ///< True if the frames is a remote frame - Direction = 4, ///< Whether the frame was transmitted or received - Bus = 5, ///< The bus where the frame was transmitted or received - Length = 6, ///< The frames payload data length - ASCII = 7, ///< The payload interpreted as ASCII characters - Data = 8, ///< The frames payload data - NUM_COLUMN -}; - CANFrameModel::~CANFrameModel() { frames.clear(); @@ -26,7 +13,6 @@ CANFrameModel::~CANFrameModel() filters.clear(); } - int CANFrameModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); @@ -82,6 +68,7 @@ CANFrameModel::CANFrameModel(QObject *parent) needFilterRefresh = false; lastUpdateNumFrames = 0; timeFormat = "MMM-dd HH:mm:ss.zzz"; + sortDirAsc = false; } void CANFrameModel::setHexMode(bool mode) @@ -189,6 +176,110 @@ void CANFrameModel::setAllFilters(bool state) sendRefresh(); } +uint64_t CANFrameModel::getCANFrameVal(int row, Column col) +{ + uint64_t temp = 0; + if (row >= frames.count()) return 0; + CANFrame frame = frames[row]; + switch (col) + { + case Column::TimeStamp: + return frame.timestamp; + case Column::FrameId: + return frame.ID; + case Column::Extended: + if (frame.extended) return 1; + return 0; + case Column::Remote: + if (frame.remote) return 1; + return 0; + case Column::Direction: + if (frame.isReceived) return 1; + return 0; + case Column::Bus: + return frame.bus; + case Column::Length: + return frame.len; + case Column::ASCII: //sort both the same for now + case Column::Data: + for (int i = 0; i < frame.len; i++) temp += ((uint64_t)frame.data[i] << (56 - (8 * i))); + //qDebug() << temp; + return temp; + } +} + +void CANFrameModel::qSortCANFrameAsc(QVector *frames, Column column, int lowerBound, int upperBound) +{ + int p, i, j; + qDebug() << "Lower " << lowerBound << " Upper" << upperBound; + if (lowerBound < upperBound) + { + uint64_t piv = getCANFrameVal(lowerBound + (upperBound - lowerBound) / 2, column); + i = lowerBound - 1; + j = upperBound + 1; + for (;;){ + do { + i++; + } while ((i < upperBound) && getCANFrameVal(i, column) < piv); + + do + { + j--; + } while ((j > lowerBound) && getCANFrameVal(j, column) > piv); + if (i < j) { + CANFrame temp = frames->at(i); + frames->replace(i, frames->at(j)); + frames->replace(j, temp); + } + else {p = j; break;} + } + + qSortCANFrameAsc(frames, column, lowerBound, p); + qSortCANFrameAsc(frames, column, p+1, upperBound); + } +} + +void CANFrameModel::qSortCANFrameDesc(QVector *frames, Column column, int lowerBound, int upperBound) +{ + int p, i, j; + qDebug() << "Lower " << lowerBound << " Upper" << upperBound; + if (lowerBound < upperBound) + { + uint64_t piv = getCANFrameVal(lowerBound + (upperBound - lowerBound) / 2, column); + i = lowerBound - 1; + j = upperBound + 1; + for (;;){ + do { + i++; + } while ((i < upperBound) && getCANFrameVal(i, column) > piv); + + do + { + j--; + } while ((j > lowerBound) && getCANFrameVal(j, column) < piv); + if (i < j) { + CANFrame temp = frames->at(i); + frames->replace(i, frames->at(j)); + frames->replace(j, temp); + } + else {p = j; break;} + } + + qSortCANFrameDesc(frames, column, lowerBound, p); + qSortCANFrameDesc(frames, column, p+1, upperBound); + } +} + +void CANFrameModel::sortByColumn(int column) +{ + sortDirAsc = !sortDirAsc; + //beginResetModel(); + if (sortDirAsc) qSortCANFrameAsc(&frames, Column(column), 0, frames.count()-1); + else qSortCANFrameDesc(&frames, Column(column), 0, frames.count()-1); + //endResetModel(); + sendRefresh(); +} + void CANFrameModel::recalcOverwrite() { if (!overwriteDups) return; //no need to do a thing if mode is disabled @@ -283,6 +374,11 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const switch (Column(index.column())) { case Column::TimeStamp: + //Reformatting the output a bit with custom code + ts = Utility::formatTimestamp(thisFrame.timestamp); + if (ts.type() == QVariant::Double) return QString::number(ts.toDouble(), 'f', 5); //never scientific notation, 5 decimal places + if (ts.type() == QVariant::LongLong) return QString::number(ts.toLongLong()); //never scientific notion, all digits shown + if (ts.type() == QVariant::DateTime) return ts.toDateTime().toString(timeFormat); //custom set format for dates and times return Utility::formatTimestamp(thisFrame.timestamp); case Column::FrameId: return Utility::formatCANID(thisFrame.ID, thisFrame.extended); diff --git a/canframemodel.h b/canframemodel.h index 42fd66b..cc3c1d7 100644 --- a/canframemodel.h +++ b/canframemodel.h @@ -10,6 +10,19 @@ #include "dbc/dbchandler.h" #include "connections/canconnection.h" +enum class Column { + TimeStamp = 0, ///< The timestamp when the frame was transmitted or received + FrameId = 1, ///< The frames CAN identifier (Standard: 11 or Extended: 29 bit) + Extended = 2, ///< True if the frames CAN identifier is 29 bit + Remote = 3, ///< True if the frames is a remote frame + Direction = 4, ///< Whether the frame was transmitted or received + Bus = 5, ///< The bus where the frame was transmitted or received + Length = 6, ///< The frames payload data length + ASCII = 7, ///< The payload interpreted as ASCII characters + Data = 8, ///< The frames payload data + NUM_COLUMN +}; + class CANFrameModel: public QAbstractTableModel { Q_OBJECT @@ -43,6 +56,7 @@ public: void recalcOverwrite(); bool needsFilterRefresh(); void insertFrames(const QVector &newFrames); + void sortByColumn(int column); int getIndexFromTimeID(unsigned int ID, double timestamp); const QVector *getListReference() const; //thou shalt not modify these frames externally! const QVector *getFilteredListReference() const; //Thus saith the Lord, NO. @@ -56,6 +70,10 @@ signals: void updatedFiltersList(); private: + void qSortCANFrameAsc(QVector* frames, Column column, int lowerBound, int upperBound); + void qSortCANFrameDesc(QVector* frames, Column column, int lowerBound, int upperBound); + uint64_t getCANFrameVal(int row, Column col); + QVector frames; QVector filteredFrames; QMap filters; @@ -71,6 +89,7 @@ private: uint64_t timeOffset; int lastUpdateNumFrames; uint32_t preallocSize; + bool sortDirAsc; }; diff --git a/config.h b/config.h index c9a1b1c..cc815a5 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 197 +#define VERSION 198 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision From 164d5adadb5132906d982ef46c70225fe9f77943 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 5 May 2019 11:52:32 -0400 Subject: [PATCH 05/20] Added a new window - Temporal Graph which shows all frame IDs and the time stamp when they came in. Shows the temporal relationship between all frames. --- SavvyCAN.pro | 9 +- mainwindow.cpp | 16 ++ mainwindow.h | 3 + re/temporalgraphwindow.cpp | 381 +++++++++++++++++++++++++++++++++++++ re/temporalgraphwindow.h | 50 +++++ ui/mainwindow.ui | 6 + ui/temporalgraphwindow.ui | 32 ++++ 7 files changed, 494 insertions(+), 3 deletions(-) create mode 100644 re/temporalgraphwindow.cpp create mode 100644 re/temporalgraphwindow.h create mode 100644 ui/temporalgraphwindow.ui diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 1e02488..9d67acf 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -67,7 +67,8 @@ SOURCES += main.cpp\ helpwindow.cpp \ blfhandler.cpp \ re/sniffer/SnifferDelegate.cpp \ - connections/newconnectiondialog.cpp + connections/newconnectiondialog.cpp \ + re/temporalgraphwindow.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -125,7 +126,8 @@ HEADERS += mainwindow.h \ helpwindow.h \ blfhandler.h \ re/sniffer/SnifferDelegate.h \ - connections/newconnectiondialog.h + connections/newconnectiondialog.h \ + re/temporalgraphwindow.h FORMS += ui/candatagrid.ui \ ui/connectionwindow.ui \ @@ -153,7 +155,8 @@ FORMS += ui/candatagrid.ui \ ui/bisectwindow.ui \ ui/signalviewerwindow.ui \ helpwindow.ui \ - ui/newconnectiondialog.ui + ui/newconnectiondialog.ui \ + ui/temporalgraphwindow.ui DISTFILES += diff --git a/mainwindow.cpp b/mainwindow.cpp index ae91358..56e6844 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -83,6 +83,7 @@ MainWindow::MainWindow(QWidget *parent) : snifferWindow = nullptr; bisectWindow = nullptr; signalViewerWindow = nullptr; + temporalGraphWindow = nullptr; dbcHandler = DBCHandler::getReference(); bDirty = false; inhibitFilterUpdate = false; @@ -131,6 +132,7 @@ MainWindow::MainWindow(QWidget *parent) : 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); @@ -226,6 +228,7 @@ void MainWindow::killEmAll() killWindow(motorctrlConfigWindow); killWindow(signalViewerWindow); killWindow(connectionWindow); + killWindow(temporalGraphWindow); } //forcefully close the window, kill it, and salt the earth @@ -837,6 +840,19 @@ void MainWindow::showGraphingWindow() 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. diff --git a/mainwindow.h b/mainwindow.h index 5814211..d19a322 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -31,6 +31,7 @@ #include "re/isotp_interpreterwindow.h" #include "motorcontrollerconfigwindow.h" #include "signalviewerwindow.h" +#include "re/temporalgraphwindow.h" class CANConnection; class ConnectionWindow; @@ -81,6 +82,7 @@ private slots: void showSnifferWindow(); void showBisectWindow(); void showSignalViewer(); + void showTemporalGraphWindow(); void exitApp(); void handleSaveDecoded(); void connectionStatusUpdated(int conns); @@ -159,6 +161,7 @@ private: MotorControllerConfigWindow *motorctrlConfigWindow; BisectWindow* bisectWindow; SignalViewerWindow *signalViewerWindow; + TemporalGraphWindow *temporalGraphWindow; //various private storage QLabel lbStatusConnected; diff --git a/re/temporalgraphwindow.cpp b/re/temporalgraphwindow.cpp new file mode 100644 index 0000000..7fedfa6 --- /dev/null +++ b/re/temporalgraphwindow.cpp @@ -0,0 +1,381 @@ +#include "temporalgraphwindow.h" +#include "ui_temporalgraphwindow.h" +#include "helpwindow.h" +#include "mainwindow.h" + +QString HexTicker::getTickLabel (double tick, const QLocale& locale, QChar formatChar, int precision) +{ + int valu = static_cast(tick); + //qDebug() << valu; + return "0x" + QString::number(valu, 16).toUpper().rightJustified(3,'0'); +} + +TemporalGraphWindow::TemporalGraphWindow(const QVector *frames, QWidget *parent) : + QDialog(parent), + ui(new Ui::TemporalGraphWindow) +{ + ui->setupUi(this); + + setWindowFlags(Qt::Window); + + readSettings(); + + modelFrames = frames; + + ui->graphingView->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes); + + ui->graphingView->xAxis->setRange(0, 8); + ui->graphingView->yAxis->setRange(0, 255); + ui->graphingView->axisRect()->setupFullAxesBox(); + + + ui->graphingView->xAxis->setLabel("Elapsed Time"); + ui->graphingView->yAxis->setLabel("ID"); + ui->graphingView->xAxis->setNumberFormat("f"); + ui->graphingView->xAxis->setNumberPrecision(6); + + HexTicker *tick = new HexTicker; + QSharedPointer sharedTicker(tick); + tick->setTickCount(10); + ui->graphingView->yAxis->setTicker(sharedTicker); + + // connect slot that ties some axis selections together (especially opposite axes): + connect(ui->graphingView, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged())); + connect(ui->graphingView, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); + connect(ui->graphingView, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel())); + connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); + // make bottom and left axes transfer their ranges to top and right axes: + connect(ui->graphingView->xAxis, SIGNAL(rangeChanged(QCPRange)), ui->graphingView->xAxis2, SLOT(setRange(QCPRange))); + connect(ui->graphingView->yAxis, SIGNAL(rangeChanged(QCPRange)), ui->graphingView->yAxis2, SLOT(setRange(QCPRange))); + + if (useOpenGL) + { + ui->graphingView->setAntialiasedElements(QCP::aeAll); + //ui->graphingView->setNoAntialiasingOnDrag(true); + ui->graphingView->setOpenGl(true); + } + else + { + ui->graphingView->setOpenGl(false); + ui->graphingView->setAntialiasedElements(QCP::aeNone); + } +} + +TemporalGraphWindow::~TemporalGraphWindow() +{ + delete ui; +} + +void TemporalGraphWindow::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + installEventFilter(this); + readSettings(); + generateGraph(); + ui->graphingView->replot(); +} + +void TemporalGraphWindow::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event); + removeEventFilter(this); + writeSettings(); +} + +void TemporalGraphWindow::readSettings() +{ + QSettings settings; + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + resize(settings.value("Temporal/WindowSize", QSize(800, 600)).toSize()); + move(settings.value("Temporal/WindowPos", QPoint(50, 50)).toPoint()); + } + useOpenGL = settings.value("Main/UseOpenGL", false).toBool(); +} + +void TemporalGraphWindow::writeSettings() +{ + QSettings settings; + + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + settings.setValue("Temporal/WindowSize", size()); + settings.setValue("Temporal/WindowPos", pos()); + } +} + +void TemporalGraphWindow::updatedFrames(int numFrames) +{ + CANFrame thisFrame; + QVector x, y; + bool appendedToGraph = false; + bool needReplot = false; + + if (numFrames == -1) //all frames deleted. Kill the display + { + //removeAllGraphs(); + //now instead of removing the graphs regenerate them which will blank them out but leave them there in case + //more traffic that matches comes in or someone otherwise loads more data + ui->graphingView->clearGraphs(); //temporarily remove the graphs from the graph view + ui->graphingView->replot(); //now, redisplay them all + + } + else if (numFrames == -2) //all new set of frames. Reset + { + //there shouldn't be any need to actually remove the graphs. + //regenerate them instead + ui->graphingView->clearGraphs(); //temporarily remove the graphs from the graph view + //needScaleSetup = true; + generateGraph(); + //ui->graphingView->replot(); //now, redisplay them all + } + else //just got some new frames. See if they are relevant. + { + if (numFrames > modelFrames->count()) return; + + appendedToGraph = false; + x.clear(); + y.clear(); + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + /* + if (graphParams[j].ID == thisFrame.ID) + { + appendToGraph(graphParams[j], thisFrame, x, y); + appendedToGraph = true; + }*/ + } + if (appendedToGraph) + { + //graphParams[j].ref->addData(x, y); + needReplot = true; + } + + if (needReplot) + { + if (followGraphEnd) + { + //find the current X span and maintain that span but move the end of it over to match the new end + //of the actual graph. This causes the view to move with the data to always show the end + QCPRange range = ui->graphingView->xAxis->range(); + double size = range.size(); + bool foundRange; + QCPRange keyRange = ui->graphingView->graph()->getKeyRange(foundRange); + if (foundRange) + { + double end, start; + end = keyRange.upper; + start = end - size; + ui->graphingView->xAxis->setRange(start, end); + } + } + ui->graphingView->replot(); + } + } +} + +void TemporalGraphWindow::generateGraph() +{ + if (modelFrames->count() == 0) return; + qDebug() << "Regenerating the graph"; + ui->graphingView->addGraph(); + graph = ui->graphingView->graph(); + + QVector x, y; + double xminval = 0, xmaxval = 0, yminval = 0, ymaxval = 0; + int frameCount = modelFrames->count(); + + x.reserve(frameCount); + y.reserve(frameCount); + + xminval = xmaxval = modelFrames->at(0).timestamp / 1000000.0; + yminval = ymaxval = modelFrames->at(0).ID; + + for (int i = 0; i < frameCount; i++) + { + x.append(modelFrames->at(i).timestamp / 1000000.0); + y.append(modelFrames->at(i).ID); + if (x[i] > xmaxval) xmaxval = x[i]; + if (x[i] < xminval) xminval = x[i]; + if (y[i] > ymaxval) ymaxval = y[i]; + if (y[i] < yminval) yminval = y[i]; + } + + ui->graphingView->graph()->setData(x,y); + ui->graphingView->graph()->setLineStyle(QCPGraph::lsNone); //no lines + ui->graphingView->graph()->setScatterStyle(QCPScatterStyle::ssPlus); + QPen graphPen; + graphPen.setColor(Qt::blue); + graphPen.setWidth(1); + ui->graphingView->graph()->setPen(graphPen); + + qDebug() << "xmin: " << xminval; + qDebug() << "xmax: " << xmaxval; + qDebug() << "ymin: " << yminval; + qDebug() << "ymax: " << ymaxval; + + ui->graphingView->xAxis->setRange(xminval, xmaxval); + ui->graphingView->yAxis->setRange(yminval, ymaxval); + ui->graphingView->axisRect()->setupFullAxesBox(); + + ui->graphingView->replot(); +} + +void TemporalGraphWindow::selectionChanged() +{ + /* + normally, axis base line, axis tick labels and axis labels are selectable separately, but we want + the user only to be able to select the axis as a whole, so we tie the selected states of the tick labels + and the axis base line together. However, the axis label shall be selectable individually. + + The selection state of the left and right axes shall be synchronized as well as the state of the + bottom and top axes. + + Further, we want to synchronize the selection of the graphs with the selection state of the respective + legend item belonging to that graph. So the user can select a graph by either clicking on the graph itself + or on its legend item. + */ + + qDebug() << "SelectionChanged"; + + // make top and bottom axes be selected synchronously, and handle axis and tick labels as one selectable object: + if (ui->graphingView->xAxis->selectedParts().testFlag(QCPAxis::spAxis) || ui->graphingView->xAxis->selectedParts().testFlag(QCPAxis::spTickLabels) || + ui->graphingView->xAxis2->selectedParts().testFlag(QCPAxis::spAxis) || ui->graphingView->xAxis2->selectedParts().testFlag(QCPAxis::spTickLabels)) + { + ui->graphingView->xAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + ui->graphingView->xAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + } + // make left and right axes be selected synchronously, and handle axis and tick labels as one selectable object: + if (ui->graphingView->yAxis->selectedParts().testFlag(QCPAxis::spAxis) || ui->graphingView->yAxis->selectedParts().testFlag(QCPAxis::spTickLabels) || + ui->graphingView->yAxis2->selectedParts().testFlag(QCPAxis::spAxis) || ui->graphingView->yAxis2->selectedParts().testFlag(QCPAxis::spTickLabels)) + { + ui->graphingView->yAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + ui->graphingView->yAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + } +} + +void TemporalGraphWindow::mousePress() +{ + // if an axis is selected, only allow the direction of that axis to be dragged + // if no axis is selected, both directions may be dragged + + if (ui->graphingView->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + { + ui->graphingView->axisRect()->setRangeDrag(ui->graphingView->xAxis->orientation()); + } + else if (ui->graphingView->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + { + ui->graphingView->axisRect()->setRangeDrag(ui->graphingView->yAxis->orientation()); + } + else + { + ui->graphingView->axisRect()->setRangeDrag(Qt::Horizontal|Qt::Vertical); + } +} + +void TemporalGraphWindow::mouseWheel() +{ + qDebug() << "Mouse WHeel"; + // if an axis is selected, only allow the direction of that axis to be zoomed + // if no axis is selected, both directions may be zoomed + + if (ui->graphingView->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + ui->graphingView->axisRect()->setRangeZoom(ui->graphingView->xAxis->orientation()); + else if (ui->graphingView->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + ui->graphingView->axisRect()->setRangeZoom(ui->graphingView->yAxis->orientation()); + else + ui->graphingView->axisRect()->setRangeZoom(Qt::Horizontal|Qt::Vertical); +} + +bool TemporalGraphWindow::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyRelease) { + QKeyEvent *keyEvent = static_cast(event); + switch (keyEvent->key()) + { + case Qt::Key_Plus: + zoomIn(); + break; + case Qt::Key_Minus: + zoomOut(); + break; + case Qt::Key_F1: + HelpWindow::getRef()->showHelp("temporalwindow.html"); + break; + } + return true; + } else { + // standard event processing + return QObject::eventFilter(obj, event); + } + + return false; +} + +void TemporalGraphWindow::resetView() +{ + double yminval=10000000.0, ymaxval = -1000000.0; + double xminval=100000000000, xmaxval = -10000000000.0; + /* + for (int i = 0; i < graphParams.count(); i++) + { + for (int j = 0; j < graphParams[i].x.count(); j++) + { + if (graphParams[i].x[j] < xminval) xminval = graphParams[i].x[j]; + if (graphParams[i].x[j] > xmaxval) xmaxval = graphParams[i].x[j]; + if (graphParams[i].y[j] < yminval) yminval = graphParams[i].y[j]; + if (graphParams[i].y[j] > ymaxval) ymaxval = graphParams[i].y[j]; + } + } + + ui->graphingView->xAxis->setRange(xminval, xmaxval); + ui->graphingView->yAxis->setRange(yminval, ymaxval); + ui->graphingView->axisRect()->setupFullAxesBox(); + + ui->graphingView->replot(); + */ +} + +void TemporalGraphWindow::zoomIn() +{ + QCPRange xrange = ui->graphingView->xAxis->range(); + QCPRange yrange = ui->graphingView->yAxis->range(); + if (ui->graphingView->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + { + ui->graphingView->xAxis->scaleRange(0.666, xrange.center()); + } + + else if (ui->graphingView->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + { + ui->graphingView->yAxis->scaleRange(0.666, yrange.center()); + } + else + { + ui->graphingView->xAxis->scaleRange(0.666, xrange.center()); + ui->graphingView->yAxis->scaleRange(0.666, yrange.center()); + } + ui->graphingView->replot(); +} + +void TemporalGraphWindow::zoomOut() +{ + QCPRange xrange = ui->graphingView->xAxis->range(); + QCPRange yrange = ui->graphingView->yAxis->range(); + if (ui->graphingView->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + { + ui->graphingView->xAxis->scaleRange(1.5, xrange.center()); + } + + else if (ui->graphingView->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + { + ui->graphingView->yAxis->scaleRange(1.5, yrange.center()); + } + else + { + ui->graphingView->xAxis->scaleRange(1.5, xrange.center()); + ui->graphingView->yAxis->scaleRange(1.5, yrange.center()); + } + ui->graphingView->replot(); +} + diff --git a/re/temporalgraphwindow.h b/re/temporalgraphwindow.h new file mode 100644 index 0000000..39b1809 --- /dev/null +++ b/re/temporalgraphwindow.h @@ -0,0 +1,50 @@ +#ifndef TEMPORALGRAPHWINDOW_H +#define TEMPORALGRAPHWINDOW_H + +#include +#include "qcustomplot.h" +#include "can_structs.h" + +namespace Ui { +class TemporalGraphWindow; +} + +class HexTicker : public QCPAxisTicker +{ + QString getTickLabel (double tick, const QLocale& locale, QChar formatChar, int precision); +}; + +class TemporalGraphWindow : public QDialog +{ + Q_OBJECT + +public: + explicit TemporalGraphWindow(const QVector *, QWidget *parent = nullptr); + ~TemporalGraphWindow(); + void showEvent(QShowEvent*); + +private slots: + void updatedFrames(int); + void mousePress(); + void mouseWheel(); + void resetView(); + void zoomIn(); + void zoomOut(); + void selectionChanged(); + +private: + Ui::TemporalGraphWindow *ui; + const QVector *modelFrames; + bool useOpenGL; + bool followGraphEnd; + QCPGraph *graph; + + void closeEvent(QCloseEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + void readSettings(); + void writeSettings(); + void generateGraph(); + +}; + +#endif // TEMPORALGRAPHWINDOW_H diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index e21a989..90c8302 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -282,6 +282,7 @@ + @@ -497,6 +498,11 @@ Start Continuous Logging + + + Temporal Graph + + diff --git a/ui/temporalgraphwindow.ui b/ui/temporalgraphwindow.ui new file mode 100644 index 0000000..d8f02e5 --- /dev/null +++ b/ui/temporalgraphwindow.ui @@ -0,0 +1,32 @@ + + + TemporalGraphWindow + + + + 0 + 0 + 886 + 683 + + + + Temporal Graph + + + + + + + + + + QCustomPlot + QWidget +
qcustomplot.h
+ 1 +
+
+ + +
From c6cba08f2e115c11140976f37a63098d68a4aa6c Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 5 May 2019 16:12:47 -0400 Subject: [PATCH 06/20] Add colormapped background to temporal view. Makes it even easier to see where traffic changes are. --- re/temporalgraphwindow.cpp | 62 +++++++++++++++++++++++++++----------- re/temporalgraphwindow.h | 2 +- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/re/temporalgraphwindow.cpp b/re/temporalgraphwindow.cpp index 7fedfa6..f61803e 100644 --- a/re/temporalgraphwindow.cpp +++ b/re/temporalgraphwindow.cpp @@ -117,6 +117,7 @@ void TemporalGraphWindow::updatedFrames(int numFrames) //now instead of removing the graphs regenerate them which will blank them out but leave them there in case //more traffic that matches comes in or someone otherwise loads more data ui->graphingView->clearGraphs(); //temporarily remove the graphs from the graph view + ui->graphingView->clearPlottables(); ui->graphingView->replot(); //now, redisplay them all } @@ -178,12 +179,15 @@ void TemporalGraphWindow::updatedFrames(int numFrames) void TemporalGraphWindow::generateGraph() { if (modelFrames->count() == 0) return; + + ui->graphingView->clearGraphs(); + ui->graphingView->clearPlottables(); + qDebug() << "Regenerating the graph"; ui->graphingView->addGraph(); graph = ui->graphingView->graph(); QVector x, y; - double xminval = 0, xmaxval = 0, yminval = 0, ymaxval = 0; int frameCount = modelFrames->count(); x.reserve(frameCount); @@ -204,10 +208,10 @@ void TemporalGraphWindow::generateGraph() ui->graphingView->graph()->setData(x,y); ui->graphingView->graph()->setLineStyle(QCPGraph::lsNone); //no lines - ui->graphingView->graph()->setScatterStyle(QCPScatterStyle::ssPlus); + ui->graphingView->graph()->setScatterStyle(QCPScatterStyle::ssCircle); QPen graphPen; graphPen.setColor(Qt::blue); - graphPen.setWidth(1); + graphPen.setWidth(2); ui->graphingView->graph()->setPen(graphPen); qDebug() << "xmin: " << xminval; @@ -220,6 +224,40 @@ void TemporalGraphWindow::generateGraph() ui->graphingView->axisRect()->setupFullAxesBox(); ui->graphingView->replot(); + + + QCPColorMap *colorMap = new QCPColorMap(ui->graphingView->xAxis, ui->graphingView->yAxis); + + int ySize = static_cast(ymaxval - yminval) / 30 + 1; + int xSize = static_cast((xmaxval - xminval) * 4.0) + 1; + colorMap->data()->setSize(xSize, ySize); + colorMap->data()->setRange(QCPRange(xminval, xmaxval), QCPRange(yminval, ymaxval)); + for (int x = 0; x < xSize; ++x) + { + for (int y = 0; y < ySize; ++y) + { + colorMap->data()->setAlpha(x, y, 180); + colorMap->data()->setCell(x, y, 0.0); + } + } + + for (int i = 0; i < frameCount; i++) + { + int x = static_cast(((modelFrames->at(i).timestamp / 1000000.0) - xminval) * 4.0); + int y = static_cast(modelFrames->at(i).ID - yminval) / 30; + double val = colorMap->data()->cell(x, y); + double inc; + inc = 1 / (val + 1); //logarithmic decay + inc = inc * inc; //square the increment to make it even more stark + val = val + inc; + qDebug() << "X: " << x << " Y: " << y << "Val: " << val; + colorMap->data()->setCell(x, y, val); + } + + colorMap->setGradient(QCPColorGradient::gpJet); + colorMap->rescaleDataRange(true); + ui->graphingView->rescaleAxes(); + ui->graphingView->replot(); } void TemporalGraphWindow::selectionChanged() @@ -300,6 +338,9 @@ bool TemporalGraphWindow::eventFilter(QObject *obj, QEvent *event) case Qt::Key_Minus: zoomOut(); break; + case Qt::Key_R: + resetView(); + break; case Qt::Key_F1: HelpWindow::getRef()->showHelp("temporalwindow.html"); break; @@ -315,26 +356,11 @@ bool TemporalGraphWindow::eventFilter(QObject *obj, QEvent *event) void TemporalGraphWindow::resetView() { - double yminval=10000000.0, ymaxval = -1000000.0; - double xminval=100000000000, xmaxval = -10000000000.0; - /* - for (int i = 0; i < graphParams.count(); i++) - { - for (int j = 0; j < graphParams[i].x.count(); j++) - { - if (graphParams[i].x[j] < xminval) xminval = graphParams[i].x[j]; - if (graphParams[i].x[j] > xmaxval) xmaxval = graphParams[i].x[j]; - if (graphParams[i].y[j] < yminval) yminval = graphParams[i].y[j]; - if (graphParams[i].y[j] > ymaxval) ymaxval = graphParams[i].y[j]; - } - } - ui->graphingView->xAxis->setRange(xminval, xmaxval); ui->graphingView->yAxis->setRange(yminval, ymaxval); ui->graphingView->axisRect()->setupFullAxesBox(); ui->graphingView->replot(); - */ } void TemporalGraphWindow::zoomIn() diff --git a/re/temporalgraphwindow.h b/re/temporalgraphwindow.h index 39b1809..789f1d3 100644 --- a/re/temporalgraphwindow.h +++ b/re/temporalgraphwindow.h @@ -38,7 +38,7 @@ private: bool useOpenGL; bool followGraphEnd; QCPGraph *graph; - + double xminval, xmaxval, yminval, ymaxval; void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); void readSettings(); From 539fecc16b4871f7732c205860d4cca4af7c3524 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 6 May 2019 21:49:10 -0400 Subject: [PATCH 07/20] Fix reported location when double clicking a graph. Now reported X,Y location matches where the circle indicator is actually placed. --- re/graphingwindow.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/re/graphingwindow.cpp b/re/graphingwindow.cpp index 7316fc5..41d8505 100644 --- a/re/graphingwindow.cpp +++ b/re/graphingwindow.cpp @@ -245,11 +245,15 @@ void GraphingWindow::plottableDoubleClick(QCPAbstractPlottable* plottable, int d double x, y; QCPGraph *graph = reinterpret_cast(plottable); graph->pixelsToCoords(event->localPos(), x, y); - locationText->setText("X: " + QString::number(x) + " Y: " + QString::number(y)); + x = ui->graphingView->xAxis->pixelToCoord(event->localPos().x()); itemTracer->setGraph(graph); itemTracer->setVisible(true); + itemTracer->setInterpolating(true); itemTracer->setGraphKey(x); + itemTracer->updatePosition(); + qDebug() << "val " << itemTracer->position->value(); + locationText->setText("X: " + QString::number(x) + " Y: " + QString::number(itemTracer->position->value())); } void GraphingWindow::gotCenterTimeID(int32_t ID, double timestamp) From d3aa669a726546f63705d42f421249e3d86c121d Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 13 May 2019 20:49:59 -0400 Subject: [PATCH 08/20] Fix a minor typo --- ui/mainwindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 90c8302..1bda8d1 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -425,7 +425,7 @@ - Open Connection WIndow + Open Connection Window From 09b8d64f4ad2d1d4002bec190c1511b3b77c575d Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 13 May 2019 20:50:16 -0400 Subject: [PATCH 09/20] Add a reset connection button. Automatically disconnects and reconnects to try to heal a broken connection. --- connections/canconmanager.cpp | 5 +++ connections/canconmanager.h | 1 + connections/canconnectionmodel.cpp | 8 ++++ connections/canconnectionmodel.h | 1 + connections/connectionwindow.cpp | 69 +++++++++++++++++++++--------- connections/connectionwindow.h | 1 + ui/connectionwindow.ui | 7 +++ 7 files changed, 71 insertions(+), 21 deletions(-) diff --git a/connections/canconmanager.cpp b/connections/canconmanager.cpp index 2b285b4..6e5020d 100644 --- a/connections/canconmanager.cpp +++ b/connections/canconmanager.cpp @@ -66,6 +66,11 @@ void CANConManager::remove(CANConnection* pConn_p) mConns.removeOne(pConn_p); } +void CANConManager::replace(int idx, CANConnection* pConn_p) +{ + mConns.replace(idx, pConn_p); +} + //Get total number of buses currently registered with the program int CANConManager::getNumBuses() { diff --git a/connections/canconmanager.h b/connections/canconmanager.h index 5a5f9dc..a479dd8 100644 --- a/connections/canconmanager.h +++ b/connections/canconmanager.h @@ -17,6 +17,7 @@ public: void add(CANConnection* pConn_p); void remove(CANConnection* pConn_p); + void replace(int idx, CANConnection* pConn_p); QList& getConnections(); void stopAllConnections(); diff --git a/connections/canconnectionmodel.cpp b/connections/canconnectionmodel.cpp index 12d4f25..6c68564 100644 --- a/connections/canconnectionmodel.cpp +++ b/connections/canconnectionmodel.cpp @@ -126,6 +126,14 @@ void CANConnectionModel::remove(CANConnection* pConn_p) endResetModel(); } +void CANConnectionModel::replace(int idx , CANConnection* pConn_p) +{ + CANConManager* manager = CANConManager::getInstance(); + + beginResetModel(); + manager->replace(idx, pConn_p); + endResetModel(); +} CANConnection* CANConnectionModel::getAtIdx(int pIdx) const { diff --git a/connections/canconnectionmodel.h b/connections/canconnectionmodel.h index 8ea3ed3..5140eae 100644 --- a/connections/canconnectionmodel.h +++ b/connections/canconnectionmodel.h @@ -25,6 +25,7 @@ public: void add(CANConnection* pConn_p); void remove(CANConnection* pConn_p); + void replace(int idx , CANConnection* pConn_p); CANConnection* getAtIdx(int) const; void refresh(int pIndex=-1); diff --git a/connections/connectionwindow.cpp b/connections/connectionwindow.cpp index c14158a..56dff96 100644 --- a/connections/connectionwindow.cpp +++ b/connections/connectionwindow.cpp @@ -55,6 +55,7 @@ ConnectionWindow::ConnectionWindow(QWidget *parent) : connect(ui->ckEnableConsole, &QCheckBox::toggled, this, &ConnectionWindow::consoleEnableChanged); connect(ui->btnClearDebug, &QPushButton::clicked, this, &ConnectionWindow::handleClearDebugText); connect(ui->btnNewConnection, &QPushButton::clicked, this, &ConnectionWindow::handleNewConn); + connect(ui->btnResetConn, &QPushButton::clicked, this, &ConnectionWindow::handleResetConn); connect(ui->tableConnections->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &ConnectionWindow::currentRowChanged); connect(ui->tabBuses, &QTabBar::currentChanged, this, &ConnectionWindow::currentTabChanged); connect(ui->btnSaveBus, &QPushButton::clicked, this, &ConnectionWindow::saveBusSettings); @@ -207,6 +208,53 @@ void ConnectionWindow::handleNewConn() delete thisDialog; } +void ConnectionWindow::handleRemoveConn() +{ + int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); + if (selIdx <0) return; + + qDebug() << "remove connection at index: " << selIdx; + + CANConnection* conn_p = connModel->getAtIdx(selIdx); + if(!conn_p) return; + + /* remove connection from model & manager */ + connModel->remove(conn_p); + + /* stop and delete connection */ + conn_p->stop(); + delete conn_p; + + /* select first connection in list */ + ui->tableConnections->selectRow(0); +} + +void ConnectionWindow::handleResetConn() +{ + QString port, driver; + CANCon::type type; + + int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); + if (selIdx <0) return; + + qDebug() << "remove connection at index: " << selIdx; + + CANConnection* conn_p = connModel->getAtIdx(selIdx); + if(!conn_p) return; + + type = conn_p->getType(); + port = conn_p->getPort(); + driver = conn_p->getDriver(); + + /* stop and delete connection */ + conn_p->stop(); + + conn_p = nullptr; + + conn_p = create(type, port, driver); + if (conn_p) connModel->replace(selIdx, conn_p); +} + /* status */ void ConnectionWindow::connectionStatus(CANConStatus pStatus) { @@ -365,27 +413,6 @@ void ConnectionWindow::handleSendText() { emit sendDebugData(bytes); } -void ConnectionWindow::handleRemoveConn() -{ - int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); - if (selIdx <0) return; - - qDebug() << "remove connection at index: " << selIdx; - - CANConnection* conn_p = connModel->getAtIdx(selIdx); - if(!conn_p) return; - - /* remove connection from model & manager */ - connModel->remove(conn_p); - - /* stop and delete connection */ - conn_p->stop(); - delete conn_p; - - /* select first connection in list */ - ui->tableConnections->selectRow(0); -} - CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QString pDriver) { CANConnection* conn_p; diff --git a/connections/connectionwindow.h b/connections/connectionwindow.h index 1af482f..22769fc 100644 --- a/connections/connectionwindow.h +++ b/connections/connectionwindow.h @@ -46,6 +46,7 @@ private slots: void consoleEnableChanged(bool checked); void handleRemoveConn(); void handleNewConn(); + void handleResetConn(); void handleClearDebugText(); void handleSendHex(); void handleSendText(); diff --git a/ui/connectionwindow.ui b/ui/connectionwindow.ui index cbc6119..12a6ada 100644 --- a/ui/connectionwindow.ui +++ b/ui/connectionwindow.ui @@ -43,6 +43,13 @@
+ + + + Reset Selected Device + + + From 64a734432e51fee632718bcd2437c4137d796c36 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 13 May 2019 21:23:21 -0400 Subject: [PATCH 10/20] Add documentation for the new temporal window --- docs/source/images/TemporalWindow.png | Bin 0 -> 149767 bytes docs/source/temporalwindow.rst | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 docs/source/images/TemporalWindow.png create mode 100644 docs/source/temporalwindow.rst diff --git a/docs/source/images/TemporalWindow.png b/docs/source/images/TemporalWindow.png new file mode 100644 index 0000000000000000000000000000000000000000..836fa708cb582921dc5dbad2d30cada5aef97eca GIT binary patch literal 149767 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%0Dw;?TCOQTcEqCK7UJa1mqsT) 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

BxUFkq3#(JKxH(0+?(U0}p_*@*N>=Z~hGD?uP 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 J>D|X6{|AZ!kjwx8 literal 0 HcmV?d00001 diff --git a/docs/source/temporalwindow.rst b/docs/source/temporalwindow.rst new file mode 100644 index 0000000..577b322 --- /dev/null +++ b/docs/source/temporalwindow.rst @@ -0,0 +1,22 @@ +Temporal Window +================= + +. + +.. image:: ./images/TemporalWindow.png + +Using the Temporal Window +========================= + +This window gives a dual graphical representation of CAN IDs over time. For each frame in the capture a circle is placed +on the graph at a point given by the time when the frame came in on the X axis and the frame ID on the Y axis. That is, a frame with ID 0x300 +that comes in at 23.543 seconds would be plotted at the point 23.543, 0x300. The graph background is color coded for "density." That brings up +the question "What do you mean by density?" The answer is somewhat nebulous but essentially the color coded density maps where there is a lot of +traffic within a small space on the graph. This could be because many frames with the same ID came in rapid fire or it could be because many +frames with similar IDs came in very close to each other - or both. + +The general point of this window is to show how active the bus is at any given point in time and which IDs are most active (they'll create +bright streaks in the background color). There isn't a lot that can be done to modify the functionality of this window. All that can be done is +zooming and panning the view. If you get the view too messed up the R key will reset the view back to standard. As with the Graphing Window, it is +possible to select just the X or Y axis and zoom/pan that axis without affecting the other one. To do so, click on the axis marks of the axis you'd like +to independently control. To control both again click in the graph itself. From 682fd9f713c11f144e90e4929f41b4f9d412cfcd Mon Sep 17 00:00:00 2001 From: Nick Kirkby Date: Wed, 15 May 2019 13:30:18 -0700 Subject: [PATCH 11/20] adding instructions on how to compile in debug mode, and how to resolve qmake dependencies in Ubuntu --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index e479a14..9040884 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,14 @@ Now run SavvyCAN ./SavvyCAN ``` +### Compiling in debug mode for additional information + +```sh +qmake CONFIG+=debug + +make +``` + ## What to do if your compile failed? The very first thing to do is try: @@ -86,3 +94,6 @@ make Did that fix it? Great! If not, ensure that you selected SerialBUS support when you installed Qt. +### What to do if `qmake` fails with error `Project ERROR: Unknown module(s) in QT: qml serialbus help` on Ubuntu? : + +`sudo apt install libqt5serialbus5-dev qtdeclarative5-dev qttools5-dev` From 377b42c8e7bec74cd5792fdd7be07f8abe64582b Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 27 May 2019 10:08:31 -0400 Subject: [PATCH 12/20] Fix some bugs related to graphing and loading graph definitions --- re/graphingwindow.cpp | 2 +- re/newgraphdialog.cpp | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/re/graphingwindow.cpp b/re/graphingwindow.cpp index 41d8505..0ab6952 100644 --- a/re/graphingwindow.cpp +++ b/re/graphingwindow.cpp @@ -81,7 +81,7 @@ GraphingWindow::GraphingWindow(const QVector *frames, QWidget *parent) selectedPen.setWidth(1); selectedPen.setColor(Qt::blue); - ui->graphingView->setAttribute(Qt::WA_AcceptTouchEvents); + //ui->graphingView->setAttribute(Qt::WA_AcceptTouchEvents); if (useOpenGL) { diff --git a/re/newgraphdialog.cpp b/re/newgraphdialog.cpp index 1e13110..1ee0227 100644 --- a/re/newgraphdialog.cpp +++ b/re/newgraphdialog.cpp @@ -93,8 +93,8 @@ void NewGraphDialog::checkSignalAgreement() GraphParams testingParams; bool bAgree = true; bool sigSigned = false; - DBC_SIGNAL *sig; - DBC_MESSAGE *msg; + DBC_SIGNAL *sig = nullptr; + DBC_MESSAGE *msg = nullptr; if (dbcHandler == nullptr) return; if (dbcHandler->getFileCount() == 0) return; @@ -104,6 +104,11 @@ void NewGraphDialog::checkSignalAgreement() { sig = msg->sigHandler->findSignalByName(ui->cbSignals->currentText()); } + else + { + ui->lblMsgStatus->setText("Msg ID doesn't exist in DBC"); + return; + } if (sig) { @@ -125,6 +130,12 @@ void NewGraphDialog::checkSignalAgreement() if (testingParams.startBit != sig->startBit) bAgree = false; if (testingParams.numBits != sig->signalSize) bAgree = false; } + else + { + ui->lblMsgStatus->setText("Signal name doesn't exist in DBC"); + return; + } + if (bAgree) { ui->lblMsgStatus->setText("Graph params match this signal"); From a0cc88ede21c187e81303d085fc3cf141c04ed95 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 28 May 2019 21:26:33 -0400 Subject: [PATCH 13/20] Fix for a few issues with DBC file loading and the ability to reorder DBC files. --- dbc/dbchandler.cpp | 10 +++++++--- dbc/dbcloadsavewindow.cpp | 6 ++++++ dbc/dbcloadsavewindow.h | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/dbc/dbchandler.cpp b/dbc/dbchandler.cpp index 6bf7c85..e86483f 100644 --- a/dbc/dbchandler.cpp +++ b/dbc/dbchandler.cpp @@ -460,9 +460,13 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg) if (!sig.receiver) sig.receiver = findNodeByIdx(0); //apply default if there was no match sig.parentMessage = msg; - msg->sigHandler->addSignal(sig); - if (isMultiplexor) msg->multiplexorSignal = msg->sigHandler->findSignalByName(sig.name); - return msg->sigHandler->findSignalByName(sig.name); + if (msg) + { + msg->sigHandler->addSignal(sig); + if (isMultiplexor) msg->multiplexorSignal = msg->sigHandler->findSignalByName(sig.name); + return msg->sigHandler->findSignalByName(sig.name); + } + else return nullptr; } return nullptr; diff --git a/dbc/dbcloadsavewindow.cpp b/dbc/dbcloadsavewindow.cpp index 07f34ab..327252b 100644 --- a/dbc/dbcloadsavewindow.cpp +++ b/dbc/dbcloadsavewindow.cpp @@ -15,6 +15,8 @@ DBCLoadSaveWindow::DBCLoadSaveWindow(const QVector *frames, QWidget *p ui->setupUi(this); + inhibitCellProcessing = false; + QStringList header; header << "Filename" << "Associated Bus" << "J1939"; ui->tableFiles->setColumnCount(3); @@ -159,6 +161,7 @@ void DBCLoadSaveWindow::editFile() void DBCLoadSaveWindow::cellChanged(int row, int col) { + if (inhibitCellProcessing) return; if (col == 1) //the bus column { DBCFile *file = dbcHandler->getFileByIdx(row); @@ -214,6 +217,7 @@ void DBCLoadSaveWindow::swapTableRows(bool up) const int destIdx = (up ? idx-1 : idx+1); Q_ASSERT(destIdx >= 0 && destIdx < ui->tableFiles->rowCount()); + inhibitCellProcessing = true; // take whole rows QList sourceItems = takeRow(idx); QList destItems = takeRow(destIdx); @@ -221,6 +225,8 @@ void DBCLoadSaveWindow::swapTableRows(bool up) // set back in reverse order setRow(idx, destItems); setRow(destIdx, sourceItems); + + inhibitCellProcessing = false; } QList DBCLoadSaveWindow::takeRow(int row) diff --git a/dbc/dbcloadsavewindow.h b/dbc/dbcloadsavewindow.h index a307a86..d724fc1 100644 --- a/dbc/dbcloadsavewindow.h +++ b/dbc/dbcloadsavewindow.h @@ -35,6 +35,7 @@ private: DBCFile *currentlyEditingFile; const QVector *referenceFrames; DBCMainEditor *editorWindow; + bool inhibitCellProcessing; void swapTableRows(bool up); QList takeRow(int row); From 2552debd6d1759523bf988ede1e564838f7f10a8 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 28 May 2019 21:47:04 -0400 Subject: [PATCH 14/20] Sort messages and signals alphabetically to make it easier to find them. Also fixed a bug that would not allow signals to be found if not in the first loaded DBC file. --- re/newgraphdialog.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/re/newgraphdialog.cpp b/re/newgraphdialog.cpp index 1ee0227..960271f 100644 --- a/re/newgraphdialog.cpp +++ b/re/newgraphdialog.cpp @@ -225,21 +225,19 @@ void NewGraphDialog::loadMessages() if (assocSignal && msg->name == assocSignal->parentMessage->name) { ui->cbMessages->setCurrentIndex(ui->cbMessages->count() -1); - qDebug() << "Found my parent"; + //qDebug() << "Found my parent"; } } } } + ui->cbMessages->model()->sort(0); } void NewGraphDialog::loadSignals(int idx) { Q_UNUSED(idx); - //messages were placed into the list in the same order as they exist - //in the data structure so it should have been possible to just - //look it up based on index but by name is probably safer and this operation - //is not time critical at all. - DBC_MESSAGE *msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByName(ui->cbMessages->currentText()); + //search through all DBC files in order to try to find a message with the given name + DBC_MESSAGE *msg = dbcHandler->findMessage(ui->cbMessages->currentText()); DBC_SIGNAL *sig; if (msg == NULL) return; @@ -254,10 +252,11 @@ void NewGraphDialog::loadSignals(int idx) if (assocSignal && sig->name == assocSignal->name) { ui->cbSignals->setCurrentIndex(ui->cbSignals->count() - 1); - qDebug() << "Found me"; + //qDebug() << "Found me"; } } } + ui->cbSignals->model()->sort(0); checkSignalAgreement(); } From 828733340fd7e8e84d990ee017b9d45712d0b7b8 Mon Sep 17 00:00:00 2001 From: Nick Kirkby Date: Tue, 11 Jun 2019 00:21:45 -0700 Subject: [PATCH 15/20] #194: new filters default to false if any existing filters are false --- canframemodel.cpp | 18 +++++++++++++++++- canframemodel.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/canframemodel.cpp b/canframemodel.cpp index 645b70b..214c6cf 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -503,6 +503,18 @@ QVariant CANFrameModel::headerData(int section, Qt::Orientation orientation, return QVariant(); } +bool CANFrameModel::any_filters_are_configured(void) +{ + for (auto const &val : filters) + { + if (val == true) + continue; + else + return true; + } + return false; +} + void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) { @@ -517,7 +529,11 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) //if this ID isn't found in the filters list then add it and show it by default if (!filters.contains(tempFrame.ID)) { - filters.insert(tempFrame.ID, true); + // if there are any filters already configured, leave the new filter disabled + if (any_filters_are_configured()) + filters.insert(tempFrame.ID, false); + else + filters.insert(tempFrame.ID, true); needFilterRefresh = true; } diff --git a/canframemodel.h b/canframemodel.h index cc3c1d7..011bf8f 100644 --- a/canframemodel.h +++ b/canframemodel.h @@ -73,6 +73,7 @@ private: void qSortCANFrameAsc(QVector* frames, Column column, int lowerBound, int upperBound); void qSortCANFrameDesc(QVector* frames, Column column, int lowerBound, int upperBound); uint64_t getCANFrameVal(int row, Column col); + bool any_filters_are_configured(void); QVector frames; QVector filteredFrames; From 0b68e79809a9c302340f7e18cb71e8c5c7ef5cf9 Mon Sep 17 00:00:00 2001 From: Nick Kirkby Date: Tue, 11 Jun 2019 01:26:24 -0700 Subject: [PATCH 16/20] #196: fix persistent settings on Ubuntu 19.04 --- main.cpp | 6 +++ mainsettingsdialog.cpp | 89 +++++++++++++++++++++--------------------- mainsettingsdialog.h | 1 - mainwindow.cpp | 5 --- 4 files changed, 50 insertions(+), 51 deletions(-) diff --git a/main.cpp b/main.cpp index 688ff1d..3373145 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,12 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); + //These things are used by QSettings to set up setting storage + a.setOrganizationName("EVTV"); + a.setApplicationName("SavvyCAN"); + a.setOrganizationDomain("evtv.me"); + QSettings::setDefaultFormat(QSettings::IniFormat); + MainWindow w; QSettings settings; diff --git a/mainsettingsdialog.cpp b/mainsettingsdialog.cpp index 1a503d5..71246fb 100644 --- a/mainsettingsdialog.cpp +++ b/mainsettingsdialog.cpp @@ -6,6 +6,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MainSettingsDialog) { + QSettings settings; ui->setupUi(this); ui->comboSendingBus->addItem(tr("None")); @@ -14,29 +15,27 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : ui->comboSendingBus->addItem(tr("Both")); ui->comboSendingBus->addItem(tr("From File")); - settings = new QSettings(); - //update the GUI with all the settings we have stored giving things //defaults if nothing was stored (if this is the first time) - ui->cbDisplayHex->setChecked(settings->value("Main/UseHex", true).toBool()); - ui->cbFlowAutoRef->setChecked(settings->value("FlowView/AutoRef", false).toBool()); - ui->cbFlowUseTimestamp->setChecked(settings->value("FlowView/UseTimestamp", true).toBool()); - ui->cbInfoAutoExpand->setChecked(settings->value("InfoCompare/AutoExpand", false).toBool()); - ui->cbMainAutoScroll->setChecked(settings->value("Main/AutoScroll", false).toBool()); - ui->cbPlaybackLoop->setChecked(settings->value("Playback/AutoLoop", false).toBool()); - ui->cbRestorePositions->setChecked(settings->value("Main/SaveRestorePositions", true).toBool()); - ui->cbValidate->setChecked(settings->value("Main/ValidateComm", true).toBool()); - ui->spinPlaybackSpeed->setValue(settings->value("Playback/DefSpeed", 5).toInt()); - ui->lineClockFormat->setText(settings->value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString()); - ui->lineRemoteHost->setText(settings->value("Remote/Host", "api.savvycan.com").toString()); - ui->lineRemotePort->setText(settings->value("Remote/Port", "21315").toString()); // = 0x5343 = SC. Yep, really creative port number - ui->cbAutoStartRemote->setChecked(settings->value("Remote/AutoStart", false).toBool()); - ui->cbLoadConnections->setChecked(settings->value("Main/SaveRestoreConnections", false).toBool()); + ui->cbDisplayHex->setChecked(settings.value("Main/UseHex", true).toBool()); + ui->cbFlowAutoRef->setChecked(settings.value("FlowView/AutoRef", false).toBool()); + ui->cbFlowUseTimestamp->setChecked(settings.value("FlowView/UseTimestamp", true).toBool()); + ui->cbInfoAutoExpand->setChecked(settings.value("InfoCompare/AutoExpand", false).toBool()); + ui->cbMainAutoScroll->setChecked(settings.value("Main/AutoScroll", false).toBool()); + ui->cbPlaybackLoop->setChecked(settings.value("Playback/AutoLoop", false).toBool()); + ui->cbRestorePositions->setChecked(settings.value("Main/SaveRestorePositions", true).toBool()); + ui->cbValidate->setChecked(settings.value("Main/ValidateComm", true).toBool()); + ui->spinPlaybackSpeed->setValue(settings.value("Playback/DefSpeed", 5).toInt()); + ui->lineClockFormat->setText(settings.value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString()); + ui->lineRemoteHost->setText(settings.value("Remote/Host", "api.savvycan.com").toString()); + ui->lineRemotePort->setText(settings.value("Remote/Port", "21315").toString()); // = 0x5343 = SC. Yep, really creative port number + ui->cbAutoStartRemote->setChecked(settings.value("Remote/AutoStart", false).toBool()); + ui->cbLoadConnections->setChecked(settings.value("Main/SaveRestoreConnections", false).toBool()); - ui->spinFontSize->setValue(settings->value("Main/FontSize", ui->cbDisplayHex->font().pointSize()).toUInt()); + ui->spinFontSize->setValue(settings.value("Main/FontSize", ui->cbDisplayHex->font().pointSize()).toUInt()); - bool secondsMode = settings->value("Main/TimeSeconds", false).toBool(); - bool clockMode = settings->value("Main/TimeClock", false).toBool(); + bool secondsMode = settings.value("Main/TimeSeconds", false).toBool(); + bool clockMode = settings.value("Main/TimeClock", false).toBool(); if (clockMode) { ui->rbSeconds->setChecked(false); @@ -59,9 +58,9 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : } } - ui->comboSendingBus->setCurrentIndex(settings->value("Playback/SendingBus", 4).toInt()); - ui->cbUseFiltered->setChecked(settings->value("Main/UseFiltered", false).toBool()); - ui->cbUseOpenGL->setChecked(settings->value("Main/UseOpenGL", false).toBool()); + ui->comboSendingBus->setCurrentIndex(settings.value("Playback/SendingBus", 4).toInt()); + ui->cbUseFiltered->setChecked(settings.value("Main/UseFiltered", false).toBool()); + ui->cbUseOpenGL->setChecked(settings.value("Main/UseOpenGL", false).toBool()); //just for simplicity they all call the same function and that function updates all settings at once connect(ui->cbDisplayHex, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); @@ -90,7 +89,6 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : MainSettingsDialog::~MainSettingsDialog() { delete ui; - delete settings; } void MainSettingsDialog::closeEvent(QCloseEvent *event) @@ -98,7 +96,6 @@ void MainSettingsDialog::closeEvent(QCloseEvent *event) Q_UNUSED(event); removeEventFilter(this); updateSettings(); - settings->sync(); } bool MainSettingsDialog::eventFilter(QObject *obj, QEvent *event) @@ -122,27 +119,29 @@ bool MainSettingsDialog::eventFilter(QObject *obj, QEvent *event) void MainSettingsDialog::updateSettings() { - settings->setValue("Main/UseHex", ui->cbDisplayHex->isChecked()); - settings->setValue("FlowView/AutoRef", ui->cbFlowAutoRef->isChecked()); - settings->setValue("FlowView/UseTimestamp", ui->cbFlowUseTimestamp->isChecked()); - settings->setValue("InfoCompare/AutoExpand", ui->cbInfoAutoExpand->isChecked()); - settings->setValue("Main/AutoScroll", ui->cbMainAutoScroll->isChecked()); - settings->setValue("Playback/AutoLoop", ui->cbPlaybackLoop->isChecked()); - settings->setValue("Main/SaveRestorePositions", ui->cbRestorePositions->isChecked()); - settings->setValue("Main/SaveRestoreConnections", ui->cbLoadConnections->isChecked()); - settings->setValue("Main/ValidateComm", ui->cbValidate->isChecked()); - settings->setValue("Playback/DefSpeed", ui->spinPlaybackSpeed->value()); - settings->setValue("Main/TimeSeconds", ui->rbSeconds->isChecked()); - settings->setValue("Main/TimeClock", ui->rbSysClock->isChecked()); - settings->setValue("Playback/SendingBus", ui->comboSendingBus->currentIndex()); - settings->setValue("Main/UseFiltered", ui->cbUseFiltered->isChecked()); - settings->setValue("Main/UseOpenGL", ui->cbUseOpenGL->isChecked()); - settings->setValue("Main/TimeFormat", ui->lineClockFormat->text()); - settings->setValue("Main/FontSize", ui->spinFontSize->value()); - settings->setValue("Remote/Host", ui->lineRemoteHost->text()); - settings->setValue("Remote/Port", ui->lineRemotePort->text()); - settings->setValue("Remote/AutoStart", ui->cbAutoStartRemote->isChecked()); + QSettings settings; - settings->sync(); + settings.setValue("Main/UseHex", ui->cbDisplayHex->isChecked()); + settings.setValue("FlowView/AutoRef", ui->cbFlowAutoRef->isChecked()); + settings.setValue("FlowView/UseTimestamp", ui->cbFlowUseTimestamp->isChecked()); + settings.setValue("InfoCompare/AutoExpand", ui->cbInfoAutoExpand->isChecked()); + settings.setValue("Main/AutoScroll", ui->cbMainAutoScroll->isChecked()); + settings.setValue("Playback/AutoLoop", ui->cbPlaybackLoop->isChecked()); + settings.setValue("Main/SaveRestorePositions", ui->cbRestorePositions->isChecked()); + settings.setValue("Main/SaveRestoreConnections", ui->cbLoadConnections->isChecked()); + settings.setValue("Main/ValidateComm", ui->cbValidate->isChecked()); + settings.setValue("Playback/DefSpeed", ui->spinPlaybackSpeed->value()); + settings.setValue("Main/TimeSeconds", ui->rbSeconds->isChecked()); + settings.setValue("Main/TimeClock", ui->rbSysClock->isChecked()); + settings.setValue("Playback/SendingBus", ui->comboSendingBus->currentIndex()); + settings.setValue("Main/UseFiltered", ui->cbUseFiltered->isChecked()); + settings.setValue("Main/UseOpenGL", ui->cbUseOpenGL->isChecked()); + settings.setValue("Main/TimeFormat", ui->lineClockFormat->text()); + settings.setValue("Main/FontSize", ui->spinFontSize->value()); + settings.setValue("Remote/Host", ui->lineRemoteHost->text()); + settings.setValue("Remote/Port", ui->lineRemotePort->text()); + settings.setValue("Remote/AutoStart", ui->cbAutoStartRemote->isChecked()); + + settings.sync(); emit updatedSettings(); } diff --git a/mainsettingsdialog.h b/mainsettingsdialog.h index d3ef3a5..0ffb0ec 100644 --- a/mainsettingsdialog.h +++ b/mainsettingsdialog.h @@ -24,7 +24,6 @@ public slots: private: Ui::MainSettingsDialog *ui; - QSettings *settings; void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); diff --git a/mainwindow.cpp b/mainwindow.cpp index 56e6844..5deac5a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -34,11 +34,6 @@ MainWindow::MainWindow(QWidget *parent) : useHex = true; - //These things are used by QSettings to set up setting storage - QCoreApplication::setOrganizationName("EVTV"); - QCoreApplication::setOrganizationDomain("evtv.me"); - QCoreApplication::setApplicationName("SavvyCAN"); - selfRef = this; this->setWindowTitle("Savvy CAN V" + QString::number(VERSION)); From 618879870e70eae151b78ced9bd40dacfb35385f Mon Sep 17 00:00:00 2001 From: Nick Kirkby Date: Tue, 11 Jun 2019 17:13:24 -0700 Subject: [PATCH 17/20] show tooltip for socketCAN error string --- connections/newconnectiondialog.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/connections/newconnectiondialog.cpp b/connections/newconnectiondialog.cpp index 92ee31b..1f03eba 100644 --- a/connections/newconnectiondialog.cpp +++ b/connections/newconnectiondialog.cpp @@ -9,7 +9,18 @@ NewConnectionDialog::NewConnectionDialog(QVector* ips, QWidget *parent) { ui->setupUi(this); - ui->rbSocketCAN->setEnabled(isSerialBusAvailable()); + if (isSerialBusAvailable()) + { + ui->rbSocketCAN->setEnabled(true); + } + else + { + ui->rbSocketCAN->setEnabled(false); + QString errorString; + const QList devices = QCanBus::instance()->availableDevices(QStringLiteral("socketcan"), &errorString); + if (!errorString.isEmpty()) ui->rbSocketCAN->setToolTip(errorString); + } + connect(ui->rbGVRET, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->rbSocketCAN, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); From d75edabbfe0e7a44ddb91e2cfe6b76b00f473f17 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 11 Jun 2019 20:58:54 -0400 Subject: [PATCH 18/20] Change abs to fabs so it will properly compile on Raspberry Pi --- re/newgraphdialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/re/newgraphdialog.cpp b/re/newgraphdialog.cpp index 960271f..cdb49b6 100644 --- a/re/newgraphdialog.cpp +++ b/re/newgraphdialog.cpp @@ -123,10 +123,10 @@ void NewGraphDialog::checkSignalAgreement() testingParams.numBits = dataLen; if (testingParams.ID != msg->ID) bAgree = false; - if (abs(testingParams.bias - sig->bias) > 0.01) bAgree = false; + if (fabs(testingParams.bias - sig->bias) > 0.01) bAgree = false; if (testingParams.isSigned != sigSigned) bAgree = false; if (testingParams.intelFormat != sig->intelByteOrder) bAgree = false; - if (abs(testingParams.scale - sig->factor) > 0.01) bAgree = false; + if (fabs(testingParams.scale - sig->factor) > 0.01) bAgree = false; if (testingParams.startBit != sig->startBit) bAgree = false; if (testingParams.numBits != sig->signalSize) bAgree = false; } From 3c262693ac4523eb143fb1072a8972917c6c41f9 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 11 Jun 2019 21:54:47 -0400 Subject: [PATCH 19/20] Overwrite mode now respects the bus number and will only overwrite frames with the same ID and bus number. Overwrite now also shows time delta instead of timestamp. --- can_structs.h | 2 ++ canframemodel.cpp | 45 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/can_structs.h b/can_structs.h index 74e5b0a..df976f5 100644 --- a/can_structs.h +++ b/can_structs.h @@ -16,6 +16,7 @@ public: uint32_t len; unsigned char data[8]; uint64_t timestamp; + uint64_t timedelta; uint32_t frameCount; //used in overwrite mode friend bool operator<(const CANFrame& l, const CANFrame& r) @@ -32,6 +33,7 @@ public: isReceived = true; len = 0; timestamp = 0; + timedelta = 0; frameCount = 1; } }; diff --git a/canframemodel.cpp b/canframemodel.cpp index 645b70b..cdd57cc 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -176,6 +176,11 @@ void CANFrameModel::setAllFilters(bool state) sendRefresh(); } +/* + * There is probably a more correct way to have done this but below are several functions that collectively implement + * quicksort on the columns and interpret the columns numerically. But, correct or not, this implementation is quite fast + * and sorts the columns properly. +*/ uint64_t CANFrameModel::getCANFrameVal(int row, Column col) { uint64_t temp = 0; @@ -184,6 +189,7 @@ uint64_t CANFrameModel::getCANFrameVal(int row, Column col) switch (col) { case Column::TimeStamp: + if (overwriteDups) return frame.timedelta; return frame.timestamp; case Column::FrameId: return frame.ID; @@ -191,6 +197,7 @@ uint64_t CANFrameModel::getCANFrameVal(int row, Column col) if (frame.extended) return 1; return 0; case Column::Remote: + if (overwriteDups) return frame.frameCount; if (frame.remote) return 1; return 0; case Column::Direction: @@ -280,6 +287,8 @@ void CANFrameModel::sortByColumn(int column) sendRefresh(); } +//End of custom sorting code + void CANFrameModel::recalcOverwrite() { if (!overwriteDups) return; //no need to do a thing if mode is disabled @@ -293,14 +302,24 @@ void CANFrameModel::recalcOverwrite() beginResetModel(); //Look at the current list of frames and turn it into just a list of unique IDs - QHash overWriteFrames; + QHash overWriteFrames; + uint64_t idAugmented; //id in lower 29 bits, bus number shifted up 29 bits foreach(CANFrame frame, frames) { - if (!overWriteFrames.contains(frame.ID)) + idAugmented = frame.ID; + idAugmented = idAugmented + (frame.bus << 29ull); + if (!overWriteFrames.contains(idAugmented)) { - overWriteFrames.insert(frame.ID, frame); + frame.timedelta = 0; + frame.frameCount = 1; + overWriteFrames.insert(idAugmented, frame); + } + else + { + frame.timedelta = frame.timestamp - overWriteFrames[idAugmented].timestamp; + frame.frameCount = overWriteFrames[idAugmented].frameCount + 1; + overWriteFrames[idAugmented] = frame; } - else overWriteFrames[frame.ID] = frame; } //Then replace the old list of frames with just the unique list frames.clear(); @@ -373,9 +392,14 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole) { switch (Column(index.column())) { - case Column::TimeStamp: + case Column::TimeStamp: //Reformatting the output a bit with custom code - ts = Utility::formatTimestamp(thisFrame.timestamp); + if (overwriteDups) + { + if (timeSeconds) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5); + return QString::number(thisFrame.timedelta); + } + else ts = Utility::formatTimestamp(thisFrame.timestamp); if (ts.type() == QVariant::Double) return QString::number(ts.toDouble(), 'f', 5); //never scientific notation, 5 decimal places if (ts.type() == QVariant::LongLong) return QString::number(ts.toLongLong()); //never scientific notion, all digits shown if (ts.type() == QVariant::DateTime) return ts.toDateTime().toString(timeFormat); //custom set format for dates and times @@ -474,13 +498,14 @@ QVariant CANFrameModel::headerData(int section, Qt::Orientation orientation, switch (Column(section)) { case Column::TimeStamp: + if (overwriteDups) return QString(tr("Time Delta")); return QString(tr("Timestamp")); case Column::FrameId: return QString(tr("ID")); case Column::Extended: return QString(tr("Ext")); case Column::Remote: - if (!overwriteDups) return QString(tr("Rem")); + if (!overwriteDups) return QString(tr("RTR")); return QString(tr("Cnt")); case Column::Direction: return QString(tr("Dir")); @@ -537,9 +562,10 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) bool found = false; for (int i = 0; i < frames.count(); i++) { - if (frames[i].ID == tempFrame.ID) + if ( (frames[i].ID == tempFrame.ID) && (frames[i].bus == tempFrame.bus) ) { tempFrame.frameCount = frames[i].frameCount + 1; + tempFrame.timedelta = tempFrame.timestamp - frames[i].timestamp; frames.replace(i, tempFrame); found = true; break; @@ -552,6 +578,7 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) { if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count()); tempFrame.frameCount = 1; + tempFrame.timedelta = 0; filteredFrames.append(tempFrame); if (autoRefresh) endInsertRows(); } @@ -560,7 +587,7 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) { for (int j = 0; j < filteredFrames.count(); j++) { - if (filteredFrames[j].ID == tempFrame.ID) + if ( (filteredFrames[j].ID == tempFrame.ID) && (filteredFrames[j].bus == tempFrame.bus) ) { if (autoRefresh) beginResetModel(); filteredFrames.replace(j, tempFrame); From d4a735a710f6d1219f9288f5bfef90d0b21f997d Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 17 Jun 2019 20:58:56 -0400 Subject: [PATCH 20/20] Allow many places to load and save the last directory used for that functionality so it can easily be used again. All frame file I/O uses the same directory load/save location but DBC files store separately as do filter files, graphing definitions, and stored text files from the various reverse engineering windows. --- config.h | 2 +- dbc/dbchandler.cpp | 9 ++++++++- framefileio.cpp | 10 ++++++++++ frameplaybackwindow.cpp | 6 ++++++ framesenderwindow.cpp | 7 ++++++- mainwindow.cpp | 11 +++++++++-- re/filecomparatorwindow.cpp | 3 +++ re/flowviewwindow.cpp | 6 ++++++ re/frameinfowindow.cpp | 3 +++ re/graphingwindow.cpp | 15 +++++++++++++++ re/udsscanwindow.cpp | 4 ++++ scriptingwindow.cpp | 6 ++++++ 12 files changed, 77 insertions(+), 5 deletions(-) diff --git a/config.h b/config.h index cc815a5..163928a 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 198 +#define VERSION 199 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision diff --git a/dbc/dbchandler.cpp b/dbc/dbchandler.cpp index e86483f..d22c023 100644 --- a/dbc/dbchandler.cpp +++ b/dbc/dbchandler.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "utility.h" #include "connections/canconmanager.h" @@ -1339,6 +1340,8 @@ void DBCFile::saveFile(QString fileName) void DBCHandler::saveDBCFile(int idx) { + QSettings settings; + if (loadedFiles.count() == 0) return; if (idx < 0) return; if (idx >= loadedFiles.count()) return; @@ -1349,6 +1352,7 @@ void DBCHandler::saveDBCFile(int idx) QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); + dialog.setDirectory(settings.value("DBC/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -1360,6 +1364,7 @@ void DBCHandler::saveDBCFile(int idx) filename = dialog.selectedFiles()[0]; if (!filename.contains('.')) filename += ".dbc"; loadedFiles[idx].saveFile(filename); + settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path()); } } @@ -1411,10 +1416,12 @@ DBCFile* DBCHandler::loadDBCFile(int idx) QString filename; QFileDialog dialog; + QSettings settings; QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); + dialog.setDirectory(settings.value("DBC/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -1426,7 +1433,7 @@ DBCFile* DBCHandler::loadDBCFile(int idx) DBCFile newFile; newFile.loadFile(filename); loadedFiles.append(newFile); - + settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path()); return &loadedFiles.last(); } diff --git a/framefileio.cpp b/framefileio.cpp index e6a9d17..5b105aa 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "utility.h" @@ -19,6 +20,7 @@ bool FrameFileIO::saveFrameFile(QString &fileName, const QVector* fram { QString filename; QFileDialog dialog(qApp->activeWindow()); + QSettings settings; bool result = false; QStringList filters; @@ -35,6 +37,7 @@ bool FrameFileIO::saveFrameFile(QString &fileName, const QVector* fram filters.append(QString(tr("Cabana Log(*.csv *.CSV)"))); filters.append(QString(tr("CANalyzer Ascii Log (*.asc *.ASC)"))); + dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -125,6 +128,7 @@ bool FrameFileIO::saveFrameFile(QString &fileName, const QVector* fram { QStringList fileList = filename.split('/'); fileName = fileList[fileList.length() - 1]; + settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); return true; } return false; @@ -136,6 +140,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache { QString filename; QFileDialog dialog; + QSettings settings; bool result = false; QStringList filters; @@ -159,6 +164,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache filters.append(QString(tr("Cabana Log (*.csv *.CSV)"))); filters.append(QString(tr("CANOpen Magic (*.csv *.CSV)"))); + dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -203,6 +209,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache { QStringList fileList = filename.split('/'); fileName = fileList[fileList.length() - 1]; + settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); return true; } else @@ -1575,10 +1582,12 @@ bool FrameFileIO::openContinuousNative() { QString filename; QFileDialog dialog(qApp->activeWindow()); + QSettings settings; QStringList filters; filters.append(QString(tr("GVRET Logs (*.csv *.CSV)"))); + dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -1595,6 +1604,7 @@ bool FrameFileIO::openContinuousNative() } continuousFile.write("Time Stamp,ID,Extended,Dir,Bus,LEN,D1,D2,D3,D4,D5,D6,D7,D8"); continuousFile.write("\n"); + settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); return true; } return false; diff --git a/frameplaybackwindow.cpp b/frameplaybackwindow.cpp index d200e0c..a156adb 100644 --- a/frameplaybackwindow.cpp +++ b/frameplaybackwindow.cpp @@ -173,10 +173,12 @@ void FramePlaybackWindow::saveFilters() { 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); @@ -203,6 +205,7 @@ void FramePlaybackWindow::saveFilters() } outFile->close(); + dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString()); } } @@ -212,10 +215,12 @@ void FramePlaybackWindow::loadFilters() { 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); @@ -264,6 +269,7 @@ void FramePlaybackWindow::loadFilters() } } inFile->close(); + dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString()); } } diff --git a/framesenderwindow.cpp b/framesenderwindow.cpp index 54ab529..9d11332 100644 --- a/framesenderwindow.cpp +++ b/framesenderwindow.cpp @@ -241,10 +241,12 @@ void FrameSenderWindow::saveGrid() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Frame Sender Definition (*.fsd)"))); + dialog.setDirectory(settings.value("FrameSender/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -258,6 +260,7 @@ void FrameSenderWindow::saveGrid() { if (!filename.contains('.')) filename += ".fsd"; saveSenderFile(filename); + settings.setValue("FrameSender/LoadSaveDirectory", dialog.directory().path()); } } } @@ -266,10 +269,12 @@ void FrameSenderWindow::loadGrid() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Frame Sender Definition (*.fsd)"))); + dialog.setDirectory(settings.value("FrameSender/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -281,6 +286,7 @@ void FrameSenderWindow::loadGrid() if (dialog.selectedNameFilter() == filters[0]) { loadSenderFile(filename); + settings.setValue("FrameSender/LoadSaveDirectory", dialog.directory().path()); } } @@ -325,7 +331,6 @@ void FrameSenderWindow::saveSenderFile(QString filename) outFile->close(); delete outFile; - } void FrameSenderWindow::loadSenderFile(QString filename) diff --git a/mainwindow.cpp b/mainwindow.cpp index 56e6844..ca9c39d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -650,10 +650,12 @@ 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); @@ -664,6 +666,7 @@ void MainWindow::handleSaveFilters() filename = dialog.selectedFiles()[0]; if (!filename.contains('.')) filename += ".ftl"; if (dialog.selectedNameFilter() == filters[0]) model->saveFilterFile(filename); + settings.setValue("Filters/LoadSaveDirectory", dialog.directory().path()); } } @@ -671,10 +674,12 @@ 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); @@ -684,19 +689,20 @@ void MainWindow::handleLoadFilters() 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()); } } -//lbStatusDatabase.setText(fileList[fileList.length() - 1] + tr(" loaded.")); - 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); @@ -707,6 +713,7 @@ void MainWindow::handleSaveDecoded() filename = dialog.selectedFiles()[0]; if (!filename.contains('.')) filename += ".txt"; saveDecodedTextFile(filename); + settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); } } diff --git a/re/filecomparatorwindow.cpp b/re/filecomparatorwindow.cpp index 3102142..8e27102 100644 --- a/re/filecomparatorwindow.cpp +++ b/re/filecomparatorwindow.cpp @@ -367,6 +367,7 @@ void FileComparatorWindow::saveDetails() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Text File (*.txt)"))); @@ -375,10 +376,12 @@ void FileComparatorWindow::saveDetails() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("FileComparator/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; + settings.setValue("FileComparator/LoadSaveDirectory", dialog.directory().path()); if (!filename.contains('.')) filename += ".txt"; QFile *outFile = new QFile(filename); diff --git a/re/flowviewwindow.cpp b/re/flowviewwindow.cpp index 5092bd6..99f206a 100644 --- a/re/flowviewwindow.cpp +++ b/re/flowviewwindow.cpp @@ -297,6 +297,7 @@ void FlowViewWindow::saveFileGraph() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("PDF Files (*.pdf)"))); @@ -307,6 +308,7 @@ void FlowViewWindow::saveFileGraph() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("FlowView/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { @@ -327,6 +329,7 @@ void FlowViewWindow::saveFileGraph() if (!filename.contains('.')) filename += ".jpg"; ui->graphView->saveJpg(filename, 1024, 768); } + settings.setValue("FlowView/LoadSaveDirectory", dialog.directory().path()); } } @@ -334,6 +337,7 @@ void FlowViewWindow::saveFileFlow() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("PNG Files (*.png)"))); @@ -343,6 +347,7 @@ void FlowViewWindow::saveFileFlow() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("FlowView/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { @@ -358,6 +363,7 @@ void FlowViewWindow::saveFileFlow() if (!filename.contains('.')) filename += ".jpg"; ui->flowView->saveImage(filename, 1024, 768); } + settings.setValue("FlowView/LoadSaveDirectory", dialog.directory().path()); } } diff --git a/re/frameinfowindow.cpp b/re/frameinfowindow.cpp index 76710e8..a59b265 100644 --- a/re/frameinfowindow.cpp +++ b/re/frameinfowindow.cpp @@ -477,6 +477,7 @@ void FrameInfoWindow::saveDetails() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Text File (*.txt)"))); @@ -485,9 +486,11 @@ void FrameInfoWindow::saveDetails() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("FrameInfo/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { + settings.setValue("FrameInfo/LoadSaveDirectory", dialog.directory().path()); filename = dialog.selectedFiles()[0]; if (!filename.contains('.')) filename += ".txt"; if (dialog.selectedNameFilter() == filters[0]) diff --git a/re/graphingwindow.cpp b/re/graphingwindow.cpp index 0ab6952..81847d8 100644 --- a/re/graphingwindow.cpp +++ b/re/graphingwindow.cpp @@ -633,6 +633,7 @@ void GraphingWindow::saveGraphs() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("PDF Files (*.pdf)"))); @@ -643,10 +644,12 @@ void GraphingWindow::saveGraphs() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("Graphing/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; + settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); if (dialog.selectedNameFilter() == filters[0]) { @@ -670,6 +673,7 @@ void GraphingWindow::saveSpreadsheet() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Spreadsheet (*.csv)"))); @@ -678,10 +682,13 @@ void GraphingWindow::saveSpreadsheet() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("Graphing/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; + settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); + if (!filename.contains('.')) filename += ".csv"; QFile *outFile = new QFile(filename); @@ -789,6 +796,7 @@ void GraphingWindow::saveDefinitions() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Graph definition (*.gdf)"))); @@ -797,10 +805,13 @@ void GraphingWindow::saveDefinitions() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("Graphing/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; + settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); + if (!filename.contains('.')) filename += ".gdf"; QFile *outFile = new QFile(filename); @@ -847,6 +858,7 @@ void GraphingWindow::loadDefinitions() { QString filename; QFileDialog dialog; + QSettings settings; QStringList filters; filters.append(QString(tr("Graph definition (*.gdf)"))); @@ -857,10 +869,13 @@ void GraphingWindow::loadDefinitions() dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); + dialog.setDirectory(settings.value("Graphing/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; + settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); + QFile *inFile = new QFile(filename); QByteArray line; diff --git a/re/udsscanwindow.cpp b/re/udsscanwindow.cpp index c86955f..3199efb 100644 --- a/re/udsscanwindow.cpp +++ b/re/udsscanwindow.cpp @@ -145,6 +145,7 @@ void UDSScanWindow::saveResults() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Text File (*.txt)"))); @@ -153,10 +154,13 @@ void UDSScanWindow::saveResults() dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("UDSScan/LoadSaveDirectory", dialog.directory().path()).toString()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; + settings.setValue("UDSScan/LoadSaveDirectory", dialog.directory().path()); + if (!filename.contains('.')) filename += ".txt"; if (dialog.selectedNameFilter() == filters[0]) { diff --git a/scriptingwindow.cpp b/scriptingwindow.cpp index 816635c..7a230db 100644 --- a/scriptingwindow.cpp +++ b/scriptingwindow.cpp @@ -180,11 +180,13 @@ void ScriptingWindow::loadNewScript() { QString filename; QFileDialog dialog; + QSettings settings; ScriptContainer *container; QStringList filters; filters.append(QString(tr("Javascript File (*.js)"))); + dialog.setDirectory(settings.value("ScriptingWindow/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -215,6 +217,7 @@ void ScriptingWindow::loadNewScript() ui->listLoadedScripts->setCurrentRow(ui->listLoadedScripts->count() - 1); changeCurrentScript(); + settings.setValue("ScriptingWindow/LoadSaveDirectory", dialog.directory().path()); } } } @@ -287,10 +290,12 @@ void ScriptingWindow::saveScript() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Javascript File (*.js)"))); + dialog.setDirectory(settings.value("ScriptingWindow/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); @@ -312,6 +317,7 @@ void ScriptingWindow::saveScript() outFile->write(editor->toPlainText().toUtf8()); outFile->close(); delete outFile; + settings.setValue("ScriptingWindow/LoadSaveDirectory", dialog.directory().path()); } } }