From fbe89c7afb3e396094012521a33b2f3fef78a9d2 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 22 Jun 2015 19:09:20 -0400 Subject: [PATCH] Addition of a file comparison window that can be used to find what sort of things changed between two files. --- SavvyCAN.pro | 9 +- filecomparatorwindow.cpp | 259 +++++++++++++++++++++++++++++++++++++++ filecomparatorwindow.h | 46 +++++++ filecomparatorwindow.ui | 91 ++++++++++++++ framefileio.cpp | 37 ++++++ framefileio.h | 2 + mainwindow.cpp | 17 +++ mainwindow.h | 3 + mainwindow.ui | 6 + utility.h | 12 ++ 10 files changed, 479 insertions(+), 3 deletions(-) create mode 100644 filecomparatorwindow.cpp create mode 100644 filecomparatorwindow.h create mode 100644 filecomparatorwindow.ui diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 30f73b1..891ce1f 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -28,7 +28,8 @@ SOURCES += main.cpp\ dbchandler.cpp \ dbcmaineditor.cpp \ dbcsignaleditor.cpp \ - framefileio.cpp + framefileio.cpp \ + filecomparatorwindow.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -49,7 +50,8 @@ HEADERS += mainwindow.h \ dbcmaineditor.h \ dbcsignaleditor.h \ framefileio.h \ - config.h + config.h \ + filecomparatorwindow.h FORMS += mainwindow.ui \ graphingwindow.ui \ @@ -60,7 +62,8 @@ FORMS += mainwindow.ui \ flowviewwindow.ui \ framesenderwindow.ui \ dbcmaineditor.ui \ - dbcsignaleditor.ui + dbcsignaleditor.ui \ + filecomparatorwindow.ui DISTFILES += diff --git a/filecomparatorwindow.cpp b/filecomparatorwindow.cpp new file mode 100644 index 0000000..22b286a --- /dev/null +++ b/filecomparatorwindow.cpp @@ -0,0 +1,259 @@ +#include "filecomparatorwindow.h" +#include "ui_filecomparatorwindow.h" + +FileComparatorWindow::FileComparatorWindow(QWidget *parent) : + QDialog(parent), + ui(new Ui::FileComparatorWindow) +{ + ui->setupUi(this); + + connect(ui->btnFirstFile, SIGNAL(clicked(bool)), this, SLOT(loadFirstFile())); + connect(ui->btnSecondFile, SIGNAL(clicked(bool)), this, SLOT(loadSecondFile())); + connect(ui->btnSaveDetails, SIGNAL(clicked(bool)), this, SLOT(saveDetails())); + + ui->lblFirstFile->setText(""); + ui->lblSecondFile->setText(""); +} + +FileComparatorWindow::~FileComparatorWindow() +{ + delete ui; +} + +void FileComparatorWindow::loadFirstFile() +{ + firstFileFrames.clear(); + QString result = FrameFileIO::loadFrameFile(&firstFileFrames); + if (result.length() > 0) + { + ui->lblFirstFile->setText(result); + firstFilename = result; + if (firstFileFrames.count() > 0 && secondFileFrames.count() > 0) calculateDetails(); + } +} + +void FileComparatorWindow::loadSecondFile() +{ + secondFileFrames.clear(); + QString result = FrameFileIO::loadFrameFile(&secondFileFrames); + if (result.length() > 0) + { + ui->lblSecondFile->setText(result); + secondFilename = result; + if (firstFileFrames.count() > 0 && secondFileFrames.count() > 0) calculateDetails(); + } +} + +void FileComparatorWindow::calculateDetails() +{ + QHash firstFileIDs; + QHash secondFileIDs; + uint64_t shiftBase = 1; //stupid hack to ensure that 64 bit shifting is used. + QTreeWidgetItem *firstOnlyBase, *secondOnlyBase, *sharedBase, *bitmapBaseFirst, *bitmapBaseSecond; + QTreeWidgetItem *valuesBase, *detail, *sharedItem, *valuesFirst, *valuesSecond; + + int idx = 0; + + ui->treeDetails->clear(); + + firstOnlyBase = new QTreeWidgetItem(); + firstOnlyBase->setText(0,"IDs found only in " + firstFilename); + secondOnlyBase = new QTreeWidgetItem(); + secondOnlyBase->setText(0, "IDs found only in " + secondFilename); + sharedBase = new QTreeWidgetItem(); + sharedBase->setText(0,"IDs found in both files"); + + //first we have to fill out the data structures to get ready to do the report + for (int x = 0; x < firstFileFrames.count(); x++) + { + CANFrame frame = firstFileFrames.at(x); + if (firstFileIDs.contains(frame.ID)) //if we saw this ID before then add to the QList in there + { + for (int y = 0; y < frame.len; y++) + { + firstFileIDs[frame.ID].values[y][frame.data[y]]++; + firstFileIDs[frame.ID].bitmap |= frame.data[y] << (8 * y); + } + } + else //never seen this ID before so add one + { + FrameData *newData = new FrameData(); + newData->ID = frame.ID; + newData->dataLen = frame.len; + //it would be possible to implement a constructor for FrameData + //that sets the bitmap and values to zero. That would be cleaner and better. + newData->bitmap = 0; + //C++ arrays take the form of a single memory block so zap the whole block at once + memset(newData->values, 0, 256 * 8); + for (int y = 0; y < frame.len; y++) + { + newData->values[y][frame.data[y]] = 1; + newData->bitmap |= frame.data[y] << (8 * y); + } + firstFileIDs.insert(frame.ID, *newData); + } + } + + for (int x = 0; x < secondFileFrames.count(); x++) + { + CANFrame frame = secondFileFrames.at(x); + if (secondFileIDs.contains(frame.ID)) //if we saw this ID before then add to the QList in there + { + for (int y = 0; y < frame.len; y++) + { + secondFileIDs[frame.ID].values[y][frame.data[y]]++; + secondFileIDs[frame.ID].bitmap |= frame.data[y] << (8 * y); + } + } + else //never seen this ID before so add one + { + FrameData *newData = new FrameData(); + newData->ID = frame.ID; + newData->dataLen = frame.len; + newData->bitmap = 0; + memset(newData->values, 0, 256 * 8); + for (int y = 0; y < frame.len; y++) + { + newData->values[y][frame.data[y]] = 1; + newData->bitmap |= frame.data[y] << (8 * y); + } + secondFileIDs.insert(frame.ID, *newData); + } + } + + //now we iterate through the IDs within both files and see which are unique to one file and which + //are shared + QHash::iterator i; + for (i = firstFileIDs.begin(); i != firstFileIDs.end(); ++i) + { + int keyone = i.key(); + if (!secondFileIDs.contains(keyone)) + { + valuesBase = new QTreeWidgetItem(); + valuesBase->setText(0, QString::number(keyone, 16)); + firstOnlyBase->addChild(valuesBase); + } + else //ID was in both files + { + sharedItem = new QTreeWidgetItem(); + sharedItem->setText(0, Utility::formatHexNum(keyone)); + sharedBase->addChild(sharedItem); + //if the ID was in both files then we can use the data accumulated above in bitmap + //and values to figure out what has changed between the two files + + FrameData first = firstFileIDs[keyone]; + FrameData second = secondFileIDs[keyone]; + + bitmapBaseFirst = new QTreeWidgetItem(); + bitmapBaseFirst->setText(0, "Bits set only in " + firstFilename); + bitmapBaseSecond = new QTreeWidgetItem(); + bitmapBaseSecond->setText(0, "Bits set only in " + secondFilename); + sharedItem->addChild(bitmapBaseFirst); + sharedItem->addChild(bitmapBaseSecond); + + //first up, which bits were set in one file but not the other + for (int b = 0; b < 64; b++) + { + detail = new QTreeWidgetItem(); + detail->setText(0, QString::number(b) + " (" + QString::number(b / 8) + ":" + QString::number(b % 8) + ")"); + if ( (first.bitmap & (shiftBase<addChild(detail); + } + if ( !(first.bitmap & (shiftBase<addChild(detail); + } + } + + for (int i = 0; i < qMax(first.dataLen, second.dataLen); i++) + { + valuesBase = new QTreeWidgetItem(); + valuesBase->setText(0, "Byte " + QString::number(i)); + sharedItem->addChild(valuesBase); + valuesFirst = new QTreeWidgetItem(); + valuesFirst->setText(0, "Values found only in " + firstFilename); + valuesSecond = new QTreeWidgetItem(); + valuesSecond->setText(0, "Values found only in " + secondFilename); + valuesBase->addChild(valuesFirst); + valuesBase->addChild(valuesSecond); + for (int j = 0; j < 256; j++) + { + detail = new QTreeWidgetItem(); + detail->setText(0, Utility::formatHexNum(j)); + if (first.values[i][j] > 0 && second.values[i][j] == 0) + { + valuesFirst->addChild(detail); + } + if (second.values[i][j] > 0 && first.values[i][j] == 0) + { + valuesSecond->addChild(detail); + } + } + } + } + } + + QHash::iterator itwo; + for (itwo = secondFileIDs.begin(); itwo != secondFileIDs.end(); ++itwo) + { + int keytwo = itwo.key(); + if (!firstFileIDs.contains(keytwo)) + { + valuesBase = new QTreeWidgetItem(); + valuesBase->setText(0, Utility::formatHexNum(keytwo)); + secondOnlyBase->addChild(valuesBase); + } + } + + ui->treeDetails->addTopLevelItem(firstOnlyBase); + ui->treeDetails->addTopLevelItem(secondOnlyBase); + ui->treeDetails->addTopLevelItem(sharedBase); + + ui->treeDetails->setSortingEnabled(true); + ui->treeDetails->sortByColumn(0, Qt::AscendingOrder); + +} + +void FileComparatorWindow::saveDetails() +{ + QString filename; + QFileDialog dialog(this); + + QStringList filters; + filters.append(QString(tr("Text File (*.txt)"))); + + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + dialog.setAcceptMode(QFileDialog::AcceptSave); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + QFile *outFile = new QFile(filename); + + if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + return; + + QTreeWidget *tree = ui->treeDetails; + + + QTreeWidgetItemIterator it(tree); + while (*it) { + QTreeWidgetItem *item = *it; + QString itemText = item->text(0); + while (item->parent()) + { + outFile->write(" "); + item = item->parent(); + } + outFile->write(itemText.toUtf8() + "\n"); + ++it; + } + + outFile->close(); + + } +} + diff --git a/filecomparatorwindow.h b/filecomparatorwindow.h new file mode 100644 index 0000000..c3c0e86 --- /dev/null +++ b/filecomparatorwindow.h @@ -0,0 +1,46 @@ +#ifndef FILECOMPARATORWINDOW_H +#define FILECOMPARATORWINDOW_H + +#include +#include +#include +#include "framefileio.h" +#include "can_structs.h" +#include "utility.h" + +namespace Ui { +class FileComparatorWindow; +} + +struct FrameData +{ + int ID; + int dataLen; + uint64_t bitmap; + int values[8][256]; //first index is the data byte, second is # of times we saw that value +}; + +class FileComparatorWindow : public QDialog +{ + Q_OBJECT + +public: + explicit FileComparatorWindow(QWidget *parent = 0); + ~FileComparatorWindow(); + +private slots: + void loadFirstFile(); + void loadSecondFile(); + void saveDetails(); + +private: + Ui::FileComparatorWindow *ui; + QVector firstFileFrames; + QVector secondFileFrames; + QString firstFilename; + QString secondFilename; + + void calculateDetails(); +}; + +#endif // FILECOMPARATORWINDOW_H diff --git a/filecomparatorwindow.ui b/filecomparatorwindow.ui new file mode 100644 index 0000000..72c10d7 --- /dev/null +++ b/filecomparatorwindow.ui @@ -0,0 +1,91 @@ + + + FileComparatorWindow + + + + 0 + 0 + 720 + 631 + + + + File Comparator + + + + + + + + + + First File: + + + + + + + TextLabel + + + + + + + Load New File + + + + + + + + + + + Second File: + + + + + + + TextLabel + + + + + + + Load New File + + + + + + + + + + + + 1 + + + + + + + + Save Details to File + + + + + + + + diff --git a/framefileio.cpp b/framefileio.cpp index 357e840..ce53d38 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -5,6 +5,43 @@ FrameFileIO::FrameFileIO() } +QString FrameFileIO::loadFrameFile(QVector* frameCache) +{ + QString filename; + QFileDialog dialog; + bool result = false; + + QStringList filters; + filters.append(QString(tr("CRTD Logs (*.txt)"))); + filters.append(QString(tr("GVRET Logs (*.csv)"))); + filters.append(QString(tr("Generic ID/Data CSV (*.csv)"))); + filters.append(QString(tr("BusMaster Log (*.log)"))); + filters.append(QString(tr("Microchip Log (*.log)"))); + + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + + if (dialog.selectedNameFilter() == filters[0]) result = loadCRTDFile(filename, frameCache); + if (dialog.selectedNameFilter() == filters[1]) result = loadNativeCSVFile(filename, frameCache); + if (dialog.selectedNameFilter() == filters[2]) result = loadGenericCSVFile(filename, frameCache); + if (dialog.selectedNameFilter() == filters[3]) result = loadLogFile(filename, frameCache); + if (dialog.selectedNameFilter() == filters[4]) result = loadMicrochipFile(filename, frameCache); + if (result) + { + QStringList fileList = filename.split('/'); + filename = fileList[fileList.length() - 1]; + return filename; + } + else return QString(""); + } + return QString(""); +} + //CRTD format from Mark Webb-Johnson / OVMS project /* Sample data in CRTD format diff --git a/framefileio.h b/framefileio.h index fa190dc..ef2b918 100644 --- a/framefileio.h +++ b/framefileio.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "can_structs.h" #include "utility.h" @@ -30,6 +31,7 @@ public: static bool saveGenericCSVFile(QString, QVector*); static bool saveLogFile(QString, QVector*); static bool saveMicrochipFile(QString, QVector*); + static QString loadFrameFile(QVector*); }; #endif // FRAMEFILEIO_H diff --git a/mainwindow.cpp b/mainwindow.cpp index b41c683..a03b963 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -101,6 +101,7 @@ MainWindow::MainWindow(QWidget *parent) : flowViewWindow = NULL; frameSenderWindow = NULL; dbcMainEditor = NULL; + comparatorWindow = NULL; dbcHandler = new DBCHandler; bDirty = false; @@ -128,6 +129,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->actionRange_State, SIGNAL(triggered(bool)), this, SLOT(showRangeWindow())); connect(ui->actionSave_Decoded_Frames, SIGNAL(triggered(bool)), this, SLOT(handleSaveDecoded())); connect(ui->actionSingle_Multi_State, SIGNAL(triggered(bool)), this, SLOT(showSingleMultiWindow())); + connect(ui->actionFile_Comparison, SIGNAL(triggered(bool)), this, SLOT(showComparisonWindow())); lbStatusConnected.setText(tr("Not connected")); updateFileStatus(); @@ -187,6 +189,12 @@ MainWindow::~MainWindow() delete frameSenderWindow; } + if (comparatorWindow) + { + comparatorWindow->close(); + delete comparatorWindow; + } + if (dbcMainEditor) { dbcMainEditor->close(); @@ -602,6 +610,15 @@ void MainWindow::showPlaybackWindow() playbackWindow->show(); } +void MainWindow::showComparisonWindow() +{ + if (!comparatorWindow) + { + comparatorWindow = new FileComparatorWindow(); + } + comparatorWindow->show(); +} + void MainWindow::showSingleMultiWindow() { //not done yet diff --git a/mainwindow.h b/mainwindow.h index ae36aac..d037b7e 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -13,6 +13,7 @@ #include "frameplaybackwindow.h" #include "flowviewwindow.h" #include "framesenderwindow.h" +#include "filecomparatorwindow.h" #include "dbchandler.h" #include "dbcmaineditor.h" @@ -45,6 +46,7 @@ private slots: void showSingleMultiWindow(); void showRangeWindow(); void showFuzzyScopeWindow(); + void showComparisonWindow(); void exitApp(); void handleSaveDecoded(); void changeBaudRates(); @@ -93,6 +95,7 @@ private: FlowViewWindow *flowViewWindow; FrameSenderWindow *frameSenderWindow; DBCMainEditor *dbcMainEditor; + FileComparatorWindow *comparatorWindow; //various private storage QLabel lbStatusConnected; diff --git a/mainwindow.ui b/mainwindow.ui index 27d532f..d0ccf66 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -252,6 +252,7 @@ + @@ -362,6 +363,11 @@ Exit Application + + + File Comparison + + diff --git a/utility.h b/utility.h index 07d9574..11e6dbe 100644 --- a/utility.h +++ b/utility.h @@ -44,6 +44,18 @@ public: QDateTime stamp = QDateTime::currentDateTime(); return (long)(((stamp.time().hour() * 3600) + (stamp.time().minute() * 60) + (stamp.time().second()) * 1000) + stamp.time().msec()); } + + //prints hex numbers in uppercase with 0's filling out the number depending + //on the size needed. Promotes hex numbers to either 2, 4, or 8 digits + static QString formatHexNum(int input) + { + if (input < 256) + return QString::number(input, 16).toUpper().rightJustified(2,'0'); + if (input < 65536) + return QString::number(input, 16).toUpper().rightJustified(4,'0'); + + return QString::number(input, 16).toUpper().rightJustified(8,'0'); + } }; #endif // UTILITY_H