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` 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/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 5865268..79f557c 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,119 @@ 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; + if (row >= frames.count()) return 0; + CANFrame frame = frames[row]; + switch (col) + { + case Column::TimeStamp: + if (overwriteDups) return frame.timedelta; + return frame.timestamp; + case Column::FrameId: + return frame.ID; + case Column::Extended: + 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: + 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(); +} + +//End of custom sorting code + void CANFrameModel::recalcOverwrite() { if (!overwriteDups) return; //no need to do a thing if mode is disabled @@ -202,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(); @@ -282,7 +392,17 @@ 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 + 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 return Utility::formatTimestamp(thisFrame.timestamp); case Column::FrameId: return Utility::formatCANID(thisFrame.ID, thisFrame.extended); @@ -378,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")); @@ -407,6 +528,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) { @@ -421,7 +554,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; } @@ -441,9 +578,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; @@ -456,6 +594,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(); } @@ -464,7 +603,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); diff --git a/canframemodel.h b/canframemodel.h index 42fd66b..011bf8f 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,11 @@ 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); + bool any_filters_are_configured(void); + QVector frames; QVector filteredFrames; QMap filters; @@ -71,6 +90,7 @@ private: uint64_t timeOffset; int lastUpdateNumFrames; uint32_t preallocSize; + bool sortDirAsc; }; diff --git a/config.h b/config.h index c9a1b1c..163928a 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 197 +#define VERSION 199 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision 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 5155e5f..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) { @@ -277,8 +325,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 +376,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()) @@ -363,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/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); 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/dbc/dbchandler.cpp b/dbc/dbchandler.cpp index 6bf7c85..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" @@ -460,9 +461,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; @@ -1335,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; @@ -1345,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); @@ -1356,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()); } } @@ -1407,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); @@ -1422,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/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); diff --git a/docs/source/images/TemporalWindow.png b/docs/source/images/TemporalWindow.png new file mode 100644 index 0000000..836fa70 Binary files /dev/null and b/docs/source/images/TemporalWindow.png differ 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. 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/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 15181e1..1f9a2a8 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)); @@ -55,9 +50,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))); @@ -83,6 +78,7 @@ MainWindow::MainWindow(QWidget *parent) : snifferWindow = nullptr; bisectWindow = nullptr; signalViewerWindow = nullptr; + temporalGraphWindow = nullptr; dbcHandler = DBCHandler::getReference(); bDirty = false; inhibitFilterUpdate = false; @@ -131,6 +127,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); @@ -186,7 +183,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); @@ -227,6 +223,7 @@ void MainWindow::killEmAll() killWindow(motorctrlConfigWindow); killWindow(signalViewerWindow); killWindow(connectionWindow); + killWindow(temporalGraphWindow); } //forcefully close the window, kill it, and salt the earth @@ -368,7 +365,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) @@ -647,10 +645,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); @@ -661,6 +661,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()); } } @@ -668,10 +669,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); @@ -681,19 +684,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); @@ -704,6 +708,7 @@ void MainWindow::handleSaveDecoded() filename = dialog.selectedFiles()[0]; if (!filename.contains('.')) filename += ".txt"; saveDecodedTextFile(filename); + settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); } } @@ -837,6 +842,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. @@ -1015,7 +1033,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/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/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 7316fc5..81847d8 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) { @@ -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) @@ -629,6 +633,7 @@ void GraphingWindow::saveGraphs() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("PDF Files (*.pdf)"))); @@ -639,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]) { @@ -666,6 +673,7 @@ void GraphingWindow::saveSpreadsheet() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Spreadsheet (*.csv)"))); @@ -674,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); @@ -785,6 +796,7 @@ void GraphingWindow::saveDefinitions() { QString filename; QFileDialog dialog(this); + QSettings settings; QStringList filters; filters.append(QString(tr("Graph definition (*.gdf)"))); @@ -793,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); @@ -843,6 +858,7 @@ void GraphingWindow::loadDefinitions() { QString filename; QFileDialog dialog; + QSettings settings; QStringList filters; filters.append(QString(tr("Graph definition (*.gdf)"))); @@ -853,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/newgraphdialog.cpp b/re/newgraphdialog.cpp index 1e13110..cdb49b6 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) { @@ -118,13 +123,19 @@ 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; } + else + { + ui->lblMsgStatus->setText("Signal name doesn't exist in DBC"); + return; + } + if (bAgree) { ui->lblMsgStatus->setText("Graph params match this signal"); @@ -214,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; @@ -243,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(); } diff --git a/re/temporalgraphwindow.cpp b/re/temporalgraphwindow.cpp new file mode 100644 index 0000000..f61803e --- /dev/null +++ b/re/temporalgraphwindow.cpp @@ -0,0 +1,407 @@ +#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->clearPlottables(); + 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; + + ui->graphingView->clearGraphs(); + ui->graphingView->clearPlottables(); + + qDebug() << "Regenerating the graph"; + ui->graphingView->addGraph(); + graph = ui->graphingView->graph(); + + QVector x, y; + 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::ssCircle); + QPen graphPen; + graphPen.setColor(Qt::blue); + graphPen.setWidth(2); + 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(); + + + 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() +{ + /* + 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_R: + resetView(); + 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() +{ + 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..789f1d3 --- /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; + double xminval, xmaxval, yminval, ymaxval; + void closeEvent(QCloseEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + void readSettings(); + void writeSettings(); + void generateGraph(); + +}; + +#endif // TEMPORALGRAPHWINDOW_H 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()); } } } 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/connectionwindow.ui b/ui/connectionwindow.ui index d132606..12a6ada 100644 --- a/ui/connectionwindow.ui +++ b/ui/connectionwindow.ui @@ -43,6 +43,13 @@ + + + + Reset Selected Device + + + @@ -68,58 +75,44 @@ - - - CAN Bus Number: - - - - - - - - - - - Speed: - + - + Listen Only: - + - + Enable Bus: - + - + Save Bus Settings diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index e21a989..1bda8d1 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -282,6 +282,7 @@ + @@ -424,7 +425,7 @@ - Open Connection WIndow + Open Connection Window @@ -497,6 +498,11 @@ Start Continuous Logging + + + Temporal Graph + + 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 + + + + 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 +
+
+ + +