Merge branch 'master' of github.com:qurrent-llc/SavvyCAN

This commit is contained in:
Andy Huska
2023-05-11 16:51:42 -05:00
83 changed files with 2307 additions and 1281 deletions
+8 -8
View File
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Prepare Environment
run: |
sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-randr0
sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-randr0 libxkbcommon-x11-0
- name: Prepare Qt Libraries
uses: jurplel/install-qt-action@v3
@@ -29,14 +29,14 @@ jobs:
- name: Package
run: |
make INSTALL_ROOT=appdir install
wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/5/linuxdeployqt-5-x86_64.AppImage"
chmod a+x linuxdeployqt-5-x86_64.AppImage
./linuxdeployqt-5-x86_64.AppImage appdir/usr/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus
wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
chmod a+x linuxdeployqt-continuous-x86_64.AppImage
./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus
- uses: actions/upload-artifact@v3
with:
name: SavvyCAN-Linux_x64.AppImage
path: SavvyCAN-x86_64.AppImage
path: SavvyCAN-*x86_64.AppImage
buildmacos:
name: macOS x64
@@ -129,7 +129,7 @@ jobs:
- uses: actions/download-artifact@v3
- name: Display structure of downloaded files
run: zip -r SavvyCAN_CIBuild.zip SavvyCAN-Windows_x64
run: zip -r SavvyCAN-Windows_x64_CIBuild.zip SavvyCAN-Windows_x64
- uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
@@ -137,8 +137,8 @@ jobs:
prerelease: true
title: "Development Build"
files: |
SavvyCAN-Linux_x64.AppImage/SavvyCAN-x86_64.AppImage
SavvyCAN_CIBuild.zip
SavvyCAN-Linux_x64.AppImage/SavvyCAN-*x86_64.AppImage
SavvyCAN-Windows_x64_CIBuild.zip
SavvyCAN-macOS_x64.dmg/SavvyCAN.dmg
notify:
+3
View File
@@ -42,3 +42,6 @@ Makefile
# Editor files
*.code-workspace
SavvyCAN.pro.qtds
.xcode
SavvyCAN.xcodeproj
+6 -1
View File
@@ -20,6 +20,8 @@ QMAKE_INFO_PLIST = Info.plist.template
ICON = icons/SavvyIcon.icns
SOURCES += main.cpp\
canbridgewindow.cpp \
connections/canserver.cpp \
connections/lawicel_serial.cpp \
connections/mqtt_bus.cpp \
dbc/dbcnodeduplicateeditor.cpp \
@@ -100,7 +102,9 @@ SOURCES += main.cpp\
HEADERS += mainwindow.h \
can_structs.h \
canbridgewindow.h \
canframemodel.h \
connections/canserver.h \
connections/lawicel_serial.h \
connections/socketcand.h \
connections/mqtt_bus.h \
@@ -189,6 +193,7 @@ HEADERS += mainwindow.h \
pcaplite.h
FORMS += ui/candatagrid.ui \
ui/canbridgewindow.ui \
ui/dbcnodeduplicateeditor.ui \
ui/dbccomparatorwindow.ui \
ui/dbcmessageeditor.ui \
@@ -218,7 +223,7 @@ FORMS += ui/candatagrid.ui \
ui/udsscanwindow.ui \
ui/bisectwindow.ui \
ui/signalviewerwindow.ui \
helpwindow.ui \
ui/helpwindow.ui \
ui/newconnectiondialog.ui \
ui/temporalgraphwindow.ui
+1 -1
View File
@@ -249,7 +249,7 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
messageBuffer.insert(msg.frameId(), msg);
//The sending ID is set to the last ID we used to send from this class which is
//very likely to be correct. But, caution, there is a chance that it isn't. Beware.
if (issueFlowMsgs && lastSenderID > 0)
if (issueFlowMsgs && lastSenderID > 0 && lastSenderBus==static_cast<uint32_t>(frame.bus))
{
CANFrame outFrame;
outFrame.bus = lastSenderBus;
+143
View File
@@ -0,0 +1,143 @@
#include "canbridgewindow.h"
#include "helpwindow.h"
#include "qevent.h"
#include "ui_canbridgewindow.h"
#include "filterutility.h"
#include "mainwindow.h"
CANBridgeWindow::CANBridgeWindow(const QVector<CANFrame> *frames, QWidget *parent) :
QDialog(parent),
ui(new Ui::CANBridgeWindow)
{
ui->setupUi(this);
modelFrames = frames;
side1BusNum = 0;
side2BusNum = 0;
connect(ui->cbSide1, &QComboBox::currentTextChanged, this, &CANBridgeWindow::recalcSides);
connect(ui->cbSide2, &QComboBox::currentTextChanged, this, &CANBridgeWindow::recalcSides);
connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &CANBridgeWindow::updatedFrames);
connect(ui->listSide1, &QListWidget::itemChanged,
[this] (QListWidgetItem *item)
{
bool checked = item->checkState() == Qt::Checked ? true:false;
int IDval = FilterUtility::getIdAsInt(item);
qDebug() << "ID " << IDval << " set to " << checked;
foundIDSide1[IDval] = checked;
});
connect(ui->listSide2, &QListWidget::itemChanged,
[this] (QListWidgetItem *item)
{
bool checked = item->checkState() == Qt::Checked ? true:false;
int IDval = FilterUtility::getIdAsInt(item);
foundIDSide2[IDval] = checked;
});
}
CANBridgeWindow::~CANBridgeWindow()
{
delete ui;
}
void CANBridgeWindow::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
//stuff to do every time this form is shown.
ui->cbSide1->clear();
ui->cbSide2->clear();
int numBuses = CANConManager::getInstance()->getNumBuses();
for (int i = 0; i < numBuses; i++)
{
ui->cbSide1->addItem(QString::number(i));
ui->cbSide2->addItem(QString::number(i));
}
}
bool CANBridgeWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyRelease) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch (keyEvent->key())
{
case Qt::Key_F1:
HelpWindow::getRef()->showHelp("canbridge.md");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
void CANBridgeWindow::recalcSides()
{
side1BusNum = ui->cbSide1->currentText().toInt();
side2BusNum = ui->cbSide2->currentText().toInt();
}
void CANBridgeWindow::updatedFrames(int numFrames)
{
bool addedSide1 = false;
bool addedSide2 = false;
if (numFrames == -1) //all frames deleted. Kill the display
{
}
else if (numFrames == -2) //all new set of frames. Reset
{
}
else //just got some new frames. See if they are relevant.
{
if (numFrames > modelFrames->count()) return;
for (int x = modelFrames->count() - numFrames; x < modelFrames->count(); x++)
{
CANFrame thisFrame = modelFrames->at(x);
int32_t id = static_cast<int32_t>(thisFrame.frameId());
if (thisFrame.bus == side1BusNum)
{
if (!foundIDSide1.contains(id))
{
foundIDSide1.insert(id, true);
FilterUtility::createCheckableFilterItem(id, true, ui->listSide1);
addedSide1 = true;
}
if (ui->ckEnableSide1->isChecked()) //if we're enabled to forward traffic on this bus to the other one
{
if (foundIDSide1[id]) //and the checkbox for this particular ID is checked
{
thisFrame.bus = side2BusNum;
CANConManager::getInstance()->sendFrame(thisFrame);
}
}
}
else if (thisFrame.bus == side2BusNum)
{
if (!foundIDSide2.contains(id))
{
foundIDSide2.insert(id, true);
FilterUtility::createCheckableFilterItem(id, true, ui->listSide2);
addedSide2 = true;
}
if (ui->ckEnableSide2->isChecked()) //if we're enabled to forward traffic on this bus to the other one
{
if (foundIDSide2[id]) //and the checkbox for this particular ID is checked
{
thisFrame.bus = side1BusNum;
CANConManager::getInstance()->sendFrame(thisFrame);
}
}
}
}
//default is to sort in ascending order
if (addedSide1) ui->listSide1->sortItems();
if (addedSide2) ui->listSide2->sortItems();
}
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef CANBRIDGEWINDOW_H
#define CANBRIDGEWINDOW_H
#include <QDialog>
#include "connections/canconmanager.h"
namespace Ui {
class CANBridgeWindow;
}
class CANBridgeWindow : public QDialog
{
Q_OBJECT
public:
explicit CANBridgeWindow(const QVector<CANFrame> *frames, QWidget *parent = nullptr);
~CANBridgeWindow();
void showEvent(QShowEvent*);
private slots:
void updatedFrames(int);
void recalcSides();
private:
Ui::CANBridgeWindow *ui;
const QVector<CANFrame> *modelFrames;
QMap<int, bool> foundIDSide1;
QMap<int, bool> foundIDSide2;
int side1BusNum;
int side2BusNum;
void processIncomingFrame(CANFrame *frame);
bool eventFilter(QObject *obj, QEvent *event);
};
#endif // CANBRIDGEWINDOW_H
+159 -44
View File
@@ -6,6 +6,57 @@
#include <QMouseEvent>
#include <QRandomGenerator>
//The program used to generate new colors every time the grid was displayed but that's ugly
//and disorienting. Instead use this list colors I've picked because they're "bright".
//It may need a bit of reorganizing to keep like colors apart and feel free to add some more
//if you want. The program will wrap around if the number of signals in a single message exceeds
//this amount of colors.
//Colors here were generated with: https://colordesigner.io/random-color-generator set to random
//hue bright colors and 40 total colors. I clicked until I kind of liked it.
QVector<QColor> signalColors =
{
QColor(101, 104, 159),
QColor(237, 35, 109),
QColor(216, 53, 221),
QColor(37, 252, 220),
QColor(8, 160, 53),
QColor(226, 111, 11),
QColor(216, 59, 2),
QColor(249, 246, 27),
QColor(214, 21, 34),
QColor(178, 219, 74),
QColor(214, 55, 71),
QColor(80, 198, 206),
QColor(83, 214, 201),
QColor(35, 210, 237),
QColor(25, 170, 105),
QColor(110, 221, 55),
QColor(30, 38, 181),
QColor(79, 124, 209),
QColor(7, 91, 175),
QColor(14, 234, 208),
QColor(125, 45, 168),
QColor(176, 211, 80),
QColor(255, 206, 48),
QColor(202, 226, 93),
QColor(201, 99, 232),
QColor(13, 72, 135),
QColor(32, 45, 219),
QColor(197, 26, 216),
QColor(119, 15, 216),
QColor(97, 18, 150),
QColor(242, 16, 242),
QColor(177, 5, 196),
QColor(214, 10, 102),
QColor(9, 50, 155),
QColor(201, 38, 76),
QColor(202, 234, 72),
QColor(59, 191, 57),
QColor(0, 25, 170),
QColor(7, 186, 93),
QColor(29, 211, 147)
};
CANDataGrid::CANDataGrid(QWidget *parent) :
QWidget(parent),
ui(new Ui::CANDataGrid)
@@ -21,7 +72,10 @@ CANDataGrid::CANDataGrid(QWidget *parent) :
bytesToDraw = 8; //default to the old behavior
for (int x = 0; x < 8; x++)
for (int y = 0; y < 64; y++)
{
textStates[y][x] = GridTextState::NORMAL;
heatData[y * 8 + x] = 0;
}
blackBrush = QBrush(Qt::black);
whiteBrush = QBrush(Qt::white);
@@ -32,6 +86,27 @@ CANDataGrid::CANDataGrid(QWidget *parent) :
grayBrush = QBrush(QColor(230,230,230));
xOffset = 0;
yOffset = 0;
//generate the palette
/* //this one looks sort of like a fire. It was my first idea but never really got to where I liked it
for(int x = 1; x < 256; x++)
{
int lum = x * 2;
if (lum < 40) lum = 40;
if (lum > 250) lum = 250;
fire[x] = QColor::fromHsl((int)(x / 3.5), 255 - ((256 - x) / 10), lum);
}
*/
for(int x = 1; x < 256; x++)
{
int hue = 256 - x;
fire[x] = QColor::fromHsl(hue, 255, 127);
}
//the 0th entry should always be black and really stand out from the rest
fire[0] = Qt::black;
}
CANDataGrid::~CANDataGrid()
@@ -85,6 +160,21 @@ void CANDataGrid::mousePressEvent(QMouseEvent *event)
else if (bytesToDraw > 32) bytesToDraw = 8;
this->update();
}
//right click is exactly like left click but the emitted signal is different so that
//external code can differentiate which button was pressed
if (event->button() == Qt::RightButton)
{
clickedPoint -= upperLeft;
if (clickedPoint.x() < 0 || clickedPoint.y() < 0)
{
return;
}
int x = clickedPoint.x() / gridSize.x();
int y = clickedPoint.y() / gridSize.y();
int bitClicked = gridToBitPosition(x, y);
emit gridRightClicked(bitClicked);
}
}
void CANDataGrid::setCellTextState(int bitPos, GridTextState state)
@@ -117,7 +207,6 @@ void CANDataGrid::clearSignalNames()
{
signalNames.clear();
signalNames.resize(40);
signalColors.clear();
}
void CANDataGrid::setUsedSignalNum(int bit, int signal)
@@ -176,14 +265,14 @@ void CANDataGrid::paintCommonBeginning()
neededXDivisions = 32;
}
if (gridMode == GridMode::CHANGED_BITS)
if (gridMode != GridMode::SIGNAL_VIEW)
{
bigTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 3.25);
smallTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 4.0);
}
if (gridMode == GridMode::SIGNAL_VIEW)
else
{
bigTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 4.50);
bigTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 3.50);
smallTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 5.5);
}
sigNameTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 5.0);
@@ -197,6 +286,7 @@ void CANDataGrid::paintCommonBeginning()
sigNameFont.setPixelSize(sigNameTextSize);
smallMetric = new QFontMetrics(sigNameFont);
largeMetric = new QFontMetrics(mainFont);
xOffset = smallMetric->maxWidth();
yOffset = smallMetric->height();
@@ -257,21 +347,6 @@ void CANDataGrid::paintGridCells()
int usedSigNum;
QString prevSigName;
//if this is true then generate unique colors for each signal
if ((signalColors.count() == 0) && (signalNames.count() > 0) && gridMode == GridMode::SIGNAL_VIEW)
{
qDebug() << "Generating colors";
signalColors.resize(signalNames.count());
for (int i = 0; i < signalNames.count(); i++)
{
QColor newColor;
while (newColor.saturation() < 40)
newColor.setRgb(QRandomGenerator::global()->bounded(160) + 60,QRandomGenerator::global()->bounded(160) + 60, QRandomGenerator::global()->bounded(160) + 60);
qDebug() << newColor;
signalColors[i] = newColor;
}
}
//now, color the bitfield by seeing if a given bit is freshly set/unset in the new data
//compared to the old. Bits that are not set in either are white, bits set in both are black
//bits that used to be set but now are unset are red, bits that used to be unset but now are set
@@ -291,41 +366,53 @@ void CANDataGrid::paintGridCells()
if ((thisByte & (1 << bitIdx)) == (1 << bitIdx)) thisBit = true;
if ((prevByte & (1 << bitIdx)) == (1 << bitIdx)) prevBit = true;
if (thisBit)
if (gridMode == GridMode::HEAT_VIEW)
{
if (prevBit)
{
if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(blackHashBrush);
else painter->setBrush(blackBrush);
}
else
{
if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(greenHashBrush);
else painter->setBrush(greenBrush);
}
painter->setBrush(QBrush(fire[heatData[bit]]));
}
else
{
if (prevBit)
if (thisBit)
{
painter->setBrush(redBrush);
if (prevBit)
{
if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(blackHashBrush);
else painter->setBrush(blackBrush);
}
else
{
if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(greenHashBrush);
else painter->setBrush(greenBrush);
}
}
else
{
usedSigNum = -1;
if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx))
{
if (prevBit)
{
if (gridMode == GridMode::SIGNAL_VIEW) usedSigNum = getUsedSignalNum(bit);
if (usedSigNum == -1)
painter->setBrush(redBrush);
}
else
{
usedSigNum = -1;
if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx))
{
grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern);
painter->setBrush(grayBrush);
if (gridMode == GridMode::SIGNAL_VIEW) usedSigNum = getUsedSignalNum(bit);
if (usedSigNum == -1)
{
grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern);
painter->setBrush(grayBrush);
}
else
{
int idx = usedSigNum % signalColors.length(); //can only use as many colors as have been defined
painter->setBrush(QBrush(signalColors[idx]));
}
}
else painter->setBrush(QBrush(signalColors[usedSigNum]));
else painter->setBrush(whiteBrush);
}
else painter->setBrush(whiteBrush);
}
}
painter->drawRect(nearX + (x * xSector), nearY + (y * ySector), xSector, ySector);
switch (textStates[byteIdx][bitIdx])
{
@@ -364,7 +451,7 @@ void CANDataGrid::paintGridCells()
* We already have a big bitmap that tells us which signals occupy which bits so every time there is a new
* signal look ahead to see if there's room in the row to just run the signal name through as long as needed.
*/
if (signalNames.count() > 0)
if ( (signalNames.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW) )
{
painter->setFont(sigNameFont);
@@ -379,7 +466,7 @@ void CANDataGrid::paintGridCells()
if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx))
{
usedSigNum = getUsedSignalNum(bit);
if (prevSigName != signalNames[usedSigNum])
if ((usedSigNum > -1) && (prevSigName != signalNames[usedSigNum]) )
{
prevSigName = signalNames[usedSigNum];
@@ -402,7 +489,23 @@ void CANDataGrid::paintGridCells()
else painter->drawText(nearX + x * xSector + 12, nearY + (y * ySector) + smallMetric->height() * 2.6, remainder);
}
else painter->drawText(nearX + x * xSector + 5, nearY + (y * ySector) + smallMetric->height() * 1.6, prevSigName);
else
{
//first see if we even have enough room to use a bigger font and use that if so. Otherwise stick with the normal font
textWidth = largeMetric->horizontalAdvance(prevSigName);
if (textWidth < usableWidth)
{
painter->setFont(mainFont);
QSize size = QSize(usableWidth, ySector - smallMetric->height() * 1.0);
painter->drawText(QRect(nearX + x * xSector, nearY + (y * ySector) + smallMetric->height() * 1.0, size.width(), size.height()), Qt::AlignCenter, prevSigName);
painter->setFont(sigNameFont);
}
else
{
QSize size = QSize(usableWidth, ySector - smallMetric->height() * 1.0);
painter->drawText(QRect(nearX + x * xSector, nearY + (y * ySector) + smallMetric->height() * 1.0, size.width(), size.height()), Qt::AlignCenter, prevSigName);
}
}
}
}
}
@@ -497,6 +600,8 @@ void CANDataGrid::setReference(unsigned char *newRef, bool bUpdate = true)
{
int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments
memcpy(refData, newRef, bytesToTransfer);
//clear all data past that point just to be sure we don't have garbage left over
if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer);
if (bUpdate) this->update();
}
@@ -504,6 +609,8 @@ void CANDataGrid::updateData(unsigned char *newData, bool bUpdate = true)
{
int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments
memcpy(data, newData, bytesToTransfer);
//clear all data past that point just to be sure we don't have garbage left over
if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer);
if (bUpdate) this->update();
}
@@ -511,5 +618,13 @@ void CANDataGrid::setUsed(unsigned char *newData, bool bUpdate = false)
{
int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments
memcpy(usedData, newData, bytesToTransfer);
//clear all data past that point just to be sure we don't have garbage left over
if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer);
if (bUpdate) this->update();
}
void CANDataGrid::setHeat(unsigned char *newData)
{
memcpy(heatData, newData, 512);
this->update();
}
+9 -2
View File
@@ -36,9 +36,12 @@ enum GridTextState
enum GridMode
{
CHANGED_BITS, //traditional view, bunch of bits we color to show what's set and how the bits have changed over time
SIGNAL_VIEW //special view for DBC window where we draw the signals in the bits they take up
SIGNAL_VIEW, //special view for DBC window where we draw the signals in the bits they take up
HEAT_VIEW //another special mode where each bit is given a heat level and we color each bit based on that. Use refData for storage of those values
};
extern QVector<QColor> signalColors;
class CANDataGrid : public QWidget
{
Q_OBJECT
@@ -50,6 +53,7 @@ public:
void setReference(unsigned char *, bool);
void updateData(unsigned char *, bool);
void setUsed(unsigned char *, bool);
void setHeat(unsigned char *);
void saveImage(QString filename, int width, int height);
void setCellTextState(int bitPos, GridTextState state);
GridTextState getCellTextState(int bitPos);
@@ -66,6 +70,7 @@ protected:
signals:
void gridClicked(int bitClicked);
void gridRightClicked(int bitClicked);
private:
Ui::CANDataGrid *ui;
@@ -73,9 +78,9 @@ private:
unsigned char refData[64];
unsigned char data[64];
unsigned char usedData[64];
unsigned char heatData[512];
int usedSignalNum[512]; //so we can specify which signal claims this bit
QVector<QString> signalNames;
QVector<QColor> signalColors;
GridTextState textStates[64][8]; //first dimension is bytes, second is bits
QPoint upperLeft, gridSize;
GridMode gridMode;
@@ -98,6 +103,8 @@ private:
QFont boldFont;
QFont sigNameFont;
QFontMetrics *smallMetric;
QFontMetrics *largeMetric;
QColor fire[256];
void paintGridCells();
void paintCommonBeginning();
+12 -25
View File
@@ -61,8 +61,14 @@ CANFrameModel::CANFrameModel(QObject *parent)
QSettings settings;
preallocSize = settings.value("Main/MaximumFrames", maxFramesDefault).toInt();
//Each CANFrame object takes up 56 bytes and we're allocating two arrays here so take the
//# of pre-alloc frames and multiply by 112 to get the RAM usage. This is around 1GiB for the default.
//the goal is to prevent a reallocation from ever happening
frames.reserve(preallocSize);
//this is pretty wasteful. We're storing all frames twice. It may be better for filteredFrames to be a list of pointers.
//Pointers take up 8 bytes instead of 56 so this is quite a savings for RAM usage. But, then filteredFrames would
//work differently from frames above so the two could not be used interchangeably. Still think about what can be done.
filteredFrames.reserve(preallocSize);
dbcHandler = DBCHandler::getReference();
@@ -70,7 +76,7 @@ CANFrameModel::CANFrameModel(QObject *parent)
overwriteDups = false;
filtersPersistDuringClear = false;
useHexMode = true;
timeSeconds = false;
timeStyle = TS_MICROS;
timeOffset = 0;
needFilterRefresh = false;
lastUpdateNumFrames = 0;
@@ -95,32 +101,13 @@ void CANFrameModel::setHexMode(bool mode)
}
}
void CANFrameModel::setSecondsMode(bool mode)
void CANFrameModel::setTimeStyle(TimeStyle newStyle)
{
if (Utility::secondsMode != mode)
if (timeStyle != newStyle)
{
this->beginResetModel();
Utility::secondsMode = mode;
this->endResetModel();
}
}
void CANFrameModel::setSysTimeMode(bool mode)
{
if (Utility::sysTimeMode != mode)
{
this->beginResetModel();
Utility::sysTimeMode = mode;
this->endResetModel();
}
}
void CANFrameModel::setMillisMode(bool mode)
{
if (Utility::millisMode != mode)
{
this->beginResetModel();
Utility::millisMode = mode;
timeStyle = newStyle;
Utility::timeStyle = newStyle;
this->endResetModel();
}
}
@@ -481,7 +468,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
//Reformatting the output a bit with custom code
if (overwriteDups)
{
if (timeSeconds) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5);
if (timeStyle == TS_SECONDS) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5);
return QString::number(thisFrame.timedelta);
}
else ts = Utility::formatTimestamp(thisFrame.timeStamp().microSeconds());
+3 -5
View File
@@ -9,6 +9,7 @@
#include "can_structs.h"
#include "dbc/dbchandler.h"
#include "connections/canconnection.h"
#include "utility.h"
enum class Column {
TimeStamp = 0, ///< The timestamp when the frame was transmitted or received
@@ -47,13 +48,11 @@ public:
void setOverwriteMode(bool);
void setHexMode(bool);
void setClearMode(bool mode);
void setSysTimeMode(bool);
void setMillisMode(bool mode);
void setTimeStyle(TimeStyle newStyle);
void setIgnoreDBCColors(bool mode);
void setFilterState(unsigned int ID, bool state);
void setBusFilterState(unsigned int BusID, bool state);
void setAllFilters(bool state);
void setSecondsMode(bool);
void setTimeFormat(QString);
void setBytesPerLine(int bpl);
void loadFilterFile(QString filename);
@@ -93,9 +92,8 @@ private:
bool overwriteDups; //should we display all frames or only the newest for each ID?
bool filtersPersistDuringClear;
QString timeFormat;
TimeStyle timeStyle;
bool useHexMode;
bool timeSeconds;
bool useSystemTime;
bool needFilterRefresh;
bool ignoreDBCColors;
int64_t timeOffset;
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef CONFIG
#define CONFIG
#define VERSION 208
#define VERSION 213
//try to keep this in sync.
//SavvyCAN will complain if you connect a GVRET board with a revision
+1 -1
View File
@@ -4,7 +4,7 @@
CANBus::CANBus()
{
speed = 250000;
speed = 500000;
listenOnly = false;
singleWire = false;
active = false;
+1
View File
@@ -21,6 +21,7 @@ namespace CANCon {
KAYAK,
MQTT,
LAWICEL,
CANSERVER,
NONE
};
}
+4 -1
View File
@@ -5,6 +5,7 @@
#include "mqtt_bus.h"
#include "socketcand.h"
#include "lawicel_serial.h"
#include "canserver.h"
using namespace CANCon;
@@ -14,7 +15,7 @@ CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDri
case SERIALBUS:
return new SerialBusConnection(pPortName, pDriverName);
case GVRET_SERIAL:
if(pPortName.contains("."))
if(pPortName.contains(".") && !pPortName.contains("tty"))
return new GVRetSerial(pPortName, true);
else
return new GVRetSerial(pPortName, false);
@@ -26,6 +27,8 @@ CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDri
return new SocketCANd(pPortName);
case MQTT:
return new MQTT_BUS(pPortName);
case CANSERVER:
return new CANserver(pPortName);
default: {}
}
+2
View File
@@ -69,7 +69,9 @@ void CANConManager::remove(CANConnection* pConn_p)
void CANConManager::replace(int idx, CANConnection* pConn_p)
{
CANConnection *original = mConns[idx];
mConns.replace(idx, pConn_p);
delete original; original = NULL;
}
//Get total number of buses currently registered with the program
+10 -2
View File
@@ -175,7 +175,10 @@ bool CANConnection::sendFrame(const CANFrame& pFrame)
CANFrame *txFrame;
txFrame = getQueue().get();
*txFrame = pFrame;
if (txFrame)
{
*txFrame = pFrame;
}
getQueue().queue();
return piSendFrame(pFrame);
@@ -298,7 +301,12 @@ bool CANConnection::addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QO
target.id = ID;
target.mask = mask;
target.observer = receiver;
mBusData[pBusId].mTargettedFrames.append(target);
if (pBusId > -1)
mBusData[pBusId].mTargettedFrames.append(target);
else
{
for (int i = 0; i < mBusData.count(); i++) mBusData[i].mTargettedFrames.append(target);
}
return true;
}
+1
View File
@@ -87,6 +87,7 @@ QVariant CANConnectionModel::data(const QModelIndex &index, int role) const
case CANCon::GVRET_SERIAL: return "GVRET";
case CANCon::KAYAK: return "socketcand";
case CANCon::LAWICEL: return "LAWICEL";
case CANCon::CANSERVER: return "CANserver";
default: {}
}
else qDebug() << "Tried to show connection type but connection was nullptr";
+238
View File
@@ -0,0 +1,238 @@
//
// canserver.cpp
// SavvyCAN
//
// Created by Chris Whiteford on 2022-01-21.
//
#include <QObject>
#include <QDebug>
#include <QCanBusFrame>
#include <QSettings>
#include <QStringBuilder>
#include <QtNetwork>
#include "utility.h"
#include "canserver.h"
CANserver::CANserver(QString serverAddressString) :
CANConnection(serverAddressString, "CANserver", CANCon::CANSERVER, 0, 0, 3, 4000, true),
_udpClient(new QUdpSocket(this)),
_heartbeatTimer(new QTimer(this))
{
qDebug() << "CANserver: " << "Constructing new Connection...";
CANBus bus_info;
bus_info.setActive(true);
bus_info.setListenOnly(true);
bus_info.setSpeed(500000);
setBusConfig(0, bus_info);
setBusConfig(1, bus_info);
setBusConfig(2, bus_info);
// setup udp client
this->_canserverAddress = QHostAddress(serverAddressString);
connect(_udpClient, SIGNAL(readyRead()), this, SLOT(readNetworkData()));
// setup signal and slot
connect(_heartbeatTimer, SIGNAL(timeout()), this, SLOT(heartbeatTimerSlot()));
_heartbeatTimer->stop();
}
CANserver::~CANserver()
{
qDebug() << "CANserver: " << "Deconstructing Connection...";
stop();
delete _heartbeatTimer; _heartbeatTimer = NULL;
delete _udpClient; _udpClient = NULL;
}
void CANserver::piStarted()
{
this->connectToDevice();
}
void CANserver::piSuspend(bool pSuspend)
{
/* update capSuspended */
setCapSuspended(pSuspend);
/* flush queue if we are suspended */
if(isCapSuspended())
getQueue().flush();
}
void CANserver::piStop()
{
this->disconnectFromDevice();
}
bool CANserver::piGetBusSettings(int pBusIdx, CANBus& pBus)
{
return getBusConfig(pBusIdx, pBus);
}
void CANserver::piSetBusSettings(int pBusIdx, CANBus bus)
{
/* sanity checks */
if( (pBusIdx < 0) || pBusIdx >= getNumBuses())
return;
/* copy bus config */
setBusConfig(pBusIdx, bus);
}
bool CANserver::piSendFrame(const CANFrame& )
{
//We don't support sending frames right now
return true;
}
void CANserver::connectToDevice()
{
qDebug() << "CANserver: " << "Establishing UDP connection to a CANserver device...";
bool bindResult = _udpClient->bind(1338);
qDebug() << "CANserver: " << "UDP Bind result: " << bindResult;
heartbeat();
qDebug() << "CANserver: " << "Scheduling additional heartbeats...";
_heartbeatTimer->start(3500);
setStatus(CANCon::CONNECTED);
CANConStatus stats;
stats.conStatus = getStatus();
stats.numHardwareBuses = 3;
emit status(stats);
}
void CANserver::disconnectFromDevice()
{
_heartbeatTimer->stop();
QByteArray Data;
Data.append("bye");
QNetworkDatagram datagram(Data, _canserverAddress, 1338);
qDebug() << "CANserver: " << "Sending bye...";
_udpClient->writeDatagram(datagram);
_udpClient->close();
}
void CANserver::heartbeat()
{
//Send the PANDA hello packet (in this case we are using the v1 version of the protocol because we just want to get ALL the data)
QByteArray Data;
Data.append("hello");
QNetworkDatagram datagram(Data, _canserverAddress, 1338);
qDebug() << "CANserver: " << "Sending a heartbeat...";
_udpClient->writeDatagram(datagram);
}
void CANserver::readNetworkData()
{
QByteArray datagram;
do {
datagram.resize(_udpClient->pendingDatagramSize());
_udpClient->readDatagram(datagram.data(), datagram.size());
} while (_udpClient->hasPendingDatagrams());
// If capture is suspended bail out after reading the bytes from the network. No need to parse them
if(isCapSuspended())
return;
uint16_t packetCount = datagram.length() / 16;
//qDebug() << "Processing " << packetCount << " packets";
//Process this chunk of data
for (int i = 0 ; i < packetCount; i++)
{
uint16_t headerByteLocation = (i*16);
uint16_t dataByteLocation = (i*16) + 8;
uint32_t headerData1Byte1 = datagram.at(headerByteLocation + 0) & 0xFF;
uint32_t headerData1Byte2 = datagram.at(headerByteLocation + 1) & 0xFF;
uint32_t headerData1Byte3 = datagram.at(headerByteLocation + 2) & 0xFF;
uint32_t headerData1Byte4 = datagram.at(headerByteLocation + 3) & 0xFF;
uint32_t headerData2Byte1 = datagram.at(headerByteLocation + 4) & 0xFF;
uint32_t headerData2Byte2 = datagram.at(headerByteLocation + 5) & 0xFF;
uint32_t headerData2Byte3 = datagram.at(headerByteLocation + 6) & 0xFF;
uint32_t headerData2Byte4 = datagram.at(headerByteLocation + 7) & 0xFF;
//printf("HB1: %02X, %02X, %02X, %02X\n", headerData1Byte1, headerData1Byte2, headerData1Byte3, headerData1Byte4);
//printf("HB2: %02X, %02X, %02X, %02X\n", headerData2Byte1, headerData2Byte2, headerData2Byte3, headerData2Byte4);
uint32_t headerData1 = headerData1Byte1;
headerData1 += headerData1Byte2 << 8;
headerData1 += headerData1Byte3 << 16;
headerData1 += headerData1Byte4 << 24;
uint32_t headerData2 = headerData2Byte1;
headerData2 += headerData2Byte2 << 8;
headerData2 += headerData2Byte3 << 16;
headerData2 += headerData2Byte4 << 24;
uint32_t frameId = headerData1 >> 21;
uint32_t length = (headerData2 & 0x0F);
uint32_t busId = (headerData2 >> 4);
//printf("frameId: %02X, busId: %d, length: %d\n", frameId, busId, length);
CANFrame* frame_p = getQueue().get();
if(frame_p)
{
frame_p->setFrameId(frameId);
frame_p->setExtendedFrameFormat(0);
// We need to change the bus id if it is the special CANserver bus id.
// This keeps us from needing to define 15 busses just to get access to our special one
if (busId == 15)
{
busId = 2;
}
frame_p->bus = busId;
frame_p->setFrameType(QCanBusFrame::DataFrame);
frame_p->isReceived = true;
frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, QDateTime::currentMSecsSinceEpoch() * 1000ul));
frame_p->setPayload(datagram.mid(dataByteLocation, length));
checkTargettedFrame(*frame_p);
/* enqueue frame */
getQueue().queue();
}
}
}
void CANserver::heartbeatTimerSlot()
{
heartbeat();
}
void CANserver::readSettings()
{
QSettings settings;
}
+64
View File
@@ -0,0 +1,64 @@
//
// canserver.h
// SavvyCAN
//
// Created by Chris Whiteford on 2022-01-21.
//
#ifndef canserver_h
#define canserver_h
#include <stdio.h>
#include <QCanBusDevice>
#include <QThread>
#include <QTimer>
#include <QUdpSocket>
/*************/
#include <QDateTime>
/*************/
#include "canframemodel.h"
#include "canconnection.h"
#include "canconmanager.h"
class CANserver : public CANConnection
{
Q_OBJECT
public:
CANserver(QString serverAddress);
virtual ~CANserver();
protected:
virtual void piStarted();
virtual void piStop();
virtual void piSetBusSettings(int pBusIdx, CANBus pBus);
virtual bool piGetBusSettings(int pBusIdx, CANBus& pBus);
virtual void piSuspend(bool pSuspend);
virtual bool piSendFrame(const CANFrame&);
private slots:
void readNetworkData();
void heartbeatTimerSlot();
private:
void readSettings();
void connectToDevice();
void disconnectFromDevice();
void heartbeat();
protected:
QHostAddress _canserverAddress;
QUdpSocket *_udpClient;
QTimer *_heartbeatTimer;
};
#endif /* canserver_h */
+4 -4
View File
@@ -109,16 +109,16 @@ void ConnectionWindow::readPendingDatagrams()
while(!CANBeaconXml.atEnd() && !CANBeaconXml.hasError())
{
CANBeaconXml.readNext();
if(CANBeaconXml.name() == "CANBeacon" && !CANBeaconXml.isEndElement())
if(CANBeaconXml.name() == QString("CANBeacon") && !CANBeaconXml.isEndElement())
KayakHost.append(CANBeaconXml.attributes().value("name"));
if(CANBeaconXml.name() == "URL")
if(CANBeaconXml.name() == QString("URL"))
KayakHost.append(" (" + CANBeaconXml.readElementText() + ')');
//Kayak can theoretically send multiple busses over one ports
//TODO: implement this case in socketcand.cpp
if(CANBeaconXml.name() == "Bus" && !CANBeaconXml.isEndElement())
KayakBus.append(CANBeaconXml.attributes().value("name") + ",");
if(CANBeaconXml.name() == QString("Bus") && !CANBeaconXml.isEndElement())
KayakBus.append(CANBeaconXml.attributes().value("name").toUtf8() + ",");
}
KayakHost = KayakBus.left(KayakBus.length() - 1) + "@" + KayakHost;
+4 -3
View File
@@ -289,9 +289,10 @@ void MQTT_BUS::clientMessageReceived(const QMQTT::Message& message)
if(frame_p)
{
uint32_t frameID = message.topic().split("/")[1].toInt();
uint64_t timeStamp = message.payload()[0] + (message.payload()[1] << 8) + (message.payload()[2] << 16) + (message.payload()[3] << 24)
+ ((uint64_t)message.payload()[4] << 32ull) + ((uint64_t)message.payload()[5] << 40ull)
+ ((uint64_t)message.payload()[6] << 48ull) + ((uint64_t)message.payload()[7] << 56ull);
QByteArray timeStampBytes = message.payload().left(8);
uint64_t timeStamp = qFromLittleEndian<uint64_t>(timeStampBytes.data());
int flags = message.payload()[8];
frame_p->setPayload(message.payload().right(message.payload().count() - 9));
frame_p->bus = 0;
+29
View File
@@ -28,6 +28,7 @@ NewConnectionDialog::NewConnectionDialog(QVector<QString>* gvretips, QVector<QSt
connect(ui->rbKayak, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged);
connect(ui->rbMQTT, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged);
connect(ui->rbLawicel, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged);
connect(ui->rbCANserver, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged);
connect(ui->cbDeviceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &NewConnectionDialog::handleDeviceTypeChanged);
connect(ui->btnOK, &QPushButton::clicked, this, &NewConnectionDialog::handleCreateButton);
@@ -62,6 +63,7 @@ void NewConnectionDialog::handleConnTypeChanged()
if (ui->rbRemote->isChecked()) selectRemote();
if (ui->rbKayak->isChecked()) selectKayak();
if (ui->rbMQTT->isChecked()) selectMQTT();
if (ui->rbCANserver->isChecked()) selectCANserver();
}
void NewConnectionDialog::handleDeviceTypeChanged()
@@ -202,6 +204,20 @@ void NewConnectionDialog::selectMQTT()
ui->cbPort->clear();
}
void NewConnectionDialog::selectCANserver()
{
ui->lPort->setText("CANserver IP Address:");
ui->lblDeviceType->setHidden(true);
ui->cbDeviceType->setHidden(true);
ui->cbCANSpeed->setHidden(true);
ui->cbSerialSpeed->setHidden(true);
ui->lblCANSpeed->setHidden(true);
ui->lblSerialSpeed->setHidden(true);
ui->cbPort->clear();
}
void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QString pDriver)
{
@@ -224,6 +240,10 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt
break;
case CANCon::LAWICEL:
ui->rbLawicel->setChecked(true);
break;
case CANCon::CANSERVER:
ui->rbCANserver->setChecked(true);
break;
default: {}
}
@@ -267,6 +287,11 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt
case CANCon::MQTT:
ui->cbPort->setCurrentText(pPortName);
break;
case CANCon::CANSERVER:
{
ui->cbPort->setCurrentText(pPortName);
break;
}
default: {}
}
}
@@ -282,6 +307,9 @@ QString NewConnectionDialog::getPortName()
return ui->cbPort->currentText();
case CANCon::KAYAK:
return ui->cbPort->currentText();
case CANCon::CANSERVER:
return ui->cbPort->currentText();
default:
qDebug() << "getPortName: can't get port";
}
@@ -324,6 +352,7 @@ CANCon::type NewConnectionDialog::getConnectionType()
if (ui->rbKayak->isChecked()) return CANCon::KAYAK;
if (ui->rbMQTT->isChecked()) return CANCon::MQTT;
if (ui->rbLawicel->isChecked()) return CANCon::LAWICEL;
if (ui->rbCANserver->isChecked()) return CANCon::CANSERVER;
qDebug() << "getConnectionType: error";
return CANCon::NONE;
+1
View File
@@ -46,6 +46,7 @@ private:
void selectKayak();
void selectMQTT();
void selectLawicel();
void selectCANserver();
bool isSerialBusAvailable();
void setPortName(CANCon::type pType, QString pPortName, QString pDriver);
};
+3 -3
View File
@@ -266,7 +266,7 @@ QString SocketCANd::decodeFrames(QString data, int busNum)
QString framePart = data.mid(firstIndex); //remove starting beginning of payload if not < frame >
const QString frameStrConst = framePart.left(framePart.indexOf(">")+1);
QString frameStr = frameStrConst;
QStringList frameParsed = (frameStr.remove(QRegExp("^<")).remove(QRegExp(">$"))).simplified().split(' ');
QStringList frameParsed = (frameStr.remove(QRegularExpression("^<")).remove(QRegularExpression(">$"))).simplified().split(' ');
if(frameParsed.length() < 3)
{
@@ -369,8 +369,8 @@ void SocketCANd::readTCPData(int busNum)
{
QString data;
if (tcpClient[busNum])
data = QString(tcpClient[busNum]->readAll());
if (QTcpSocket* socket = tcpClient.value(busNum))
data = QString(socket->readAll());
//sendDebug("Got data from TCP. Len = " % QString::number(data.length()));
//qDebug() << "Received datagramm: " << data;
procRXData(data, busNum);
+1
View File
@@ -110,6 +110,7 @@ public: //TODO: this is sloppy. It shouldn't all be public!
QList<DBC_VAL_ENUM_ENTRY> valList;
QList<DBC_SIGNAL *> multiplexedChildren;
DBC_SIGNAL *multiplexParent;
DBC_SIGNAL *self;
DBC_SIGNAL();
bool processAsText(const CANFrame &frame, QString &outString, bool outputName = true);
+24 -10
View File
@@ -441,12 +441,21 @@ void DBCFile::findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList<DBC_ATTRIBUTE>
}
}
//there's no external way to clear the flag. It is only cleared when the file is saved by this object.
void DBCFile::setDirtyFlag()
{
isDirty = true;
}
//BE CAREFUL HERE. Do not clear the dirty flag unless you're absolutely sure nothing has changed.
//Currently the signal editor clears this flag if the entire undo buffer is emptied but still
//it's possible that signals or messages were deleted or added so this is potentially not that safe
//It would be better if every node, message, and signal had a dirty flag. Then the DBCFile getDirtyFlag
//function could traverse the tree and see if anything is dirty.
void DBCFile::clearDirtyFlag()
{
isDirty = false;
}
bool DBCFile::getDirtyFlag()
{
return isDirty;
@@ -1294,7 +1303,7 @@ bool DBCFile::saveFile(QString fileName)
int msgNumber = 1;
int sigNumber = 1;
QFile *outFile = new QFile(fileName);
QString nodesOutput, msgOutput, commentsOutput, valuesOutput;
QString nodesOutput, msgOutput, commentsOutput, valuesOutput, extMultiplexOutput;
QString defaultsOutput, attrValOutput;
bool hasExtendedMultiplexing = false;
@@ -1512,6 +1521,9 @@ bool DBCFile::saveFile(QString fileName)
msgOutput.clear(); //got to reset it after writing
}
outFile->write(commentsOutput.toUtf8());
commentsOutput.clear();
//Now dump out all of the stored attributes
for (int x = 0; x < dbc_attributes.count(); x++)
{
@@ -1573,6 +1585,8 @@ bool DBCFile::saveFile(QString fileName)
defaultsOutput.append("\"" + dbc_attributes[x].enumVals[dbc_attributes[x].defaultValue.toInt()] + "\";\n");
break;
case ATTR_INT:
defaultsOutput.append(QString::number(dbc_attributes[x].defaultValue.toLongLong()) + ";\n");
break;
case ATTR_FLOAT:
defaultsOutput.append(dbc_attributes[x].defaultValue.toString() + ";\n");
break;
@@ -1592,7 +1606,7 @@ bool DBCFile::saveFile(QString fileName)
uint32_t ID = msg->ID;
if (msg->ID > 0x7FF || msg->extendedID)
{
msg->ID += 0x80000000ul; //set bit 31 if this ID is extended.
ID += 0x80000000ul; //set bit 31 if this ID is extended.
}
for (int s = 0; s < msg->sigHandler->getCount(); s++)
@@ -1605,7 +1619,7 @@ bool DBCFile::saveFile(QString fileName)
msgOutput.append(sig->name + " " + sig->multiplexParent->name + " ");
msgOutput.append(QString::number(sig->multiplexLowValue) + "-" + QString::number(sig->multiplexHighValue) + ";");
msgOutput.append("\n");
outFile->write(msgOutput.toUtf8());
extMultiplexOutput.append(msgOutput);
msgOutput.clear(); //got to reset it after writing
}
}
@@ -1613,15 +1627,15 @@ bool DBCFile::saveFile(QString fileName)
}
//now write out all of the accumulated comments and value tables from above
outFile->write(attrValOutput.toUtf8());
outFile->write(defaultsOutput.toUtf8());
outFile->write(commentsOutput.toUtf8());
outFile->write(attrValOutput.toUtf8());
outFile->write(valuesOutput.toUtf8());
outFile->write(extMultiplexOutput.toUtf8());
attrValOutput.clear();
defaultsOutput.clear();
commentsOutput.clear();
valuesOutput.clear();
extMultiplexOutput.clear();
outFile->close();
delete outFile;
@@ -2235,14 +2249,14 @@ DBCHandler::DBCHandler()
qDebug() << "Previously loaded DBC file count: " << filecount;
for (int i=0; i<filecount; i++)
{
QString filename = settings.value("DBC/Filename_" + QString(i),"").toString();
QString filename = settings.value("DBC/Filename_" + QString::number(i),"").toString();
DBCFile * file = loadDBCFile(filename);
if (file)
{
int bus = settings.value("DBC/AssocBus_" + QString(i),0).toInt();
int bus = settings.value("DBC/AssocBus_" + QString::number(i),0).toInt();
file->setAssocBus(bus);
MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString(i),0).toInt();
MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString::number(i),0).toInt();
DBC_ATTRIBUTE attr;
+1
View File
@@ -87,6 +87,7 @@ public:
void setAssocBus(int bus);
void setDirtyFlag();
bool getDirtyFlag();
void clearDirtyFlag();
void sort();
DBCMessageHandler *messageHandler;
+6 -6
View File
@@ -108,10 +108,10 @@ void DBCLoadSaveWindow::updateSettings()
qDebug() << "Save DBC settings #" << i << " File: " << file->getFullFilename()
<< "Bus: " << file->getAssocBus() << "MC: " << file->messageHandler->getMatchingCriteria()
<< "Filter Labeling: " << (file->messageHandler->filterLabeling() ? "enabled" : "disabled");
settings.setValue("DBC/Filename_" + QString(i), file->getFullFilename());
settings.setValue("DBC/AssocBus_" + QString(i), file->getAssocBus());
settings.setValue("DBC/MatchingCriteria_" + QString(i), file->messageHandler->getMatchingCriteria());
settings.setValue("DBC/FilterLabeling_" + QString(i), file->messageHandler->filterLabeling());
settings.setValue("DBC/Filename_" + QString::number(i), file->getFullFilename());
settings.setValue("DBC/AssocBus_" + QString::number(i), file->getAssocBus());
settings.setValue("DBC/MatchingCriteria_" + QString::number(i), file->messageHandler->getMatchingCriteria());
settings.setValue("DBC/FilterLabeling_" + QString::number(i), file->messageHandler->filterLabeling());
}
}
emit updatedDBCSettings();
@@ -200,7 +200,7 @@ void DBCLoadSaveWindow::loadFile()
DBC_ATTRIBUTE *attr = file->findAttributeByName("matchingcriteria");
QComboBox * mc_item = addMatchingCriteriaCombobox(idx);
if (attr && attr->defaultValue > 0)
if (attr && attr->defaultValue.toInt() > 0)
{
mc_item->setCurrentIndex(attr->defaultValue.toInt());
}
@@ -208,7 +208,7 @@ void DBCLoadSaveWindow::loadFile()
attr = file->findAttributeByName("filterlabeling");
QTableWidgetItem *item = new QTableWidgetItem("");
ui->tableFiles->setItem(idx, 3, item);
if (attr && attr->defaultValue > 0)
if (attr && attr->defaultValue.toInt() > 0)
{
item->setCheckState(Qt::Checked);
}
+4
View File
@@ -675,6 +675,9 @@ void DBCMainEditor::newMessage()
msgItem = nodeItem->parent();
nodeItem = msgItem->parent();
}
if (typ == DBCItemTypes::NODE){
msgItem = nodeItem;
}
//if there was a comment this will find the location of the comment and snip it out.
QString nodeName = nodeItem->data(0, Qt::DisplayRole).toString().split(" - ")[0];
@@ -702,6 +705,7 @@ void DBCMainEditor::newMessage()
msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500));
msg.ID = 0;
msg.len = 8;
msg.bgColor = QApplication::palette().color(QPalette::Base);
}
}
else
+316 -131
View File
@@ -37,7 +37,9 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
ui->bitfield->setMode(GridMode::SIGNAL_VIEW);
connect(ui->bitfield, SIGNAL(gridClicked(int)), this, SLOT(bitfieldClicked(int)));
connect(ui->bitfield, SIGNAL(gridClicked(int)), this, SLOT(bitfieldLeftClicked(int)));
connect(ui->bitfield, SIGNAL(gridRightClicked(int)), this, SLOT(bitfieldRightClicked(int)));
connect(ui->valuesTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomMenuValues(QPoint)));
ui->valuesTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->valuesTable, SIGNAL(cellChanged(int,int)), this, SLOT(onValuesCellChanged(int,int)));
@@ -47,18 +49,29 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
[=]()
{
if (currentSignal == nullptr) return;
if (currentSignal->intelByteOrder != ui->cbIntelFormat->isChecked()) dbcFile->setDirtyFlag();
currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked();
fillSignalForm(currentSignal);
if (currentSignal->intelByteOrder != ui->cbIntelFormat->isChecked())
{
dbcFile->setDirtyFlag();
pushToUndoBuffer();
currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked();
//fillSignalForm(currentSignal);
refreshBitGrid();
}
});
connect(ui->comboReceiver, &QComboBox::currentTextChanged,
[=]()
{
if (currentSignal == nullptr) return;
if (inhibitMsgProc) return;
DBC_NODE *node = dbcFile->findNodeByName(ui->comboReceiver->currentText());
if (currentSignal->receiver != node) dbcFile->setDirtyFlag();
currentSignal->receiver = node;
if (currentSignal->receiver != node)
{
dbcFile->setDirtyFlag();
pushToUndoBuffer();
currentSignal->receiver = node;
}
});
connect(ui->comboType, &QComboBox::currentTextChanged,
[=]()
@@ -67,38 +80,66 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
switch (ui->comboType->currentIndex())
{
case 0:
currentSignal->valType = UNSIGNED_INT;
if (currentSignal->valType != UNSIGNED_INT)
{
pushToUndoBuffer();
currentSignal->valType = UNSIGNED_INT;
dbcFile->setDirtyFlag();
fillSignalForm(currentSignal);
}
break;
case 1:
currentSignal->valType = SIGNED_INT;
if (currentSignal->valType != SIGNED_INT)
{
pushToUndoBuffer();
currentSignal->valType = SIGNED_INT;
dbcFile->setDirtyFlag();
fillSignalForm(currentSignal);
}
break;
case 2:
currentSignal->valType = SP_FLOAT;
if (dbcMessage) //if we have a good msg reference we can use it to get the # of bytes expected.
if (currentSignal->valType != SP_FLOAT)
{
int maxBit = ((dbcMessage->len * 8) - 32 + 7);
if (maxBit < 0) maxBit = 0;
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
pushToUndoBuffer();
currentSignal->valType = SP_FLOAT;
dbcFile->setDirtyFlag();
if (dbcMessage) //if we have a good msg reference we can use it to get the # of bytes expected.
{
int maxBit = ((dbcMessage->len * 8) - 32 + 7);
if (maxBit < 0) maxBit = 0;
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
}
else if (currentSignal->startBit > 39) currentSignal->startBit = 39;
currentSignal->signalSize = 32;
fillSignalForm(currentSignal);
}
else if (currentSignal->startBit > 39) currentSignal->startBit = 39;
currentSignal->signalSize = 32;
break;
case 3:
currentSignal->valType = DP_FLOAT;
if (dbcMessage)
if (currentSignal->valType != DP_FLOAT)
{
int maxBit = ((dbcMessage->len * 8) - 64 + 7);
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
pushToUndoBuffer();
currentSignal->valType = DP_FLOAT;
dbcFile->setDirtyFlag();
if (dbcMessage)
{
int maxBit = ((dbcMessage->len * 8) - 64 + 7);
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
}
else currentSignal->startBit = 7; //has to be!
currentSignal->signalSize = 64;
fillSignalForm(currentSignal);
}
else currentSignal->startBit = 7; //has to be!
currentSignal->signalSize = 64;
break;
case 4:
currentSignal->valType = STRING;
if (currentSignal->valType != STRING)
{
pushToUndoBuffer();
currentSignal->valType = STRING;
dbcFile->setDirtyFlag();
fillSignalForm(currentSignal);
}
break;
}
dbcFile->setDirtyFlag();
fillSignalForm(currentSignal);
});
connect(ui->txtBias, &QLineEdit::editingFinished,
[=]()
@@ -109,8 +150,12 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
temp = ui->txtBias->text().toDouble(&result);
if (result)
{
if (currentSignal->bias != temp) dbcFile->setDirtyFlag();
currentSignal->bias = temp;
if (currentSignal->bias != temp)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->bias = temp;
}
}
});
@@ -123,8 +168,12 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
temp = ui->txtMaxVal->text().toDouble(&result);
if (result)
{
if (currentSignal->max != temp) dbcFile->setDirtyFlag();
currentSignal->max = temp;
if (currentSignal->max != temp)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->max = temp;
}
}
});
@@ -137,10 +186,15 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
temp = ui->txtMinVal->text().toDouble(&result);
if (result)
{
if (currentSignal->min != temp) dbcFile->setDirtyFlag();
currentSignal->min = temp;
if (currentSignal->min != temp)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->min = temp;
}
}
});
connect(ui->txtScale, &QLineEdit::editingFinished,
[=]()
{
@@ -150,26 +204,40 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
temp = ui->txtScale->text().toDouble(&result);
if (result)
{
if (currentSignal->factor != temp) dbcFile->setDirtyFlag();
currentSignal->factor = temp;
if (currentSignal->factor != temp)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->factor = temp;
}
}
});
connect(ui->txtComment, &QLineEdit::editingFinished,
[=]()
{
if (currentSignal == nullptr) return;
if (currentSignal->comment != ui->txtComment->text().simplified().replace(' ','_')) dbcFile->setDirtyFlag();
currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_');
emit updatedTreeInfo(currentSignal);
if (currentSignal->comment != ui->txtComment->text().simplified().replace(' ','_'))
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_');
emit updatedTreeInfo(currentSignal);
}
});
connect(ui->txtUnitName, &QLineEdit::editingFinished,
[=]()
{
if (currentSignal == nullptr) return;
if (currentSignal->unitName != ui->txtUnitName->text().simplified().replace(' ','_')) dbcFile->setDirtyFlag();
currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_');
if (currentSignal->unitName != ui->txtUnitName->text().simplified().replace(' ','_'))
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_');
}
});
connect(ui->txtBitLength, &QLineEdit::textChanged,
[=]()
{
@@ -182,20 +250,35 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
if (temp > (int)(dbcMessage->len * 8)) return;
}
else if (temp > 64) return;
if (currentSignal->signalSize != temp) dbcFile->setDirtyFlag();
if (currentSignal->valType != SP_FLOAT && currentSignal->valType != DP_FLOAT)
if (currentSignal->valType == SP_FLOAT) temp = 32;
if (currentSignal->valType == DP_FLOAT) temp = 64;
if (currentSignal->signalSize != temp)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->signalSize = temp;
fillSignalForm(currentSignal);
//fillSignalForm(currentSignal);
refreshBitGrid();
}
});
connect(ui->txtName, &QLineEdit::editingFinished,
[=]()
{
if (currentSignal == nullptr) return;
QString tempNameStr = ui->txtName->text().simplified().replace(' ', '_');
if (currentSignal->name != tempNameStr) dbcFile->setDirtyFlag();
if (tempNameStr.length() > 0) currentSignal->name = tempNameStr;
//need to update the tree too.
emit updatedTreeInfo(currentSignal);
if (tempNameStr.length() == 0) return; //can't do that!
if (currentSignal->name != tempNameStr)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->name = tempNameStr;
refreshBitGrid();
//need to update the tree too.
emit updatedTreeInfo(currentSignal);
}
});
connect(ui->txtMultiplexLow, &QLineEdit::editingFinished,
@@ -204,9 +287,13 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
if (currentSignal == nullptr) return;
int temp;
temp = Utility::ParseStringToNum(ui->txtMultiplexLow->text());
if (currentSignal->multiplexLowValue != temp) dbcFile->setDirtyFlag();
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
currentSignal->multiplexLowValue = temp;
if (currentSignal->multiplexLowValue != temp)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
currentSignal->multiplexLowValue = temp;
}
});
connect(ui->txtMultiplexHigh, &QLineEdit::editingFinished,
@@ -215,78 +302,98 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
if (!currentSignal) return;
int temp;
temp = Utility::ParseStringToNum(ui->txtMultiplexHigh->text());
if (currentSignal->multiplexHighValue != temp) dbcFile->setDirtyFlag();
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
currentSignal->multiplexHighValue = temp;
if (currentSignal->multiplexHighValue != temp)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
currentSignal->multiplexHighValue = temp;
}
});
connect(ui->rbExtended, &QRadioButton::toggled,
[=](bool state)
{
if (!currentSignal) return;
if (state && currentSignal) //signal is now set as an extended multiplex/multiplexor
if (!state) return; //we only need to handle the case where it is true
//only do anything if this is different from the current state. It should be because we're in a toggle event but let's be sure
if (!currentSignal->isMultiplexed || !currentSignal->isMultiplexor)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->isMultiplexed = true;
currentSignal->isMultiplexor = true;
//an extended multi signal cannot be the root multiplexor for a message so make sure to remove it if it was.
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
fillSignalForm(currentSignal);
}
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
dbcFile->setDirtyFlag();
});
connect(ui->rbMultiplexed, &QRadioButton::toggled,
[=](bool state)
{
if (!currentSignal) return;
if (state && currentSignal) //signal is now set as a multiplexed signal
if (!state) return; //we only need to handle the case where it is true
//only do anything if this is different from the current state. It should be because we're in a toggle event but let's be sure
if (!currentSignal->isMultiplexed || currentSignal->isMultiplexor)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->isMultiplexed = true;
currentSignal->isMultiplexor = false;
//if the set multiplexor for the message was this signal then clear it
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
fillSignalForm(currentSignal);
}
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
dbcFile->setDirtyFlag();
});
connect(ui->rbMultiplexor, &QRadioButton::toggled,
[=](bool state)
{
if (!currentSignal) return;
if (state && currentSignal) //signal is now set as a multiplexed signal
if (!state) return; //we only need to handle the case where it is true
if (currentSignal->isMultiplexed || !currentSignal->isMultiplexor)
{
//don't allow this signal to be a multiplexor if there is already one for this message.
//if (dbcMessage->multiplexorSignal != currentSignal && dbcMessage->multiplexorSignal != nullptr) return; //I spoke too soon above...
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->isMultiplexed = false;
currentSignal->isMultiplexor = true;
//we just set that this is the multiplexor so update the message to show that as well.
dbcMessage->multiplexorSignal = currentSignal;
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
fillSignalForm(currentSignal);
}
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
dbcFile->setDirtyFlag();
});
connect(ui->rbNotMulti, &QRadioButton::toggled,
[=](bool state)
{
if (!currentSignal) return;
if (state && currentSignal) //signal is now set as a multiplexed signal
if (!state) return; //we only need to handle the case where it is true
if (currentSignal->isMultiplexed || currentSignal->isMultiplexor)
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
currentSignal->isMultiplexed = false;
currentSignal->isMultiplexor = false;
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
fillSignalForm(currentSignal);
}
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
dbcFile->setDirtyFlag();
});
connect(ui->cbMultiplexParent, &QComboBox::textActivated,
@@ -294,17 +401,20 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
{
if (currentSignal == nullptr) return;
if (inhibitMsgProc) return;
//qDebug() << "Curr text: :" << ui->cbMultiplexParent->currentText();
//try to look up the signal that we're set to now, remove this signal from existing children list
//add it to this one, update this signal's parent multiplexor
DBC_SIGNAL *newSig = dbcMessage->sigHandler->findSignalByName(ui->cbMultiplexParent->currentText());
DBC_SIGNAL *oldParent = currentSignal->multiplexParent;
if (newSig && oldParent)
if (newSig && oldParent && (newSig != oldParent))
{
pushToUndoBuffer();
dbcFile->setDirtyFlag();
oldParent->multiplexedChildren.removeOne(currentSignal);
currentSignal->multiplexParent = newSig;
newSig->multiplexedChildren.append(currentSignal);
dbcFile->setDirtyFlag();
refreshBitGrid();
emit updatedTreeInfo(currentSignal);
}
});
@@ -333,6 +443,12 @@ bool DBCSignalEditor::eventFilter(QObject *obj, QEvent *event)
case Qt::Key_F1:
HelpWindow::getRef()->showHelp("signaleditor.md");
break;
case Qt::Key_Z:
if (keyEvent->modifiers() == Qt::ControlModifier)
{
popFromUndoBuffer();
}
break;
}
return true;
} else {
@@ -392,18 +508,6 @@ void DBCSignalEditor::showEvent(QShowEvent* event)
fillSignalForm(currentSignal);
fillValueTable(currentSignal);
ui->bitfield->clearSignalNames();
for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++)
{
DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x);
//only set a signal name for signals which match multiplexparent with our currentsignal
if (!sig->multiplexParent || ((sig->multiplexParent == currentSignal->multiplexParent) && (sig->multiplexHighValue == currentSignal->multiplexHighValue)) )
{
ui->bitfield->setSignalNames(x, sig->name);
qDebug() << sig->name << sig->multiplexParent;
}
}
}
void DBCSignalEditor::refreshView()
@@ -467,12 +571,14 @@ void DBCSignalEditor::deleteCurrentValue()
/* WARNING: fillSignalForm can be called recursively since it is in the listener of cbIntelFormat */
void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
{
unsigned char bitpattern[64];
//sanity checks
if (!dbcMessage) return;
if (!dbcMessage->sigHandler) return;
inhibitMsgProc = true;
if (sig == nullptr) {
ui->groupBox->setEnabled(false);
//ui->groupBox->setEnabled(false);
ui->txtName->setText("");
ui->txtBias->setText("");
ui->txtBitLength->setText("");
@@ -486,9 +592,6 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
ui->rbMultiplexed->setChecked(false);
ui->rbMultiplexor->setChecked(false);
ui->rbNotMulti->setChecked(true);
memset(bitpattern, 0, 64); //clear it out
ui->bitfield->setReference(bitpattern, false);
ui->bitfield->updateData(bitpattern, true);
ui->comboReceiver->setCurrentIndex(0);
ui->comboType->setCurrentIndex(0);
inhibitMsgProc = false;
@@ -496,9 +599,8 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
}
/* we have a signal */
ui->groupBox->setEnabled(true);
//ui->groupBox->setEnabled(true);
generateUsedBits();
ui->txtName->setText(sig->name);
ui->txtBias->setText(QString::number(sig->bias));
ui->txtBitLength->setText(QString::number(sig->signalSize));
@@ -532,7 +634,9 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
DBC_SIGNAL *sig_iter = dbcMessage->sigHandler->findSignalByIdx(i);
if (sig_iter && sig_iter->isMultiplexor && (sig_iter != sig))
{
ui->cbMultiplexParent->addItem(sig_iter->name);
//only add this entry if there are no other entries with that name yet
if (ui->cbMultiplexParent->findText(sig_iter->name) == -1)
ui->cbMultiplexParent->addItem(sig_iter->name);
if (sig->multiplexParent == sig_iter) ui->cbMultiplexParent->setCurrentIndex(ui->cbMultiplexParent->count() - 1);
}
}
@@ -541,42 +645,6 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
ui->txtMultiplexHigh->setEnabled(sig->isMultiplexed);
ui->cbMultiplexParent->setEnabled(sig->isMultiplexed);
memset(bitpattern, 0, 64); //clear it out first.
int startBit, endBit;
startBit = sig->startBit;
bitpattern[startBit / 8] |= 1 << (startBit % 8); //make the start bit a different color to set it apart
ui->bitfield->setReference(bitpattern, false);
if (sig->intelByteOrder)
{
endBit = startBit + sig->signalSize - 1;
if (startBit < 0) startBit = 0;
if (endBit > 511) endBit = 511;
for (int y = startBit; y <= endBit; y++)
{
int byt = y / 8;
bitpattern[byt] |= 1 << (y % 8);
}
}
else //big endian / motorola format
{
//much more irritating than the intel version...
int size = sig->signalSize;
while (size > 0)
{
int byt = startBit / 8;
bitpattern[byt] |= 1 << (startBit % 8);
size--;
if ((startBit % 8) == 0) startBit += 15;
else startBit--;
if (startBit > 511) startBit = 511;
}
}
ui->bitfield->updateData(bitpattern, true);
ui->cbIntelFormat->setChecked(sig->intelByteOrder);
switch (sig->valType)
@@ -607,9 +675,71 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
}
}
refreshBitGrid();
inhibitMsgProc = false;
}
void DBCSignalEditor::refreshBitGrid()
{
unsigned char bitpattern[64];
memset(bitpattern, 0, 64); //clear it out
ui->bitfield->setReference(bitpattern, false);
ui->bitfield->updateData(bitpattern, true);
ui->bitfield->clearSignalNames();
for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++)
{
DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x);
//only set a signal name for signals which match multiplexparent with our currentsignal
if (!sig->multiplexParent || ((sig->multiplexParent == currentSignal->multiplexParent) && (sig->multiplexHighValue == currentSignal->multiplexHighValue)) )
{
ui->bitfield->setSignalNames(x, sig->name);
//qDebug() << sig->name << sig->multiplexParent;
}
}
generateUsedBits();
memset(bitpattern, 0, 64); //clear it out first.
int startBit, endBit;
startBit = currentSignal->startBit;
bitpattern[startBit / 8] |= 1 << (startBit % 8); //make the start bit a different color to set it apart
ui->bitfield->setReference(bitpattern, false);
if (currentSignal->intelByteOrder)
{
endBit = startBit + currentSignal->signalSize - 1;
if (startBit < 0) startBit = 0;
if (endBit > 511) endBit = 511;
for (int y = startBit; y <= endBit; y++)
{
int byt = y / 8;
bitpattern[byt] |= 1 << (y % 8);
}
}
else //big endian / motorola format
{
//much more irritating than the intel version...
int size = currentSignal->signalSize;
while (size > 0)
{
int byt = startBit / 8;
bitpattern[byt] |= 1 << (startBit % 8);
size--;
if ((startBit % 8) == 0) startBit += 15;
else startBit--;
if (startBit > 511) startBit = 511;
}
}
ui->bitfield->updateData(bitpattern, true);
}
/* fillValueTable also handles "enabled" state */
void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig)
{
@@ -644,9 +774,12 @@ void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig)
inhibitCellChanged = false;
}
void DBCSignalEditor::bitfieldClicked(int bit)
//Left clicking the grid sets the "starting bit" for the current signal
void DBCSignalEditor::bitfieldLeftClicked(int bit)
{
if (currentSignal == nullptr) return;
pushToUndoBuffer();
currentSignal->startBit = bit;
if (currentSignal->valType == SP_FLOAT)
{
@@ -672,6 +805,28 @@ void DBCSignalEditor::bitfieldClicked(int bit)
fillSignalForm(currentSignal);
}
//Right clicking the grid starts editing on whichever signal currently "owns" that bit.
//If there is no other signal then nothing happens (right now).
//Would be possible to create a new signal in that case
void DBCSignalEditor::bitfieldRightClicked(int bit)
{
//will return -1 if there is no signal there. Otherwise, returns signal number
//which is quite luckily also the index into the signal handler table
int sigNum = ui->bitfield->getUsedSignalNum(bit);
if (sigNum < 0) return;
pushToUndoBuffer(); // undo to resume editing the previous signal
currentSignal = dbcMessage->sigHandler->findSignalByIdx(sigNum);
if (currentSignal)
{
fillSignalForm(currentSignal);
fillValueTable(currentSignal);
}
}
void DBCSignalEditor::generateUsedBits()
{
uint8_t usedBits[64];
@@ -728,3 +883,33 @@ void DBCSignalEditor::generateUsedBits()
ui->bitfield->setUsed(usedBits, false);
ui->bitfield->setBytesToDraw(dbcMessage->len);
}
//Copy the current signal in its entirety to the undo buffer. Just for safe keeping
//Called before an edit is done to save the state so we can revert if necessary
void DBCSignalEditor::pushToUndoBuffer()
{
if (!currentSignal) return;
//store a copy of the pointer so that if we need to pop we can pop to the proper place
currentSignal->self = currentSignal;
undoBuffer.append(*currentSignal); //save the whole thing
qDebug() << "Pushing to undo buffer";
}
//Pop the last copy of a signal from the stack and begin editing it
void DBCSignalEditor::popFromUndoBuffer()
{
if (undoBuffer.empty())
{
dbcFile->clearDirtyFlag(); //TODO: Don't do this. Implement per-item dirty flags.
qDebug() << "Undo buffer empty";
return; //can't pop if there are no stored entries!
}
qDebug() << "Popping undo buffer";
DBC_SIGNAL sig = undoBuffer.back();
undoBuffer.pop_back();
currentSignal = sig.self; //restore the pointer
*currentSignal = sig; //write the contents into the memory pointed to
fillSignalForm(currentSignal);
fillValueTable(currentSignal);
}
+6 -1
View File
@@ -26,7 +26,8 @@ signals:
void updatedTreeInfo(DBC_SIGNAL *sig);
private slots:
void bitfieldClicked(int bit);
void bitfieldLeftClicked(int bit);
void bitfieldRightClicked(int bit);
void onValuesCellChanged(int row,int col);
void onCustomMenuValues(QPoint);
void deleteCurrentValue();
@@ -36,6 +37,7 @@ private:
DBCHandler *dbcHandler;
DBC_MESSAGE *dbcMessage;
DBC_SIGNAL *currentSignal;
QList<DBC_SIGNAL> undoBuffer;
DBCFile *dbcFile;
bool inhibitCellChanged;
bool inhibitMsgProc;
@@ -43,11 +45,14 @@ private:
void fillSignalForm(DBC_SIGNAL *sig);
void fillValueTable(DBC_SIGNAL *sig);
void generateUsedBits();
void refreshBitGrid();
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
void readSettings();
void writeSettings();
void pushToUndoBuffer();
void popFromUndoBuffer();
};
#endif // DBCSIGNALEDITOR_H
+88 -51
View File
@@ -867,7 +867,7 @@ bool FrameFileIO::loadCARBUSAnalyzerFile(QString filename, QVector<CANFrame>* fr
line = txt.readLine().simplified();
if (line.length() > 2)
{
QList<QString> tokens = line.split(QRegExp("\\s+"));
QList<QString> tokens = line.split(QRegularExpression("\\s+"));
if (tokens.length() > 3)
{
QString time = tokens[0].replace(",", "");
@@ -1062,6 +1062,9 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector<CANFrame>* frames)
QByteArray line;
int lineCounter = 0;
bool foundErrors = false;
int addendumTime = 0;
double previousTime = 0;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
{
@@ -1097,7 +1100,11 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector<CANFrame>* frames)
multiplier = 1; //special case. Assume no decimal means microseconds
}
//qDebug() << "decimal places " << decimalPlaces;
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[0].toDouble() * multiplier)));
if (previousTime > tokens[0].toDouble()) {
addendumTime += 60;
}
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>((tokens[0].toDouble() + addendumTime) * multiplier)));
previousTime = tokens[0].toDouble();
thisFrame.setFrameId( static_cast<uint32_t>(tokens[1].toInt(nullptr, 16)) );
thisFrame.setExtendedFrameFormat((thisFrame.frameId() > 0x7FF));
thisFrame.isReceived = true;
@@ -1395,10 +1402,13 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
lineCounter = 0;
}
line = inFile->readLine();
if (line.startsWith(";$FILEVERSION=2.0")) fileVersion = 20;
if (line.startsWith(";$FILEVERSION=1.3")) fileVersion = 13;
if (line.startsWith(";$FILEVERSION=1.1")) fileVersion = 11;
if (line.startsWith(';')) continue;
if (line.startsWith(';'))
{
if (line.contains("$FILEVERSION=2.0")) fileVersion = 20;
if (line.contains("$FILEVERSION=1.3")) fileVersion = 13;
if (line.contains("$FILEVERSION=1.1")) fileVersion = 11;
continue;
}
if (line.length() > 41)
{
line = line.simplified();
@@ -1459,7 +1469,7 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
if (tokens.length() > 6)
{
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[1].toDouble() * 1000.0)));
thisFrame.setFrameId(tokens[3].toUInt(nullptr, 16));
thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16));
if (thisFrame.frameId() < 0x1FFFFFFF)
{
int numBytes = tokens[6].toInt();
@@ -2024,6 +2034,9 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector<CANFrame>* frames)
if (tokens[2].toUpper().contains("TRUE")) thisFrame.setExtendedFrameFormat(true);
else thisFrame.setExtendedFrameFormat(false);
//fix for faulty files that fail to set the extended flag when they should
if (thisFrame.frameId() > 0x7FF) thisFrame.setExtendedFrameFormat(true);
thisFrame.setFrameType(QCanBusFrame::DataFrame);
if (fileVersion == 1)
@@ -3497,9 +3510,9 @@ bool FrameFileIO::isCanDumpFile(QString filename)
QFile *inFile = new QFile(filename);
QByteArray line;
QList<QByteArray> tokens;
QRegExp timeExp("^\\((\\S+)\\)$");
QRegExp IdValExp("^(\\S+)#(\\S+)$");
QRegExp valExp("(\\S{2})");
QRegularExpression timeExp(QRegularExpression::anchoredPattern("^\\((\\S+)\\)$")); //anchored pattern causes exact match
QRegularExpression IdValExp(QRegularExpression::anchoredPattern("^(\\S+)#(\\S+)$"));
QRegularExpression valExp("(\\S{2})");
int lineCounter = 0;
int pos = 0;
bool isMatch = true;
@@ -3524,11 +3537,13 @@ bool FrameFileIO::isCanDumpFile(QString filename)
tokens = line.simplified().split(' ');
if(tokens.count() < 3) isMatch = false;
/* timestamp */
ret = timeExp.exactMatch(tokens[0]);
if(!ret) isMatch = false;
/* timestamp */
QRegularExpressionMatch timeExpMatched = timeExp.match(tokens[0]);
if(!timeExpMatched.hasMatch()) {
isMatch = false;
}
/*uint64_t timestamp = (uint64_t)*/(timeExp.cap(1).toDouble(&ret) /** (double)1000000.0*/);
/*uint64_t timestamp = (uint64_t)*/(timeExpMatched.captured(1).toDouble(&ret) /** (double)1000000.0*/);
if(!ret) isMatch = false;
if (line.contains('[')) //the expanded format
@@ -3558,17 +3573,19 @@ bool FrameFileIO::isCanDumpFile(QString filename)
isMatch = false;
continue;
}
ret = IdValExp.exactMatch(tokens[2]);
if(!ret)
QRegularExpressionMatch IdValExpMatched = IdValExp.match(tokens[2]);
if(!IdValExpMatched.hasMatch())
{
isMatch = false;
continue;
}
/* ID */
/*int ID = */IdValExp.cap(1).toInt(&ret, 16);
/*int ID = */IdValExpMatched.captured(1).toInt(&ret, 16);
QString val= IdValExp.cap(2);
QString val= IdValExpMatched.captured(2);
pos = 0;
int len = 0;
@@ -3582,22 +3599,23 @@ bool FrameFileIO::isCanDumpFile(QString filename)
} else {
/* val byte per byte */
int lng = 0;
while ((pos = valExp.indexIn(val, pos)) != -1)
{
QRegularExpressionMatch valExpMatch;
QRegularExpressionMatchIterator i = valExp.globalMatch(val);
while (i.hasNext()) {
valExpMatch = i.next();
lng++;
if (lng > 8)
{
isMatch = false;
break;
}
/*int data = */valExp.cap(1).toInt(&ret, 16);
/*int data = */valExpMatch.captured(1).toInt(&ret, 16);
if(!ret)
{
isMatch = false;
break;
}
pos += valExp.matchedLength();
}
}
}
@@ -3624,11 +3642,10 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
CANFrame thisFrame;
QByteArray line;
QList<QByteArray> tokens;
QRegExp timeExp("^\\((\\S+)\\)$");
QRegExp IdValExp("^(\\S+)#(\\S+)$");
QRegExp valExp("(\\S{2})");
QRegularExpression timeExp(QRegularExpression::anchoredPattern("^\\((\\S+)\\)$")); //anchored pattern causes exact match
QRegularExpression IdValExp(QRegularExpression::anchoredPattern("^(\\S+)#(\\S+)$"));
QRegularExpression valExp("(\\S{2})");
int lineCounter = 0;
int pos = 0;
bool ret;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
@@ -3654,8 +3671,8 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
if(tokens.count()<3) continue;
/* timestamp */
ret = timeExp.exactMatch(tokens[0]);
if(!ret) continue;
QRegularExpressionMatch timeExpMatched = timeExp.match(tokens[0]);
if(!timeExpMatched.hasMatch()) continue;
//Sort out the bus
std::string busString = tokens[1].toStdString();
@@ -3676,7 +3693,7 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
thisFrame.bus = busNum;
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (uint64_t)(timeExp.cap(1).toDouble(&ret) * (double)1000000.0)));
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (uint64_t)(timeExpMatched.captured(1).toDouble(&ret) * (double)1000000.0)));
if(!ret) continue;
if (line.contains('[')) //the expanded format (second one from the above list)
@@ -3699,24 +3716,26 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
{
/* ID & value */
//qDebug() << tokens[2];
ret = IdValExp.exactMatch(tokens[2]);
if(!ret)
QRegularExpressionMatch IdValExpMatched = IdValExp.match(tokens[2]);
if(!IdValExpMatched.hasMatch())
{
qDebug() << "ID regex didn't match!";
continue;
}
/* ID */
thisFrame.setFrameId(static_cast<uint32_t>(IdValExp.cap(1).toInt(&ret, 16)));
if (IdValExp.cap(1).length() > 3) {
thisFrame.setFrameId(static_cast<uint32_t>(IdValExpMatched.captured(1).toInt(&ret, 16)));
if (IdValExpMatched.captured(1).length() > 3)
{
thisFrame.setExtendedFrameFormat(true);
} else {
}
else
{
thisFrame.setExtendedFrameFormat(false);
}
QString val= IdValExp.cap(2);
QString val = IdValExpMatched.captured(2);
pos = 0;
QByteArray bytes;
if (val.startsWith("R") && val.at(1).isDigit()) {
thisFrame.payload().resize( val.at(1).toLatin1() - '0' );
@@ -3724,12 +3743,13 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
} else {
thisFrame.setFrameType(QCanBusFrame::DataFrame);
/* val byte per byte */
while ((pos = valExp.indexIn(val, pos)) != -1)
QRegularExpressionMatch valExpMatch;
QRegularExpressionMatchIterator i = valExp.globalMatch(val);
while (i.hasNext())
{
bytes.append((char)valExp.cap(1).toInt(&ret, 16));
valExpMatch = i.next();
bytes.append((char)valExpMatch.captured(1).toInt(&ret, 16));
if(!ret) continue;
pos += valExp.matchedLength();
}
}
thisFrame.setPayload(bytes);
@@ -4402,7 +4422,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
return false;
}
QString const validSeparators(" -~");
QString const validSeparators(" -~;");
// Decode separators and format for later decoding.
headerLine = fileStream.readLine();
@@ -4611,17 +4631,34 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
currentFrame.setFrameType(QCanBusFrame::DataFrame);
currentFrame.bus = 0;
currentTime = currentTime.addYears(res.captured("year").toUInt());
currentTime = currentTime.addMonths(res.captured("month").toUInt());
currentTime = currentTime.addDays(res.captured("day").toUInt());
currentTime = currentTime.addSecs(
res.captured("hours").toUInt() * 60 * 60 +
res.captured("minutes").toUInt() * 60 +
res.captured("seconds").toUInt()
);
currentTime = currentTime.addMSecs(res.captured("ms").toUInt());
//currentTime is already initialized with startDate so it already has year,month,etc filled out.
//For each timestamp on the line we need to figure out how it differs from the already set time
//and move the timestamp accordingly.
if (res.captured("year").length() > 0)
{
//for all of these, only add the difference not the whole value!
currentTime = currentTime.addYears(res.captured("year").toUInt() - startDate.date().year());
}
currentFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, currentTime.toMSecsSinceEpoch()));
if (res.captured("month").length() > 0)
{
currentTime = currentTime.addMonths(res.captured("month").toUInt() - startDate.date().month());
}
if (res.captured("day").length() > 0)
{
currentTime = currentTime.addMonths(res.captured("day").toUInt() - startDate.date().day());
}
unsigned int seconds = res.captured("hours").toUInt() * 60 * 60 + res.captured("minutes").toUInt() * 60 +
res.captured("seconds").toUInt() - (startDate.time().msecsSinceStartOfDay() / 1000);
currentTime = currentTime.addSecs(seconds);
currentTime = currentTime.addMSecs(res.captured("ms").toUInt() - (startDate.time().msecsSinceStartOfDay() % 1000));
//time is stored in microseconds so we need to get the timestamp as milliseconds since the epoch
//then multiplyby 1000 to get microseconds since the epoch.
currentFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, currentTime.toMSecsSinceEpoch() * 1000));
if(res.captured("type").length() != 0) {
auto frameType = res.captured("type").toUInt();
+76 -5
View File
@@ -1,4 +1,5 @@
#include "frameplaybackwindow.h"
#include "mainwindow.h"
#include "ui_frameplaybackwindow.h"
#include <QDebug>
#include <QFileDialog>
@@ -48,6 +49,8 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidge
currentPosition = 0;
forward = true;
isPlaying = false;
wantPlaying = false;
haveIncomingTraffic = false;
updateFrameLabel();
@@ -72,6 +75,7 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidge
connect(ui->btnLoadFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::loadFilters);
connect(ui->btnSaveFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::saveFilters);
connect(ui->cbOriginalTiming, &QCheckBox::toggled, this, &FramePlaybackWindow::useOrigTimingClicked);
connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int)));
connect(&playbackObject, &FramePlaybackObject::EndOfFrameCache, this, &FramePlaybackWindow::EndOfFrameCache);
connect(&playbackObject, &FramePlaybackObject::statusUpdate, this, &FramePlaybackWindow::getStatusUpdate);
@@ -101,6 +105,8 @@ void FramePlaybackWindow::showEvent(QShowEvent *)
{
readSettings();
installEventFilter(this);
//any time the view is shown we'll go see how many buses are registered and redo the combobox to match
int numBuses = CANConManager::getInstance()->getNumBuses();
ui->comboCANBus->clear();
for (int n = 0; n < numBuses; n++) ui->comboCANBus->addItem(QString::number(n));
@@ -279,6 +285,32 @@ void FramePlaybackWindow::loadFilters()
}
}
void FramePlaybackWindow::updatedFrames(int numFrames)
{
CANFrame thisFrame;
if (numFrames == -1) //all frames deleted. Don't care
{
}
else if (numFrames == -2) //all new set of frames. Don't care
{
}
else //just got some new frames.
{
if (numFrames > 0)
{
haveIncomingTraffic = true;
if (wantPlaying && !isPlaying)
{
isPlaying = true;
updateFrameLabel();
if (forward) playbackObject.startPlaybackForward();
else playbackObject.startPlaybackBackward();
}
}
}
}
void FramePlaybackWindow::refreshIDList()
{
if (currentSeqNum < 0 || currentSeqItem == nullptr)
@@ -338,6 +370,8 @@ void FramePlaybackWindow::EndOfFrameCache()
else //not looping so stop playback entirely
{
isPlaying = false;
wantPlaying = false;
haveIncomingTraffic = false;
playbackObject.stopPlayback();
}
}
@@ -361,6 +395,8 @@ void FramePlaybackWindow::EndOfFrameCache()
else //not looping so stop playback entirely
{
isPlaying = false;
wantPlaying = false;
haveIncomingTraffic = false;
playbackObject.stopPlayback();
}
}
@@ -395,7 +431,10 @@ void FramePlaybackWindow::updateFrameLabel()
ui->lblCurrPlayback->setText(currentSeqItem->filename);
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()));
if (wantPlaying && !isPlaying)
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()) + " (WAITING)");
else
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()));
}
void FramePlaybackWindow::seqTableCellClicked(int row, int col)
@@ -537,28 +576,40 @@ void FramePlaybackWindow::btnLoadLive()
void FramePlaybackWindow::btnBackOneClick()
{
if (!checkNoSeqLoaded()) return;
forward = false;
isPlaying = false;
wantPlaying = false;
playbackObject.stepPlaybackBackward();
updateFrameLabel();
}
void FramePlaybackWindow::btnPauseClick()
{
isPlaying = false;
wantPlaying = false;
playbackObject.pausePlayback();
updateFrameLabel();
}
void FramePlaybackWindow::btnReverseClick()
{
if (!checkNoSeqLoaded()) return;
forward = false;
isPlaying = true;
playbackObject.startPlaybackBackward();
wantPlaying = true;
if (!ui->ckWaitForTraffic->isChecked())
{
playbackObject.startPlaybackBackward();
isPlaying = true;
}
updateFrameLabel();
}
void FramePlaybackWindow::btnStopClick()
{
isPlaying = false;
wantPlaying = false;
haveIncomingTraffic = false;
playbackObject.stopPlayback();
if (seqItems.count() > 0)
{
@@ -574,20 +625,40 @@ void FramePlaybackWindow::btnStopClick()
ui->tblSequence->setCurrentCell(0, 0);
refreshIDList();
}
updateFrameLabel();
}
void FramePlaybackWindow::btnPlayClick()
{
if (!checkNoSeqLoaded()) return;
forward = true;
isPlaying = true;
playbackObject.startPlaybackForward();
wantPlaying = true;
if (!ui->ckWaitForTraffic->isChecked())
{
playbackObject.startPlaybackForward();
isPlaying = true;
}
updateFrameLabel();
}
void FramePlaybackWindow::btnFwdOneClick()
{
if (!checkNoSeqLoaded()) return;
forward = true;
isPlaying = false;
wantPlaying = false;
playbackObject.stepPlaybackForward();
updateFrameLabel();
}
bool FramePlaybackWindow::checkNoSeqLoaded()
{
if (seqItems.count() == 0)
{
QMessageBox::warning(this, "Warning", "Cannot begin playback until at\nleast one playback source is loaded.");
return false;
}
return true;
}
void FramePlaybackWindow::changePlaybackSpeed(int newSpeed)
+4
View File
@@ -44,6 +44,7 @@ private slots:
void useOrigTimingClicked();
void getStatusUpdate(int frameNum);
void EndOfFrameCache();
void updatedFrames(int);
private:
Ui::FramePlaybackWindow *ui;
@@ -55,8 +56,10 @@ private:
int currentSeqNum;
FramePlaybackObject playbackObject;
bool forward;
bool wantPlaying;
bool isPlaying;
int currentPosition;
bool haveIncomingTraffic = false;
void refreshIDList();
void updateFrameLabel();
@@ -66,6 +69,7 @@ private:
void readSettings();
void writeSettings();
void calculateWhichBus();
bool checkNoSeqLoaded();
bool eventFilter(QObject *obj, QEvent *event);
};
+4 -2
View File
@@ -1,7 +1,7 @@
Bisector Window
=================
![Bisector Window](/images/Bisector.png)
![Bisector Window](images/Bisector.png)
Using the Bisector Window
==========================
@@ -14,8 +14,10 @@ Frame Number - You can split right at a given frame number. All frames up to tha
Percentage - Pretty much just like the Frame Number option but in percentage instead in case that is more convenient.
Bus Number - You can also split the capture to include or exclude a given bus number. This can be helpful to allow breaking up the file into per-bus files.
In all cases, you have the option of which side of the split you want to save. Click "Calculate Split" to process the split. You will see above the buttons a reference of how many frames there were in total and how many you would be saving after the split. From here you *should* be able to do one of two things:
"Save split frames to a new file" - Save the new list of frames (after the split) to a file. You can save to any file format that SavvyCAN supports elsewhere.
"Replace main list with split frames" - Were this to work it would replace the frames loaded in the rest of the application with the split list. I'm sure this would be a really nice thing to be able to do. Well, you can't right now. It's not implemented yet. So, just imagine you can do this. In the mean time you can save to a file then load that file. Sorry...
"Replace main list with split frames" - Erases all messages on the main window and replaces them with the results of the bisection. You will lose all discarded frames if you haven't saved them elsewhere.
+13
View File
@@ -0,0 +1,13 @@
CAN Bridge Window
=================
![Bridge Window](images/CANBridge.png)
Using the Bridge Window
==========================
The purpose of this window is simple, to allow one to forward traffic from one bus to another. Any two buses you have connected could be bridged in this way, even if they originate on different hardware adapters. By default neither side will forward to the other. You must select different buses for Side 1 and Side 2 then enable forwarding from one to the other. You can enable bi-directional forwarding. However, this can be problematic. If you are not careful you can create an infinite loop where traffic from one side gets forwarded to the other side which then forwards to the first side, and so on.
If you'd like to only forward some traffic then uncheck the boxes next to IDs you do not want to forward.
At the moment this window is in the preliminary stages and, as mentioned above, could be prone to allowing infinite loops in some circumstances.
+20 -6
View File
@@ -1,17 +1,31 @@
DBC Message Editor
===================
![DBC Editor](./images/DBCEditor.png)
![DBC Editor](images/DBCEditor.png)
This new interface places all nodes, messages, and signals into a tree structure. Each node has a list of messages that are sent by that node. Each message, in turn, has a list of signals contained within. Multiplexed and multi-level multiplexed messages are supported. Double clicking a node, message, or signal will bring up the relevant editor.
Quick Cheat Sheet
==================
* F3 = Go to the previous item while searching
* F4 = Go to the next item while searching
* F5 = Create a new node
* F6 = Create a new message
* F7 = Create a new signal
* DEL = Delete the currently selected item (node, message, or signal)
* You can right click on nodes to get some special operations
* If you have a message selected and you create a new message it will be a clone of the selected message. Likewise for signals.
* If you have a message selected and create a signal then that signal will have the message as its parent.
* All items have icons to help you quickly determine what they are. Nodes have triangle icons, messages have envelope icons, signals have a few different icons depending on whether they're normal signals, multiplexor, or multiplexed.
Working with Nodes
===================
In DBC files a node is a device on the CAN bus. For instance, the engine control unit (ECU) would be a node as would a motor controller, a battery charger, or any other device that is connected to the CAN bus. DBC files let you define nodes that are
set as either the sender or receiver of a message. This allows messages to be organized for more easy retrieval. To add
a new node click on the empty row beneath the last defined node and type a new name and optionally a comment. The comment is not used by SavvyCAN but can be filled out for your own reference. When a node is selected in the top list you will then see
in the bottom a listing of every message it sends.
In DBC files a node is a device on the CAN bus. For instance, the engine control unit (ECU) would be a node as would a motor controller, a battery charger, or any other device that is connected to the CAN bus. DBC files let you define nodes that are set as either the sender or receiver of a message. This allows messages to be organized for more easy retrieval. To add a new node click on the "New Node" button at the top or press F5 and type a new name and optionally a comment. The comment is not used by SavvyCAN but can be filled out for your own reference. The arrow next to a node can be pressed to get a list of all messages contained within.
Working with Messages
=====================
The bottom list is all of the messages that are sent by the selected node. A message is defined based on its message ID. The message ID is the CAN id used for this message. For normal DBC files this creates a one to one correspondence of ID to a given CAN ID. For J1939 messages special masking is done and so more than one actual CAN id will map to the given message ID but still only one specific J1939 PGN will come through. Once a message ID is entered in for a new message it will attempt to auto populate the Data Len column with the number of data bytes that message has. This is only for your information, it is not used by SavvyCAN. You can give the DBC message a meaningful name. The "Fg" and "Bg" columns can be clicked on to set a color. This will set the foreground and background color to use for this message. These colors will be used in the main frame view on the main screen when you click "Interpret Frames." Clicking on the "Signals" column will bring up the signals editor for that message so that you can edit, add, or remove signals from the message. The "Comments" column is once again not used by SavvyCAN and only for your viewing reference.
![DBC Editor](images/DBCMessageEditor.png)
Within each node are zero or more messages. Messages are defined based on their frame ID. For normal DBC files this creates a one to one correspondence of ID to a given CAN ID. For J1939 and GMLAN messages special masking is done and so more than one actual CAN id will map to the given message ID but still only one specific J1939 PGN will come through. Once a message ID is entered in for a new message it will attempt to auto populate the Data Len column with the number of data bytes that message has. You won't be able to set signals into bits that are past the message length so care should be taken to make this value accurate. You can give the DBC message a meaningful name. You can set the text color and background color for each message. This information will be used on the main screen when you click "Interpret Frames" but can quickly back fire. If you set black on black you will have a bad time. And, making your window look like a circus might not be ideal either. However, the choice is yours!
+1 -1
View File
@@ -6,7 +6,7 @@ DBC File Manager
Working with DBC Files
=======================
This screen allows you to load and save DBC files. SavvyCAN supports loading more than one DBC file at a time. It can even use more than one DBC file per bus. But, "Associated Bus" can be used to associate a given DBC file to only one bus. If you don't need to associate to any specific bus then set this value to -1 which means "any bus." The J1939 button causes SavvyCAN to mask out J1939 message IDs to conform to J1939 signaling. You can create a brand new DBC file by clicking "Create new DBC" button. It will be automatically named a unique name for you. You probably don't want that name though. Any time you save a DBC file its name will automatically update in the list. The "Load", "Save", "Remove", "Edit" buttons are all straight forward. You can also edit a DBC file by double clicking it in the list.
This screen allows you to load and save DBC files. SavvyCAN supports loading more than one DBC file at a time. It can even use more than one DBC file per bus. But, "Associated Bus" can be used to associate a given DBC file to only one bus. If you don't need to associate to any specific bus then set this value to -1 which means "any bus." Matching criteria is used to select J1939 or GMLAN if necessary. These two systems have special ways of interpreting the frame ID. You can create a brand new DBC file by clicking "Create new DBC" button. It will be automatically named a unique name for you. You probably don't want that name though. Any time you save a DBC file its name will automatically update in the list. The "Load", "Save", "Remove", "Edit" buttons are all straight forward. You can edit a DBC file by double clicking it in the list.
DBC File Ordering
+6 -2
View File
@@ -16,6 +16,10 @@ If you have a DBC file loaded which matches the ID you've selected then you will
The top right graph is a histogram of all the bits and the number of times each bit was set. This can be used to quickly visually see where data has changed.
The bottom right graph is of each individual byte as its value varies over time. All textual information can be saved to a text file for later analysis.
The bottom right has 8 graphs, one for each possible byte in a standard CAN frame (CAN-FD support is coming... eventually) Double clicking one of these graphs will size it up and remove the other 7 graphs. Double clicking again brings the 8 byte view back.
The interval histogram is in logarithmic scale and shows a listing of what intervals were seen between frames. This can be used to visually see the frame timing. Some frames get sent very regularly. They will show a very pronouced bell curve. Other frames might get sent on demand. These frames will have peaks at odd places and not conform to a nice distribution. For instance, in the picture you can see that the frame shows many frames around 15-20ms timing but some around 40, 60, and 80 as well. This might indicate the frame is not always sent or it might show that the frame is getting lost. After all, those timings are all around multiples of 20ms.
As in other windows, it is possible to use the mouse wheel to scale all the graphs on this form and it is also possible to pan around by clicking and dragging. Selecting one axis will also let you scale / pan just that one axis.
In the middle there is a bitfield view. This is color coded based upon how often each bit changes. It is called the "heatmap" for this reason. Bits that don't change often are cold and colored blue. Bits that are hot get increasingly red. In the picture you can see distinct areas where the bits are blue, light blue, green, orange. This is highly indicative of a counter. When a counter is found you will find that the lower bit changes basically every frame, the next bit up every other frame, the next bit every fourth frame, etc. This produces a very distinct pattern in the heatmap. Bits that never change are black. This view thus allows one to see where changing data is found within a frame. Chances are you can ignore all the black parts and focus only on places where some change has happened.
The interval histogram is in logarithmic scale and shows a listing of what intervals were seen between frames. This can be used to visually see the frame timing. Some frames get sent very regularly. They will show a very pronouced bell curve. Other frames might get sent on demand. These frames will have peaks at odd places and not conform to a nice distribution. For instance, in the picture you can see that the frame shows quite a few messages around 100ms but messages extend out to around 600ms as well. Still, the logrithmic scale means that the faster interval is, by far, the most common.
+4
View File
@@ -39,6 +39,8 @@ Once you've set both Data Len and Little Endian you can set the bits to use. The
"Stride" is not often used but will cause only every "x" values to actually be graphed. This can be used to graph a very dense set of data with less points to speed things up.
"Associated Bus" defaults to -1 which means any bus. If you know you want to only graph messages that arrived on a particular bus you can enter the number here. It is rare for two buses to have the same frame ID but with different actual messages but not unheard of. If you find that your graphs appear to randomly jitter back and forth between two distinct sets of values this may be the cause.
"Only Points" will graph using disconnected points instead of lines.
"Point Style" has a list of every style you can use for points. The default is to not show the points and instead draw a line through where the points are at. But, there are many other options.
@@ -48,3 +50,5 @@ Once you've set both Data Len and Little Endian you can set the bits to use. The
"Line Color" changes the color of the graphed line. It is automatically randomly set for new graphs but if you don't like the random color you can click the color and select a better one.
"Fill Color" If you're feeling fancy you can also specify a fill color. The default fill color has an alpha value of 0 meaning it is completely transparent / no used. If you set a fill color you will need to set the alpha channel as well. An alpha of even 40-60 is usually plenty.
DO NOTE -> all the options starting at "Point Style" will drastically affect performance. If you try to graph a million points with a special point style, large line thickness, and a fill color it WILL be slow. For maximum speed just graph colored lines with no points.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 50 KiB

+16 -13
View File
@@ -12,17 +12,18 @@ The Main Frame List
The main frame list takes up the majority of the main screen. This list consists of the following sections:
- Timestamp: The timestamp is either in microseconds or seconds. This is a setting in preferences. Either way, the timestamp can have microsecond resolution. The difference is just whether there is a decimal point or not. GVRET has the ability to maintain full microsecond resolution for timestamping purposes. There is a third timing mode where the timestamp can be customized and is based upon the actual "clock" time.
- Timestamp: The timestamp is either in microseconds or seconds. This is a setting in preferences. Either way, the timestamp can have microsecond resolution. The difference is just whether there is a decimal point or not. Many of the CAN capture devices have the ability to maintain full microsecond resolution for timestamping purposes. There is a third timing mode where the timestamp can be customized and is based upon the actual "clock" time.
- ID: The ID is specified either in hexadecimal or decimal (a preference you can set). This is the message identifier sent over the CAN bus.
- RTR: 0 = Standard Message 1 = Remote transmit request. An RTR frame merely asks a node to send a message, it has no payload of its own.
- Ext: 0 = Standard message (11 bit ID). 1 = Extended message (29 bit ID)
- Dir: Either "Rx" or "Tx" to show whether SavvyCAN has received or sent this message.
- Bus: SavvyCAN supports a variety of capture hardware. GVRET compatible devices can support more than one bus. The bus a frame came in on
is specified here. Many file formats do not specify bus and thus all frames will be loaded as bus 0.
- Len: The number of data bytes that were sent with this frame. It can range from 0 to 8.
- Len: The number of data bytes that were sent with this frame. It can range from 0 to 8 for standard CAN and 0 to 64 for CAN-FD.
- ASCII: A character based view of the CAN bytes in ASCII characters. Many systems that send serial numbers or VIN numbers will send them in ASCII and these will thus be visible here.
- Data: All of the data bytes separated by spaces. Can be in either hexadecimal or decimal (preference). If "Interpret Frames" is checked you will
also see extra data at the end of any frames that have DBC data. To see the rest of this data click upon the frame in the list. It will automatically expand to show all signals attached to that frame.
There is also a setting to limit the number of displayed bytes per line. This is especially useful for CAN-FD traffic.
The Bottom Statusbar
@@ -38,31 +39,33 @@ At the very bottom of the main screen is a status bar with three sections.
The Rest of the Main Window
===========================
To the right of the main frames list is an area that shows the total number of captured frames and the frames per second. Total frames might not match the number of shown frames. If you've deselected any IDs in the filter list then fewer frames will be shown. Frames per second is calculated as an average and so will wind up or down when there is a sudden change.
*To the right of the main frames list is an area that shows the total number of captured frames and the frames per second. Total frames might not match the number of shown frames. If you've deselected any IDs in the filter list then fewer frames will be shown. Frames per second is calculated as an average and so will wind up or down when there is a sudden change.
Suspend Capturing / Resume Capturing is a button that will temporarily disable frame capture or re-enable it. This can be used to keep everything connected without capturing traffic for a short time. This can help to not capture traffic in between tests.
*Suspend Capturing / Resume Capturing is a button that will temporarily disable frame capture or re-enable it. This can be used to keep everything connected without capturing traffic for a short time. This can help to not capture traffic in between tests.
The "Normalize Frame Timing" button is used to reset the lowest timestamp to "0" and offset all other timestamps accordingly. This is useful to remove the starting offset when you start up a device long before actual traffic starts. SavvyCAN is designed such that this doesn't really matter most of the time but normalizing the timing might be useful to help correlate the timing between two different captures.
*The "Normalize Frame Timing" button is used to reset the lowest timestamp to "0" and offset all other timestamps accordingly. This is useful to remove the starting offset when you start up a device long before actual traffic starts. SavvyCAN is designed such that this doesn't really matter most of the time but normalizing the timing might be useful to help correlate the timing between two different captures.
The "Clear Frames" button will erase all captured messages. They will be irreversibly erased and all memory will be freed.
*The "Clear Frames" button will erase all captured messages. They will be irreversibly erased and all memory will be freed.
The "Auto Scroll Window" checkbox will cause the main frame list to hunt toward the bottom of the list as frames come in. It will normally not be quite
*"Keep Filters While Clearing" will keep all the filters intact if you push the "Clear Frames" button. Otherwise all filters will also be cleared.
*The "Auto Scroll Window" checkbox will cause the main frame list to hunt toward the bottom of the list as frames come in. It will normally not be quite
at the very bottom as, for performance reasons, the program runs at quarter second updates to things like the auto scroll. Thus, the main list will be
scrolled to the bottom four times per second.
The "Interpret Frames" checkbox is used to specify whether the loaded DBC file should be used to interpret all available messages and signals. One might want
*The "Interpret Frames" checkbox is used to specify whether the loaded DBC file should be used to interpret all available messages and signals. One might want
this off for performance reasons (interpreting takes some extra processor power and RAM) or to declutter the main frame list.
The "Overwrite Mode" checkbox is used to ensure that only the newest frame for each message ID is shown. That is, if 100 messages with ID 0x105 come in you
*The "Overwrite Mode" checkbox is used to ensure that only the newest frame for each message ID is shown. That is, if 100 messages with ID 0x105 come in you
will see only the newest one. This is generally used alongside "Interpret Frames" to interpret frames and always see the up-to-date information.
"Expand All Rows" will expand all the rows to show every signal in every message. This will take a **VERY** long time if there are many messages loaded. Because of this, you may receive a warning if the program determines that this will take an excessive amount of time to complete. You can make it work faster by filtering away any unneeded messages.
*"Expand All Rows" will expand all the rows to show every signal in every message. This will take a **VERY** long time if there are many messages loaded. Because of this, you may receive a warning if the program determines that this will take an excessive amount of time to complete. You can make it work faster by filtering away any unneeded messages.
"Collapse All Rows" will drop all rows back to taking up only one line. This can also take a while to run and will also warn if the operation seems like it will take a very long time to complete.
*"Collapse All Rows" will drop all rows back to taking up only one line. This can also take a while to run and will also warn if the operation seems like it will take a very long time to complete.
"Bus Filtering" allows for messages to be shown or hidden based on which bus they came in on.
*"Bus Filtering" allows for messages to be shown or hidden based on which bus they came in on.
"Frame Filtering" provides a list of all the frame IDs seen so far. Any ID which is checked will be shown in the main list. Any ID which is unchecked will not.
*"Frame Filtering" provides a list of all the frame IDs seen so far. Any ID which is checked will be shown in the main list. Any ID which is unchecked will not.
This can be used to hone in on frames of importance while hiding frames that are currently of no interest. The filtered list can be saved as well.
+4 -2
View File
@@ -1,7 +1,7 @@
Playback Window
===============
![Playback Window](./images/PlayBack.png)
![Playback Window](./images/Playback.png)
Preparing Frames for Playback
@@ -18,7 +18,9 @@ The playback window can send frames on a specific bus, all buses (be careful wit
The next order of business is frame timing. There are two approaches possible here. If you click "Use original frame timing from captured frames" then frames will be sent out in approximately the same timing as they came in with. The word approximately is used because it is difficult to get 1ms timing precision on a desktop OS. Frames that come in rapidly might have a 2-3ms jitter. In practice this is almost always irrelevant. This setting is suitable for nearly all uses.
Alternatively, it is also possible to send on a set schedule. With the "Use original" checkbox not checked you can set a playback speed in milliseconds and a burst rate. Burst means that it'll send that many frames every tick. So, if you have a burst of 5 and a timing of 10ms then every 10ms 5 frames will be sent. This mode can provide for a predictable number of frames per second and could be useful to test how quickly a device really requires traffic without faulting. But, it will potentially drastically alter the timing of frames compared to their timing when they were captured.
Alternatively, it is also possible to send on a set schedule. With the "Use original" checkbox not checked you can set a playback speed in milliseconds and a burst rate. Burst means that it'll send that many frames every tick. So, if you have a burst of 5 and a timing of 10ms then every 10ms 5 frames will be sent. This mode can provide for a predictable number of frames per second and could be useful to test how quickly a device really requires traffic without faulting. But, it will potentially drastically alter the timing of frames compared to their timing when they were captured. You can set a burst rate as well. Burst Rate is the number of frames sent every "tick." This can speed up how fast you can send traffic.
There is also now a checkbox that allows for waiting for traffic before sending CAN frames when you click play forward or backward. If this checkbox is checked you will see (WAITING) to the left of the number of frames below "Current frame." Once any CAN traffic starts to come in your frames will begin to playback automatically. Why would you want to do this? Well, the most likely reason is that you want to play back a CAN capture but you only want to do so once the vehicle has been powered on. And, you want to wait until the CAN buses are active so that you don't fault by sending traffic into nowhere. Lastly, this allows your playback to happen very rapidly after start up which might otherwise be tougher to pull off accurately.
The top of the window has a series of 6 icons all in a row:
+34 -16
View File
@@ -5,53 +5,71 @@ Preference Window
There are a variety of preferences that can be set in the program and persisted for future sessions.
Main Form Settings
===================
* Autoscroll main frame winow by default - When capturing frames should the main window track the newest incoming messages or stay where it was? This option defaults it to follow the incoming frames by default. The main window has a toggle for auto scroll. This setting just sets the default value of that toggle.
* Maximum data bytes per line - Really must helpful for CAN-FD traffic. Setting this to 8, 16, or 32 can help to be able to see all the bytes without the window having to be extremely wide.
General Settings
====================
"Save/Restore window positions and sizes": If this is set then the size and placement of the various windows in this application will be saved when the program is closed and loaded when each window is brought back up in the future. This can be used to create your own preferred layout. If you'd rather things come up in their default state every time then you can uncheck this box.
* "Save/Restore window positions and sizes": If this is set then the size and placement of the various windows in this application will be saved when the program is closed and loaded when each window is brought back up in the future. This can be used to create your own preferred layout. If you'd rather things come up in their default state every time then you can uncheck this box.
"Save/Restore CAN bus connections": This will cause the program to remember the devices you were connected to last time you ran the program and attempt to reconnect to them upon start up.
* "Save/Restore CAN bus connections": This will cause the program to remember the devices you were connected to last time you ran the program and attempt to reconnect to them upon start up.
"Display values as hexadecimal": A lot of the time people who are doing CAN reverse engineering like to see values in hexadecimal (base 16) instead of the more familiar decimal (base 10) system. Checking this box will cause most of the values in the application to show up in hex. This applies to CAN ids and data bytes. Unchecking this causes values to default to decimal instead. The reason for using hex is that each hex digit is 4 bits. Integers on a computer tend to be in multiples of 8 - 8, 16, 32, 64. So, hex digits have a direct mapping to the underlying binary. Decimal does not have this correspondence AT ALL. But, the choice is yours.
* "Display values as hexadecimal": A lot of the time people who are doing CAN reverse engineering like to see values in hexadecimal (base 16) instead of the more familiar decimal (base 10) system. Checking this box will cause most of the values in the application to show up in hex. This applies to CAN ids and data bytes. Unchecking this causes values to default to decimal instead. The reason for using hex is that each hex digit is 4 bits. Integers on a computer tend to be in multiples of 8 - 8, 16, 32, 64. So, hex digits have a direct mapping to the underlying binary. Decimal does not have this correspondence AT ALL. But, the choice is yours.
"Require validation of GVRET connection": GVRET style devices run over a serial connection. Serial connections can be finicky sometimes and so the connection can be validated to prove that everything is really still operating and talking. There probably isn't any reason to turn this off except while debugging to see if it changes anything. Mostly just don't touch this.
* "Require validation of GVRET connection": GVRET style devices run over a serial connection. Serial connections can be finicky sometimes and so the connection can be validated to prove that everything is really still operating and talking. There probably isn't any reason to turn this off except while debugging to see if it changes anything. Mostly just don't touch this.
"Label filters using messages from DBC files": Part of a two part system. If this is selected and a given DBC file also has its checkbox checked then the IDs list on the main screen (lower right) used for filtering will have not only the IDs but also the message name from the DBC file. This will allow for more easily determining what a given message means. Sometimes IDs can be hard to remember. Do note that this checkbox must be checked or the checkboxes for each loaded DBC file won't do anything. This is the master on/off switch.
* "Use filtered frames in sub-windows": The main window has a filtering interface where you can uncheck IDs to hide them. Ordinarily when you bring up one of the other windows it will still use the main unfiltered list. Sometimes you really do want to deal with the filtered list of frames even in the other windows. If this is checked then the other windows will see the filtered list and not the unfiltered actual list of frames that have been captured.
"Time Keeping": There are a variety of ways one could timestamp CAN frames as they come into the program. Selecting "Seconds" will cause the timestamp to be expressed as seconds since the frame list was last cleared. This tends to be an easy choice to work with. "Microseconds" will express the timestamp as millionths of a second since the last time the frame list was cleared. This is exactly like "Seconds" mode but without any decimal point. You might find this to be a bit hard to conceptualize. The last option is "System Clock" this will timestamp frames with the current system time when the frame came in. This is still very precise but now you'll get an absolute time stamp with the full date and time. The display of this mode can be changed by editing the "Time Format String" value. It defaults to an output that looks like "JAN-10 12:34:53.234" But you can set it to other values. Look here to find a reference for how you can create new format strings: http://doc.qt.io/qt-4.8/qdatetime.html#toString
* "OpenGL Accelerated AntiAliased Graphing": Checking this will cause all of the graphs to use OpenGL 3D acceleration. Most modern machines have some form of 3D acceleration so this option should be OK to use. If you check this your graphs will look a lot better and on good hardware should also be faster. In the future other options are likely to be added to the graphing screen that will likely only be enabled if OpenGL mode is also enabled. Try enabling this and see if performance is still good. It's safe to leave it off if in doubt.
"Use filtered frames in sub-windows": The main window has a filtering interface where you can uncheck IDs to hide them. Ordinarily when you bring up one of the other windows it will still use the main unfiltered list. Sometimes you really do want to deal with the filtered list of frames even in the other windows. If this is checked then the other windows will see the filtered list and not the unfiltered actual list of frames that have been captured.
* "CAN Frame Pre-allocation Size" - This requires a bit of explanation and caution. When SavvyCAN starts it pre-allocates a giant buffer for incoming CAN traffic. Otherwise as traffic comes in the program would have a limited amount of space allocated to receive the traffic. If this reserved space runs out then the program would have to go ask the operating system for more and copy all existing frames to the newer, bigger buffer. This is a slow process. So, instead a giant buffer is allocated up front (by default 10 million frames worth!). You aren't likely to exceed this value and so it never has to ask for more memory and things run smoothly. 10M frames is about 1/2 of a gigabyte. This is a lot of memory but very doable for most modern PCs. But, if you are running on a Raspberry Pi it may be a good idea to turn this down to, say, 1M instead. You may be tempted to make this value really large so that, no matter what, it never has to reallocate. But, setting this 100x bigger would try to allocate 50GB of RAM. You probably don't have that much RAM to spare. So, be cautious if you raise this value. 10M should be enough for most anyone. Even if you did happen to exceed the value the program won't crash, it will just pause for a long time as it creates a larger buffer and moves everything over.
"OpenGL Accelerated AntiAliased Graphing": Checking this will cause all of the graphs to use OpenGL 3D acceleration. Most modern machines have some form of 3D acceleration so this option should be OK to use. If you check this your graphs will look a lot better and on good hardware should also be faster. In the future other options are likely to be added to the graphing screen that will likely only be enabled if OpenGL mode is also enabled. Try enabling this and see if performance is still good. It's safe to leave it off if in doubt.
* "Time Keeping": There are a variety of ways one could timestamp CAN frames as they come into the program. Selecting "Seconds" will cause the timestamp to be expressed as seconds since the frame list was last cleared. This tends to be an easy choice to work with. "Microseconds" will express the timestamp as millionths of a second since the last time the frame list was cleared. This is exactly like "Seconds" mode but without any decimal point. You might find this to be a bit hard to conceptualize. The last option is "System Clock" this will timestamp frames with the current system time when the frame came in. This is still very precise but now you'll get an absolute time stamp with the full date and time. The display of this mode can be changed by editing the "Time Format String" value. It defaults to an output that looks like "JAN-10 12:34:53.234" But you can set it to other values. Look here to find a reference for how you can create new format strings: http://doc.qt.io/qt-4.8/qdatetime.html#toString
"Autoscroll main frame window by default": If checked it will automatically check the relevant checkbox on the main screen when the program starts. This will cause the view to automatically scroll to the bottom by default. This is merely a convenience option if you'd prefer it to be the default.
Font Settings
==============
* "Use fixed-width font in tables" - The default is to use the normal system font for everything in the program. The problem is, most default fonts are not fixed width. Why would you want fixed width? This makes things line up better between lines. With fixed width fonts you know that the third byte of one line is directly over top of the third byte for the line one down. This can make it easier to scan through multiple lines.
* Size - You can set the default font size used in the application. Setting this up or down a bit may help things to look better to you. Getting to absurd is likely to make the program look really bad. This setting may be required if you use a 4k monitor and your operating system doesn't want to cooperate.
Flow View Settings
==================
"Use timestamp mode by default": This is another convenience option. With this checked the Flow view will default to using time stamps on the graphing area instead of frame numbers.
* "Use timestamp mode by default": This is another convenience option. With this checked the Flow view will default to using time stamps on the graphing area instead of frame numbers.
"Set Auto Reference by default": The Flow view can either use static referencing or dynamic referencing (see the Flow view documentation for more info). If you'd like to use dynamic referencing and automatically set the reference by default then check this option.
* "Set Auto Reference by default": The Flow view can either use static referencing or dynamic referencing (see the Flow view documentation for more info). If you'd like to use dynamic referencing and automatically set the reference by default then check this option.
* "Hexadecimal Graph Y Axis" - Use 00-FF instead of 0-255 for the Y axis.
Playback Window Settings
========================
"Loop by default": Set the playback window to default to looping infinitely by default.
* "Loop by default": Set the playback window to default to looping infinitely by default.
"Default playback speed (ms)": Set the default timing for playback
* "Default playback speed (ms)": Set the default timing for playback
"Default Sending Bus": Set a default for which bus to send frames on.
* "Default Sending Bus": Set a default for which bus to send frames on.
DBC Settings
=============
* "Label DBC Messages by default" - Normally the filter section of the main screen just shows the frame IDs. If you label DBC signals you will actually see the name of the DBC message next to the ID.
* "Ignore DBC message colors" - It's possible to make DBC messages have rainbow colors. Each message can have a different background and foreground color. Sometimes they can get set to some really silly values. To save your sanity you have the option to completely ignore all colors set in the DBC file and just use the default colors for everything.
File Info And Comparator Window Settings
========================================
"Auto expand all nodes": Both of the referenced windows have tree views that potentially have a large number of nodes. It's more neat not to expand them all by default but it also then requires more clicks if you have to expand them to view the information. So, you can set whether you'd like to expand them all by default or not.
* "Auto expand all nodes": Both of the referenced windows have tree views that potentially have a large number of nodes. It's more neat not to expand them all by default but it also then requires more clicks if you have to expand them to view the information. So, you can set whether you'd like to expand them all by default or not.
* "Hexadecimal Graph Y Axis" - As with the Flowview, it is possible to change the Y axis to hexadecimal instead of decimal.
MQTT Settings
=============
MQTT allows SavvyCAN to connec to a broker for CAN transmission over the internet. You will need to set up the host, port, username, and password. These things are determined by your broker. You can run your own on Linux with Mosquitto.
* MQTT allows SavvyCAN to connect to a broker for CAN transmission over the internet. You will need to set up the host, port, username, and password. These things are determined by your broker. You can run your own on Linux with Mosquitto. Yes, api.savvycan.com really does exist but, no, you can't actually connect as Anonymous and use it. Sorry...
Final Word of Warning
+15 -2
View File
@@ -3,12 +3,25 @@ DBC Signal Editor
![Signal Editor](./images/SignalEditor.png)
Not So Secret Things
=====================
First of all, here are the things that might otherwise pass under the radar but are functions of this form:
* Pretty much everything on this form now has secret undo functionality. If you make a mistake you can press CTRL-Z to revert it. This will revert practically anything you've done on this form.
* The 8x8 data grid is not locked that way. CAN-FD signals are supported and so up to 64 bytes can be shown in the bitfield. The bitfield will automatically adjust for the number of bytes in a given message. However, you can also switch the views yourself by middle clicking in the bitfield.
* If you right click a different signal in the bitfield you will then be editing that signal instead. This lets you quickly move between signals in the same message. However, this only works for other signals that are either not multiplexed (if you started with a non-multiplexed signal when editing) or otherwise only other signals that have the same multiplexor value.
Defining and Editing Signals
============================
On the right side you can rename the signal and you'll see that it is renamed in the DBC window as well.
At the top you can rename the signal and you'll see that it is renamed in the DBC window as well.
"Bit Length" - This sets how many bits the signal uses. Once you do this you'll see that that many bits are now highlighted in the 8x8 data grid above. The black bit is the "start" bit, green bits are the other bits in the signal. Gray bits are already used by another signal. You can still use them for the current signal too but doing so would be quite unusual unless you have multiplexed signals.
The majority of the view is taken up by the bitfield grid. This view shows all the signals contained within this message (that have the same multiplexor value as this one). They're all labeled as well. Bonus fun fact - SavvyCAN technically does support CAN-FD DBC files! That's right, you can load DBC files with CAN-FD signals and they will work. The bitfield will adjust to show enough bits to show the whole message.
"Bit Length" - This sets how many bits the signal uses. Once you do this you'll see that that many bits are now highlighted in the data grid. The black bit with diagonal stripes is the "start" bit, green bits (with stripes the other way) are the other bits in the signal. Bits used by other signals are colored according to a secret list of colors. They also have the signal name. You should not overlap onto other signals. This could be possible with multiplexed signals but you will NOT see signals on this grid that are not part of the same multiplexor value as this signal so you should NOT overlap here.
"Byte Order" - The way that the green bits are filled out is changed by this checkbox. Checking it selects little endian mode whereas deselecting chooses big endian mode. This will have an effect on any signal that crosses byte boundaries. The simplest explanation is that little endian signals start at the start bit and then go "down" in bit numbers while big endian mode goes "up."
+8 -4
View File
@@ -12,13 +12,15 @@ This is essentially another CAN fuzzing window but a very special one. This wind
Using the UDS Scan Window
==========================
This window allows one to set a list of tests to perform. They will be done in order. You are free to change the parameters of each separately. For instance, one test can scan 0x7E0 through 0x7E7 and the next test can can only 0x600.
UDS queries are sent out on the bus from "Starting ID" to "Ending ID". Usually UDS compliant ECUs will respond to 0x7E0 through 0x7E7 which is why those are the defaults. Some vehicles use UDS "like" protocols on other IDs. Usually UDS nodes reply with an ID 8 higher than the request ID. This is thus the default in the program. However, some nodes cheat and do not do this. It is quite common for responses to come from an address 16 higher instead. Sometimes the reply address has no resemblance to the listening address. To deal with this situation there is a checkbox "Allow adaptive reply offset." If this is checked then replies will be accepted no matter what address they come from. Deselecting this will cause only replies of the proper offset to be accepted. The offset defaults to 8 but can be changed with the "Reply Offset" selector. Additionally, you can select which bus to scan and set how long you want to wait for replies.
"Show tests with no Replies" - This checkbox does what it says. It is a personal preference whether you'd like to see an entry in the list for scans that returned no results. Sometimes an ECU will just plain ignore messages it doesn't like. In that case you have the option to see an entry in the list telling you that the message was ignored or whether you'd prefer to reduce clutter and just skip anything that had no reply.
"Show 'No Reply'" - This checkbox does what it says. It is a personal preference whether you'd like to see an entry in the list for scans that returned no results. Sometimes an ECU will just plain ignore messages it doesn't like. In that case you have the option to see an entry in the list telling you that the message was ignored or whether you'd prefer to reduce clutter and just skip anything that had no reply.
You need to also set a type of scan to do. You can select more than one type but if you don't set any then you aren't going to see any results. Keep that in mind as none are checked by default.
You need to also set a type of scan to do. Each test you register will do one type of scan but you are free to create as many tests as you like.
"Read By ID" - UDS allows one to read data from the ECU by an ID number. These are not defined anywhere and are custom to each ECU. But, you can use this to scan a range of IDs to see if you get a response to any of them. There is no real standard for how many bytes the ID will be. It could be 1, it could be 2, 3, 4. It's likely to be around 2. You can set the number of subfunction bytes which will set the size of the ID. Then you can set the upper and lower bound to scan.
"Read By ID" - UDS allows one to read data from the ECU by an ID number. Many of these are not defined anywhere and are custom to each ECU. But, you can use this to scan a range of IDs to see if you get a response to any of them. There is usually no real standard for how many bytes the ID will be. It could be 1, it could be 2, 3, 4. It's likely to be around 2. You can set the number of subfunction bytes which will set the size of the ID. Then you can set the upper and lower bound to scan.
"Read By Addr" - You can also read data by address. Like IDs the address could be different sizes depending on the hardware you are querying. This works the same as reading by ID but by address instead.
@@ -26,8 +28,10 @@ You need to also set a type of scan to do. You can select more than one type but
"ECU Reset" - The ECU might also support being reset by a UDS message. This scan type will try the various reset types and see which are supported.
"Security Access" - When a device is first started and normally operating it will generally not allow you to do potentially dangerous or sensitive operations such as downloading firmware or changing parameters. To do these things you need to enter a different security level. The security access mechanism is used for this. There are a few different security levels that are likely to be supported. This scan type will attempt to find security levels and see if they are protected or not. That is, unfortunately for the casual cracker, most of the time the elevate security levels will be protected by a challenge/response system. The ECU will send you a challenge in the form of one or more random looking bytes. You are tasked with returning the proper response for those challenge bytes. You generally don't have very many guesses before your hands are slapped. Sometimes the C/R is actually fixed and you can just capture valid traffic once and then use the same response forever. Sometimes the C/R is stupidly easy or there are only a couple of different possible answers. Sometimes the ECU developer actually took more than 2 seconds to implement these features and your work will be cut out for you. This scan type will find which levels the ECU seems to support but is unlikely to actually unlock them.
"Security Access" - When a device is first started and normally operating it will generally not allow you to do potentially dangerous or sensitive operations such as downloading firmware or changing parameters. To do these things you need to enter a different security level. The security access mechanism is used for this. There are a few different security levels that are likely to be supported. This scan type will attempt to find security levels and see if they are protected or not. That is, unfortunately for the casual cracker, most of the time the elevated security levels will be protected by a challenge/response system. The ECU will send you a challenge in the form of one or more random looking bytes. You are tasked with returning the proper response for those challenge bytes. You generally don't have very many guesses before your hands are slapped. Sometimes the C/R is actually fixed and you can just capture valid traffic once and then use the same response forever. Sometimes the C/R is stupidly easy or there are only a couple of different possible answers. Sometimes the ECU developer actually took more than 2 seconds to implement these features and your work will be cut out for you. This scan type will find which levels the ECU seems to support but is unlikely to actually unlock them.
"Tester Present" - A scan that tries to see if tester present is supported. It almost certainly is. This scan can be used to sweep a wide range of addresses just to narrow down the list of addresses to scan more thoroughly. Normally tester present is sent periodically by a connected device just to let the ECU know that someone is still there.
"Wildcard" - Allows for you to set a lower and upper range for the service byte as well as the number of subfunction bytes and the range there as well. This allows for UDS fuzzing by shooting the moon and trying a huge range of traffic just to see what is supported and what isn't. This test can take a VERY long time if you aren't careful but will thoroughly determine what the ECU will support and what it won't.
"Run test in session type" is supported for some of the above scan types. This will cause the program to attempt to put the ECU into the chosen session type before doing the test. This can be useful as some things will only be supported in diagnostics mode or programming mode. Unfortunately, entering programming mode is likely to require one to elevate the security level which this program is not set up to do (that being a proprietary process.)
+44 -10
View File
@@ -95,6 +95,7 @@ MainWindow::MainWindow(QWidget *parent) :
signalViewerWindow = nullptr;
temporalGraphWindow = nullptr;
dbcComparatorWindow = nullptr;
canBridgeWindow = nullptr;
dbcHandler = DBCHandler::getReference();
bDirty = false;
inhibitFilterUpdate = false;
@@ -136,6 +137,7 @@ MainWindow::MainWindow(QWidget *parent) :
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(ui->actionCAN_Bridge, &QAction::triggered, this, &MainWindow::showCANBridgeWindow);
//handlers fror interactions with the main can frame view table
connect(ui->canFramesView, &QAbstractItemView::clicked, this, &MainWindow::gridClicked);
@@ -262,8 +264,11 @@ void MainWindow::killEmAll()
killWindow(firmwareUploaderWindow);
killWindow(motorctrlConfigWindow);
killWindow(signalViewerWindow);
killWindow(connectionWindow);
killWindow(temporalGraphWindow);
killWindow(canBridgeWindow);
//trying to kill this window can cause a fault to happen. It's closed last just in case.
killWindow(connectionWindow);
}
//forcefully close the window, kill it, and salt the earth
@@ -382,12 +387,17 @@ void MainWindow::readUpdateableSettings()
useHex = settings.value("Main/UseHex", true).toBool();
model->setHexMode(useHex);
Utility::decimalMode = !useHex;
secondsMode = settings.value("Main/TimeSeconds", false).toBool();
model->setSecondsMode(secondsMode);
useSystemClock = settings.value("Main/TimeClock", false).toBool();
model->setSysTimeMode(useSystemClock);
millisMode = settings.value("Main/TimeMillis", false).toBool();
model->setMillisMode(millisMode);
bool tempBool;
TimeStyle ts = TS_MICROS;
tempBool = settings.value("Main/TimeSeconds", false).toBool();
if (tempBool) ts = TS_SECONDS;
tempBool = settings.value("Main/TimeClock", false).toBool();
if (tempBool) ts = TS_CLOCK;
tempBool = settings.value("Main/TimeMillis", false).toBool();
if (tempBool) ts = TS_MILLIS;
model->setTimeStyle(ts);
useFiltered = settings.value("Main/UseFiltered", false).toBool();
model->setTimeFormat(settings.value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString());
ignoreDBCColors = settings.value("Main/IgnoreDBCColors", false).toBool();
@@ -473,8 +483,21 @@ void MainWindow::expandAllRows()
void MainWindow::manageRowExpansion()
{
if(rowExpansionActive && model->getInterpretMode())
ui->canFramesView->resizeRowsToContents();
int numRows = ui->canFramesView->model()->rowCount();
if(numRows < 20000)
{
if(rowExpansionActive && model->getInterpretMode())
ui->canFramesView->resizeRowsToContents();
}
else
{
disableAutoRowExpansion();
}
}
void MainWindow::disableAutoRowExpansion()
{
rowExpansionActive = false;
}
void MainWindow::collapseAllRows()
@@ -575,7 +598,7 @@ void MainWindow::setupSendToLatestGraphWindow()
GraphParams param;
QString signalName = getSignalNameFromPosition(contextMenuPosition);
param.ID = getMessageIDFromPosition(contextMenuPosition);
DBC_MESSAGE *msg = dbcHandler->findMessageForFilter(param.ID, nullptr);
DBC_MESSAGE *msg = dbcHandler->findMessage(param.ID);
if(msg)
{
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(signalName);
@@ -869,6 +892,7 @@ void MainWindow::handleLoadFile()
if (loadResult)
{
disableAutoRowExpansion();
ui->canFramesView->scrollToTop();
model->clearFrames();
model->insertFrames(tempFrames);
@@ -912,6 +936,7 @@ void MainWindow::handleDroppedFile(const QString &filename)
if (loadResult)
{
disableAutoRowExpansion();
ui->canFramesView->scrollToTop();
model->clearFrames();
model->insertFrames(loadedFrames);
@@ -1427,6 +1452,15 @@ void MainWindow::showBisectWindow()
bisectWindow->show();
}
void MainWindow::showCANBridgeWindow()
{
if (!canBridgeWindow)
{
canBridgeWindow = new CANBridgeWindow(model->getListReference());
}
canBridgeWindow->show();
}
void MainWindow::showFrameSenderWindow()
{
if (!frameSenderWindow)
+4 -3
View File
@@ -33,6 +33,7 @@
#include "signalviewerwindow.h"
#include "re/temporalgraphwindow.h"
#include "re/dbccomparatorwindow.h"
#include "canbridgewindow.h"
class CANConnection;
class ConnectionWindow;
@@ -89,6 +90,7 @@ private slots:
void showSignalViewer();
void showTemporalGraphWindow();
void showDBCComparisonWindow();
void showCANBridgeWindow();
void exitApp();
void handleSaveDecoded();
void handleSaveDecodedCsv();
@@ -145,9 +147,6 @@ private:
bool inhibitFilterUpdate;
bool useHex;
bool allowCapture;
bool secondsMode;
bool millisMode;
bool useSystemClock;
bool ignoreDBCColors;
bool bDirty; //have frames been added or subtracted since the last save/load?
bool useFiltered; //should sub-windows use the unfiltered or filtered frames list?
@@ -183,6 +182,7 @@ private:
SignalViewerWindow *signalViewerWindow;
TemporalGraphWindow *temporalGraphWindow;
DBCComparatorWindow *dbcComparatorWindow;
CANBridgeWindow *canBridgeWindow;
//various private storage
QLabel lbStatusConnected;
@@ -209,6 +209,7 @@ private:
void writeSettings();
bool eventFilter(QObject *obj, QEvent *event);
void manageRowExpansion();
void disableAutoRowExpansion();
};
#endif // MAINWINDOW_H
+115 -87
View File
@@ -1,7 +1,7 @@
/***************************************************************************
** **
** QCustomPlot, an easy to use, modern plotting widget for Qt **
** Copyright (C) 2011-2021 Emanuel Eichhammer **
** Copyright (C) 2011-2022 Emanuel Eichhammer **
** **
** This program is free software: you can redistribute it and/or modify **
** it under the terms of the GNU General Public License as published by **
@@ -18,16 +18,16 @@
** **
****************************************************************************
** Author: Emanuel Eichhammer **
** Website/Contact: http://www.qcustomplot.com/ **
** Date: 29.03.21 **
** Version: 2.1.0 **
** Website/Contact: https://www.qcustomplot.com/ **
** Date: 06.11.22 **
** Version: 2.1.1 **
****************************************************************************/
#include "qcustomplot.h"
/* including file 'src/vector2d.cpp' */
/* modified 2021-03-29T02:30:44, size 7973 */
/* modified 2022-11-06T12:45:56, size 7973 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPVector2D
@@ -272,7 +272,7 @@ QCPVector2D &QCPVector2D::operator-=(const QCPVector2D &vector)
/* including file 'src/painter.cpp' */
/* modified 2021-03-29T02:30:44, size 8656 */
/* modified 2022-11-06T12:45:56, size 8656 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPPainter
@@ -489,7 +489,7 @@ void QCPPainter::makeNonCosmetic()
/* including file 'src/paintbuffer.cpp' */
/* modified 2021-03-29T02:30:44, size 18915 */
/* modified 2022-11-06T12:45:56, size 18915 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAbstractPaintBuffer
@@ -908,11 +908,6 @@ void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const
qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
return;
}
QOpenGLContext *cont = mGlContext.toStrongRef().data();
if (cont != QOpenGLContext::currentContext())
cont->makeCurrent(cont->surface());
painter->drawImage(0, 0, mGlFrameBuffer->toImage());
}
@@ -981,7 +976,7 @@ void QCPPaintBufferGlFbo::reallocateBuffer()
/* including file 'src/layer.cpp' */
/* modified 2021-03-29T02:30:44, size 37615 */
/* modified 2022-11-06T12:45:56, size 37615 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPLayer
@@ -1841,7 +1836,7 @@ void QCPLayerable::wheelEvent(QWheelEvent *event)
/* including file 'src/axis/range.cpp' */
/* modified 2021-03-29T02:30:44, size 12221 */
/* modified 2022-11-06T12:45:56, size 12221 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPRange
@@ -2163,7 +2158,7 @@ bool QCPRange::validRange(const QCPRange &range)
/* including file 'src/selection.cpp' */
/* modified 2021-03-29T02:30:44, size 21837 */
/* modified 2022-11-06T12:45:56, size 21837 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPDataRange
@@ -2764,7 +2759,7 @@ QCPDataSelection QCPDataSelection::inverse(const QCPDataRange &outerRange) const
/* including file 'src/selectionrect.cpp' */
/* modified 2021-03-29T02:30:44, size 9215 */
/* modified 2022-11-06T12:45:56, size 9215 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPSelectionRect
@@ -2993,7 +2988,7 @@ void QCPSelectionRect::draw(QCPPainter *painter)
/* including file 'src/layout.cpp' */
/* modified 2021-03-29T02:30:44, size 78863 */
/* modified 2022-11-06T12:45:56, size 78863 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPMarginGroup
@@ -5166,7 +5161,7 @@ void QCPLayoutInset::addElement(QCPLayoutElement *element, const QRectF &rect)
/* including file 'src/lineending.cpp' */
/* modified 2021-03-29T02:30:44, size 11189 */
/* modified 2022-11-06T12:45:56, size 11189 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPLineEnding
@@ -5460,7 +5455,7 @@ void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, double ang
/* including file 'src/axis/labelpainter.cpp' */
/* modified 2021-03-29T02:30:44, size 27296 */
/* modified 2022-11-06T12:45:56, size 27519 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -5658,8 +5653,8 @@ QByteArray QCPLabelPainterPrivate::generateLabelParameterHash() const
QByteArray result;
result.append(QByteArray::number(mParentPlot->bufferDevicePixelRatio()));
result.append(QByteArray::number(mRotation));
//result.append(QByteArray::number((int)tickLabelSide)); TODO: check whether this is really a cache-invalidating property
result.append(QByteArray::number((int)mSubstituteExponent));
//result.append(QByteArray::number(int(tickLabelSide))); TODO: check whether this is really a cache-invalidating property
result.append(QByteArray::number(int(mSubstituteExponent)));
result.append(QString(mMultiplicationSymbol).toUtf8());
result.append(mColor.name().toLatin1()+QByteArray::number(mColor.alpha(), 16));
result.append(mFont.toString().toLatin1());
@@ -5762,9 +5757,12 @@ QPointF QCPLabelPainterPrivate::getAnchorPos(const QPointF &tickPos)
case asTopRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, mPadding*M_SQRT1_2);
case asBottomRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2);
case asBottomLeft: return tickPos+QPointF(mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2);
default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor side: " << mAnchorSide; break;
}
break;
}
case amSkewedUpright:
// fall through
case amSkewedRotated:
{
QCPVector2D anchorNormal(tickPos-mAnchorReference);
@@ -5773,6 +5771,7 @@ QPointF QCPLabelPainterPrivate::getAnchorPos(const QPointF &tickPos)
anchorNormal.normalize();
return tickPos+(anchorNormal*mPadding).toPointF();
}
default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor mode: " << mAnchorMode; break;
}
return tickPos;
}
@@ -5990,8 +5989,8 @@ QByteArray QCPLabelPainterPrivate::cacheKey(const QString &text, const QColor &c
{
return text.toUtf8()+
QByteArray::number(color.red()+256*color.green()+65536*color.blue(), 36)+
QByteArray::number(color.alpha()+256*(int)side, 36)+
QByteArray::number((int)(rotation*100)%36000, 36);
QByteArray::number(color.alpha()+256*int(side), 36)+
QByteArray::number(int(rotation*100), 36);
}
QCPLabelPainterPrivate::AnchorSide QCPLabelPainterPrivate::skewedAnchorSide(const QPointF &tickPos, double sideExpandHorz, double sideExpandVert) const
@@ -6059,7 +6058,7 @@ void QCPLabelPainterPrivate::analyzeFontMetrics()
/* including file 'src/axis/axisticker.cpp' */
/* modified 2021-03-29T02:30:44, size 18688 */
/* modified 2022-11-06T12:45:56, size 18693 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAxisTicker
@@ -6443,7 +6442,7 @@ double QCPAxisTicker::pickClosest(double target, const QVector<double> &candidat
*/
double QCPAxisTicker::getMantissa(double input, double *magnitude) const
{
const double mag = qPow(10.0, qFloor(qLn(input)/qLn(10.0)));
const double mag = std::pow(10.0, std::floor(std::log10(input)));
if (magnitude) *magnitude = mag;
return input/mag;
}
@@ -6479,7 +6478,7 @@ double QCPAxisTicker::cleanMantissa(double input) const
/* including file 'src/axis/axistickerdatetime.cpp' */
/* modified 2021-03-29T02:30:44, size 18829 */
/* modified 2022-11-06T12:45:56, size 18829 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAxisTickerDateTime
@@ -6836,7 +6835,7 @@ double QCPAxisTickerDateTime::dateTimeToKey(const QDate &date, Qt::TimeSpec time
/* including file 'src/axis/axistickertime.cpp' */
/* modified 2021-03-29T02:30:44, size 11745 */
/* modified 2022-11-06T12:45:56, size 11745 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAxisTickerTime
@@ -7085,7 +7084,7 @@ void QCPAxisTickerTime::replaceUnit(QString &text, QCPAxisTickerTime::TimeUnit u
/* including file 'src/axis/axistickerfixed.cpp' */
/* modified 2021-03-29T02:30:44, size 5575 */
/* modified 2022-11-06T12:45:56, size 5575 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAxisTickerFixed
@@ -7187,7 +7186,7 @@ double QCPAxisTickerFixed::getTickStep(const QCPRange &range)
/* including file 'src/axis/axistickertext.cpp' */
/* modified 2021-03-29T02:30:44, size 8742 */
/* modified 2022-11-06T12:45:56, size 8742 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAxisTickerText
@@ -7404,7 +7403,7 @@ QVector<double> QCPAxisTickerText::createTickVector(double tickStep, const QCPRa
/* including file 'src/axis/axistickerpi.cpp' */
/* modified 2021-03-29T02:30:44, size 11177 */
/* modified 2022-11-06T12:45:56, size 11177 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAxisTickerPi
@@ -7691,7 +7690,7 @@ QString QCPAxisTickerPi::unicodeSubscript(int number) const
/* including file 'src/axis/axistickerlog.cpp' */
/* modified 2021-03-29T02:30:44, size 7890 */
/* modified 2022-11-06T12:45:56, size 7890 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAxisTickerLog
@@ -7832,7 +7831,7 @@ QVector<double> QCPAxisTickerLog::createTickVector(double tickStep, const QCPRan
/* including file 'src/axis/axis.cpp' */
/* modified 2021-03-29T02:30:44, size 99883 */
/* modified 2022-11-06T12:45:56, size 99911 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -10353,7 +10352,7 @@ QCPAxisPainterPrivate::TickLabelData QCPAxisPainterPrivate::getTickLabelData(con
int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart
if (substituteExponent)
{
ePos = text.indexOf(QLatin1Char('e'));
ePos = text.indexOf(QString(mParentPlot->locale().exponential()));
if (ePos > 0 && text.at(ePos-1).isDigit())
{
eLast = ePos;
@@ -10550,7 +10549,7 @@ void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString
/* including file 'src/scatterstyle.cpp' */
/* modified 2021-03-29T02:30:44, size 17466 */
/* modified 2022-11-06T12:45:56, size 17466 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPScatterStyle
@@ -11023,7 +11022,7 @@ void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const
/* including file 'src/plottable.cpp' */
/* modified 2021-03-29T02:30:44, size 38818 */
/* modified 2022-11-06T12:45:56, size 38818 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPSelectionDecorator
@@ -11994,7 +11993,7 @@ void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged)
/* including file 'src/item.cpp' */
/* modified 2021-03-29T02:30:44, size 49486 */
/* modified 2022-11-06T12:45:56, size 49486 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemAnchor
@@ -13266,7 +13265,7 @@ QCP::Interaction QCPAbstractItem::selectionCategory() const
/* including file 'src/core.cpp' */
/* modified 2021-03-29T02:30:44, size 127198 */
/* modified 2022-11-06T12:45:56, size 127625 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCustomPlot
@@ -13278,7 +13277,7 @@ QCP::Interaction QCPAbstractItem::selectionCategory() const
interacts with the user.
For tutorials on how to use QCustomPlot, see the website\n
http://www.qcustomplot.com/
https://www.qcustomplot.com/
*/
/* start of documentation of inline functions */
@@ -13602,7 +13601,7 @@ QCustomPlot::QCustomPlot(QWidget *parent) :
xAxis2(nullptr),
yAxis2(nullptr),
legend(nullptr),
mBufferDevicePixelRatio(1.0), // will be adapted to primary screen below
mBufferDevicePixelRatio(1.0), // will be adapted to true value below
mPlotLayout(nullptr),
mAutoAddPlottableToLegend(true),
mAntialiasedElements(QCP::aeNone),
@@ -13631,7 +13630,6 @@ QCustomPlot::QCustomPlot(QWidget *parent) :
mOpenGlCacheLabelsBackup(true)
{
setAttribute(Qt::WA_NoMousePropagation);
setAttribute(Qt::WA_OpaquePaintEvent);
setFocusPolicy(Qt::ClickFocus);
setMouseTracking(true);
QLocale currentLocale = locale();
@@ -15471,6 +15469,22 @@ QSize QCustomPlot::sizeHint() const
void QCustomPlot::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
// detect if the device pixel ratio has changed (e.g. moving window between different DPI screens), and adapt buffers if necessary:
#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
# ifdef QCP_DEVICEPIXELRATIO_FLOAT
double newDpr = devicePixelRatioF();
# else
double newDpr = devicePixelRatio();
# endif
if (!qFuzzyCompare(mBufferDevicePixelRatio, newDpr))
{
setBufferDevicePixelRatio(newDpr);
replot(QCustomPlot::rpQueuedRefresh);
return;
}
#endif
QCPPainter painter(this);
if (painter.isActive())
{
@@ -16485,7 +16499,7 @@ void QCustomPlot::toPainter(QCPPainter *painter, int width, int height)
/* including file 'src/colorgradient.cpp' */
/* modified 2021-03-29T02:30:44, size 25278 */
/* modified 2022-11-06T12:45:56, size 25408 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -16715,10 +16729,10 @@ void QCPColorGradient::colorize(const double *data, const QCPRange &range, QRgb
const double value = data[dataIndexFactor*i];
if (skipNanCheck || !std::isnan(value))
{
int index = int((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor);
qint64 index = qint64((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor);
if (!mPeriodic)
{
index = qBound(0, index, mLevelCount-1);
index = qBound(qint64(0), index, qint64(mLevelCount-1));
} else
{
index %= mLevelCount;
@@ -16776,10 +16790,10 @@ void QCPColorGradient::colorize(const double *data, const unsigned char *alpha,
const double value = data[dataIndexFactor*i];
if (skipNanCheck || !std::isnan(value))
{
int index = int((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor);
qint64 index = qint64((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor);
if (!mPeriodic)
{
index = qBound(0, index, mLevelCount-1);
index = qBound(qint64(0), index, qint64(mLevelCount-1));
} else
{
index %= mLevelCount;
@@ -17022,7 +17036,7 @@ void QCPColorGradient::updateColorBuffer()
for (int i=0; i<mLevelCount; ++i)
{
double position = i*indexToPosFactor;
QMap<double, QColor>::const_iterator it = mColorStops.lowerBound(position);
QMap<double, QColor>::const_iterator it = const_cast<const QMap<double, QColor>*>(&mColorStops)->lowerBound(position); // force using the const lowerBound method
if (it == mColorStops.constEnd()) // position is on or after last stop, use color of last stop
{
if (useAlpha)
@@ -17120,7 +17134,7 @@ void QCPColorGradient::updateColorBuffer()
/* including file 'src/selectiondecorator-bracket.cpp' */
/* modified 2021-03-29T02:30:44, size 12308 */
/* modified 2022-11-06T12:45:56, size 12308 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPSelectionDecoratorBracket
@@ -17406,7 +17420,7 @@ QPointF QCPSelectionDecoratorBracket::getPixelCoordinates(const QCPPlottableInte
/* including file 'src/layoutelements/layoutelement-axisrect.cpp' */
/* modified 2021-03-29T02:30:44, size 47193 */
/* modified 2022-11-06T12:45:56, size 47193 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -18691,7 +18705,7 @@ void QCPAxisRect::wheelEvent(QWheelEvent *event)
/* including file 'src/layoutelements/layoutelement-legend.cpp' */
/* modified 2021-03-29T02:30:44, size 31762 */
/* modified 2022-11-06T12:45:56, size 31762 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPAbstractLegendItem
@@ -19615,7 +19629,7 @@ void QCPLegend::parentPlotInitialized(QCustomPlot *parentPlot)
/* including file 'src/layoutelements/layoutelement-textelement.cpp' */
/* modified 2021-03-29T02:30:44, size 12925 */
/* modified 2022-11-06T12:45:56, size 12925 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPTextElement
@@ -20021,7 +20035,7 @@ QColor QCPTextElement::mainTextColor() const
/* including file 'src/layoutelements/layoutelement-colorscale.cpp' */
/* modified 2021-03-29T02:30:44, size 26531 */
/* modified 2022-11-06T12:45:56, size 26531 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -20705,7 +20719,7 @@ void QCPColorScaleAxisRectPrivate::axisSelectableChanged(QCPAxis::SelectablePart
/* including file 'src/plottables/plottable-graph.cpp' */
/* modified 2021-03-29T02:30:44, size 74518 */
/* modified 2022-11-06T12:45:57, size 74926 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPGraphData
@@ -21535,21 +21549,37 @@ QVector<QPointF> QCPGraph::dataToImpulseLines(const QVector<QCPGraphData> &data)
{
for (int i=0; i<data.size(); ++i)
{
const double key = keyAxis->coordToPixel(data.at(i).key);
result[i*2+0].setX(valueAxis->coordToPixel(0));
result[i*2+0].setY(key);
result[i*2+1].setX(valueAxis->coordToPixel(data.at(i).value));
result[i*2+1].setY(key);
const QCPGraphData &current = data.at(i);
if (!qIsNaN(current.value))
{
const double key = keyAxis->coordToPixel(current.key);
result[i*2+0].setX(valueAxis->coordToPixel(0));
result[i*2+0].setY(key);
result[i*2+1].setX(valueAxis->coordToPixel(current.value));
result[i*2+1].setY(key);
} else
{
result[i*2+0] = QPointF(0, 0);
result[i*2+1] = QPointF(0, 0);
}
}
} else // key axis is horizontal
{
for (int i=0; i<data.size(); ++i)
{
const double key = keyAxis->coordToPixel(data.at(i).key);
result[i*2+0].setX(key);
result[i*2+0].setY(valueAxis->coordToPixel(0));
result[i*2+1].setX(key);
result[i*2+1].setY(valueAxis->coordToPixel(data.at(i).value));
const QCPGraphData &current = data.at(i);
if (!qIsNaN(current.value))
{
const double key = keyAxis->coordToPixel(data.at(i).key);
result[i*2+0].setX(key);
result[i*2+0].setY(valueAxis->coordToPixel(0));
result[i*2+1].setX(key);
result[i*2+1].setY(valueAxis->coordToPixel(data.at(i).value));
} else
{
result[i*2+0] = QPointF(0, 0);
result[i*2+1] = QPointF(0, 0);
}
}
}
return result;
@@ -22463,7 +22493,7 @@ int QCPGraph::findIndexBelowY(const QVector<QPointF> *data, double y) const
/* including file 'src/plottables/plottable-curve.cpp' */
/* modified 2021-03-29T02:30:44, size 63851 */
/* modified 2022-11-06T12:45:56, size 63851 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPCurveData
@@ -23921,7 +23951,7 @@ double QCPCurve::pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer:
/* including file 'src/plottables/plottable-bars.cpp' */
/* modified 2021-03-29T02:30:44, size 43907 */
/* modified 2022-11-06T12:45:56, size 43907 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -25097,7 +25127,7 @@ void QCPBars::connectBars(QCPBars *lower, QCPBars *upper)
/* including file 'src/plottables/plottable-statisticalbox.cpp' */
/* modified 2021-03-29T02:30:44, size 28951 */
/* modified 2022-11-06T12:45:57, size 28951 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPStatisticalBoxData
@@ -25759,7 +25789,7 @@ QVector<QLineF> QCPStatisticalBox::getWhiskerBarLines(QCPStatisticalBoxDataConta
/* including file 'src/plottables/plottable-colormap.cpp' */
/* modified 2021-03-29T02:30:44, size 48149 */
/* modified 2022-11-06T12:45:56, size 48189 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPColorMapData
@@ -26129,8 +26159,8 @@ void QCPColorMapData::recalculateDataBounds()
{
if (mKeySize > 0 && mValueSize > 0)
{
double minHeight = mData[0];
double maxHeight = mData[0];
double minHeight = std::numeric_limits<double>::max();
double maxHeight = -std::numeric_limits<double>::max();
const int dataCount = mValueSize*mKeySize;
for (int i=0; i<dataCount; ++i)
{
@@ -26173,8 +26203,7 @@ void QCPColorMapData::clearAlpha()
void QCPColorMapData::fill(double z)
{
const int dataCount = mValueSize*mKeySize;
for (int i=0; i<dataCount; ++i)
mData[i] = z;
memset(mData, z, dataCount*sizeof(*mData));
mDataBounds = QCPRange(z, z);
mDataModified = true;
}
@@ -26193,8 +26222,7 @@ void QCPColorMapData::fillAlpha(unsigned char alpha)
if (mAlpha || createAlpha(false))
{
const int dataCount = mValueSize*mKeySize;
for (int i=0; i<dataCount; ++i)
mAlpha[i] = alpha;
memset(mAlpha, alpha, dataCount*sizeof(*mAlpha));
mDataModified = true;
}
}
@@ -26892,7 +26920,7 @@ void QCPColorMap::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
/* including file 'src/plottables/plottable-financial.cpp' */
/* modified 2021-03-29T02:30:44, size 42914 */
/* modified 2022-11-06T12:45:57, size 42914 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPFinancialData
@@ -27854,7 +27882,7 @@ QRectF QCPFinancial::selectionHitBox(QCPFinancialDataContainer::const_iterator i
/* including file 'src/plottables/plottable-errorbar.cpp' */
/* modified 2021-03-29T02:30:44, size 37679 */
/* modified 2022-11-06T12:45:56, size 37679 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPErrorBarsData
@@ -28822,7 +28850,7 @@ bool QCPErrorBars::rectIntersectsLine(const QRectF &pixelRect, const QLineF &lin
/* including file 'src/items/item-straightline.cpp' */
/* modified 2021-03-29T02:30:44, size 7596 */
/* modified 2022-11-06T12:45:56, size 7596 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemStraightLine
@@ -29003,7 +29031,7 @@ QPen QCPItemStraightLine::mainPen() const
/* including file 'src/items/item-line.cpp' */
/* modified 2021-03-29T02:30:44, size 8525 */
/* modified 2022-11-06T12:45:56, size 8525 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemLine
@@ -29234,7 +29262,7 @@ QPen QCPItemLine::mainPen() const
/* including file 'src/items/item-curve.cpp' */
/* modified 2021-03-29T02:30:44, size 7273 */
/* modified 2022-11-06T12:45:56, size 7273 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemCurve
@@ -29403,7 +29431,7 @@ QPen QCPItemCurve::mainPen() const
/* including file 'src/items/item-rect.cpp' */
/* modified 2021-03-29T02:30:44, size 6472 */
/* modified 2022-11-06T12:45:56, size 6472 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemRect
@@ -29560,7 +29588,7 @@ QBrush QCPItemRect::mainBrush() const
/* including file 'src/items/item-text.cpp' */
/* modified 2021-03-29T02:30:44, size 13335 */
/* modified 2022-11-06T12:45:56, size 13335 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemText
@@ -29908,7 +29936,7 @@ QBrush QCPItemText::mainBrush() const
/* including file 'src/items/item-ellipse.cpp' */
/* modified 2021-03-29T02:30:44, size 7881 */
/* modified 2022-11-06T12:45:56, size 7881 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemEllipse
@@ -30097,7 +30125,7 @@ QBrush QCPItemEllipse::mainBrush() const
/* including file 'src/items/item-pixmap.cpp' */
/* modified 2021-03-29T02:30:44, size 10622 */
/* modified 2022-11-06T12:45:56, size 10622 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemPixmap
@@ -30367,7 +30395,7 @@ QPen QCPItemPixmap::mainPen() const
/* including file 'src/items/item-tracer.cpp' */
/* modified 2021-03-29T02:30:44, size 14645 */
/* modified 2022-11-06T12:45:56, size 14645 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemTracer
@@ -30737,7 +30765,7 @@ QBrush QCPItemTracer::mainBrush() const
/* including file 'src/items/item-bracket.cpp' */
/* modified 2021-03-29T02:30:44, size 10705 */
/* modified 2022-11-06T12:45:56, size 10705 */
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPItemBracket
@@ -30978,7 +31006,7 @@ QPen QCPItemBracket::mainPen() const
/* including file 'src/polar/radialaxis.cpp' */
/* modified 2021-03-29T02:30:44, size 49415 */
/* modified 2022-11-06T12:45:57, size 49415 */
@@ -32439,7 +32467,7 @@ QCP::Interaction QCPPolarAxisRadial::selectionCategory() const
/* including file 'src/polar/layoutelement-angularaxis.cpp' */
/* modified 2021-03-29T02:30:44, size 57266 */
/* modified 2022-11-06T12:45:57, size 57266 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34130,7 +34158,7 @@ bool QCPPolarAxisAngular::registerPolarGraph(QCPPolarGraph *graph)
/* including file 'src/polar/polargrid.cpp' */
/* modified 2021-03-29T02:30:44, size 7493 */
/* modified 2022-11-06T12:45:57, size 7493 */
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34318,7 +34346,7 @@ void QCPPolarGrid::drawAngularGrid(QCPPainter *painter, const QPointF &center, d
/* including file 'src/polar/polargraph.cpp' */
/* modified 2021-03-29T02:30:44, size 44035 */
/* modified 2022-11-06T12:45:57, size 44035 */
////////////////////////////////////////////////////////////////////////////////////////////////////
+129 -80
View File
@@ -1,7 +1,7 @@
/***************************************************************************
** **
** QCustomPlot, an easy to use, modern plotting widget for Qt **
** Copyright (C) 2011-2021 Emanuel Eichhammer **
** Copyright (C) 2011-2022 Emanuel Eichhammer **
** **
** This program is free software: you can redistribute it and/or modify **
** it under the terms of the GNU General Public License as published by **
@@ -18,9 +18,9 @@
** **
****************************************************************************
** Author: Emanuel Eichhammer **
** Website/Contact: http://www.qcustomplot.com/ **
** Date: 29.03.21 **
** Version: 2.1.0 **
** Website/Contact: https://www.qcustomplot.com/ **
** Date: 06.11.22 **
** Version: 2.1.1 **
****************************************************************************/
#ifndef QCUSTOMPLOT_H
@@ -129,10 +129,10 @@ class QCPPolarGrid;
class QCPPolarGraph;
/* including file 'src/global.h' */
/* modified 2021-03-29T02:30:44, size 16981 */
/* modified 2022-11-06T12:45:57, size 18102 */
#define QCUSTOMPLOT_VERSION_STR "2.1.0"
#define QCUSTOMPLOT_VERSION 0x020100
#define QCUSTOMPLOT_VERSION_STR "2.1.1"
#define QCUSTOMPLOT_VERSION 0x020101
// decl definitions for shared library compilation/usage:
#if defined(QT_STATIC_BUILD)
@@ -156,8 +156,35 @@ class QCPPolarGraph;
It provides QMetaObject-based reflection of its enums and flags via \a QCP::staticMetaObject.
*/
// Qt version < 6.2.0: to get metatypes Q_GADGET/Q_ENUMS/Q_FLAGS in namespace we have to make it look like a class during moc-run
#if QT_VERSION >= 0x060200 // don't use QT_VERSION_CHECK here, some moc versions don't understand it
namespace QCP {
Q_NAMESPACE
Q_NAMESPACE // this is how to add the staticMetaObject to namespaces in newer Qt versions
#else // Qt version older than 6.2.0
# ifndef Q_MOC_RUN
namespace QCP {
# else // not in moc run
class QCP {
Q_GADGET
Q_ENUMS(ExportPen)
Q_ENUMS(ResolutionUnit)
Q_ENUMS(SignDomain)
Q_ENUMS(MarginSide)
Q_ENUMS(AntialiasedElement)
Q_ENUMS(PlottingHint)
Q_ENUMS(Interaction)
Q_ENUMS(SelectionRectMode)
Q_ENUMS(SelectionType)
Q_FLAGS(AntialiasedElements)
Q_FLAGS(PlottingHints)
Q_FLAGS(MarginSides)
Q_FLAGS(Interactions)
public:
# endif
#endif
/*!
Defines the different units in which the image resolution can be specified in the export
@@ -300,20 +327,6 @@ enum SelectionType { stNone ///< The plottable is not selectable
,stMultipleDataRanges ///< Any combination of data points/ranges can be selected
};
Q_ENUM_NS(ExportPen)
Q_ENUM_NS(ResolutionUnit)
Q_ENUM_NS(SignDomain)
Q_ENUM_NS(MarginSide)
Q_FLAG_NS(MarginSides)
Q_ENUM_NS(AntialiasedElement)
Q_FLAG_NS(AntialiasedElements)
Q_ENUM_NS(PlottingHint)
Q_FLAG_NS(PlottingHints)
Q_ENUM_NS(Interaction)
Q_FLAG_NS(Interactions)
Q_ENUM_NS(SelectionRectMode)
Q_ENUM_NS(SelectionType)
/*! \internal
Returns whether the specified \a value is considered an invalid data value for plottables (i.e.
@@ -374,21 +387,55 @@ inline int getMarginValue(const QMargins &margins, QCP::MarginSide side)
return 0;
}
// for newer Qt versions we have to declare the enums/flags as metatypes inside the namespace using Q_ENUM_NS/Q_FLAG_NS:
// if you change anything here, don't forget to change it for older Qt versions below, too,
// and at the start of the namespace in the fake moc-run class
#if QT_VERSION >= 0x060200
Q_ENUM_NS(ExportPen)
Q_ENUM_NS(ResolutionUnit)
Q_ENUM_NS(SignDomain)
Q_ENUM_NS(MarginSide)
Q_ENUM_NS(AntialiasedElement)
Q_ENUM_NS(PlottingHint)
Q_ENUM_NS(Interaction)
Q_ENUM_NS(SelectionRectMode)
Q_ENUM_NS(SelectionType)
extern const QMetaObject staticMetaObject; // in moc-run we create a static meta object for QCP "fake" object. This line is the link to it via QCP::staticMetaObject in normal operation as namespace
Q_FLAG_NS(AntialiasedElements)
Q_FLAG_NS(PlottingHints)
Q_FLAG_NS(MarginSides)
Q_FLAG_NS(Interactions)
#else
extern const QMetaObject staticMetaObject;
#endif
} // end of namespace QCP
Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::AntialiasedElements)
Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::PlottingHints)
Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::MarginSides)
Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::Interactions)
//no need to use Q_DECLARE_METATYPE on enum since Q_ENUM_NS adds enum as metatype automatically
// for older Qt versions we have to declare the enums/flags as metatypes outside the namespace using Q_DECLARE_METATYPE:
// if you change anything here, don't forget to change it for newer Qt versions above, too,
// and at the start of the namespace in the fake moc-run class
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
Q_DECLARE_METATYPE(QCP::ExportPen)
Q_DECLARE_METATYPE(QCP::ResolutionUnit)
Q_DECLARE_METATYPE(QCP::SignDomain)
Q_DECLARE_METATYPE(QCP::MarginSide)
Q_DECLARE_METATYPE(QCP::AntialiasedElement)
Q_DECLARE_METATYPE(QCP::PlottingHint)
Q_DECLARE_METATYPE(QCP::Interaction)
Q_DECLARE_METATYPE(QCP::SelectionRectMode)
Q_DECLARE_METATYPE(QCP::SelectionType)
#endif
/* end of 'src/global.h' */
/* including file 'src/vector2d.h' */
/* modified 2021-03-29T02:30:44, size 4988 */
/* modified 2022-11-06T12:45:56, size 4988 */
class QCP_LIB_DECL QCPVector2D
{
@@ -463,7 +510,7 @@ inline QDebug operator<< (QDebug d, const QCPVector2D &vec)
/* including file 'src/painter.h' */
/* modified 2021-03-29T02:30:44, size 4035 */
/* modified 2022-11-06T12:45:56, size 4035 */
class QCP_LIB_DECL QCPPainter : public QPainter
{
@@ -522,7 +569,7 @@ Q_DECLARE_METATYPE(QCPPainter::PainterMode)
/* including file 'src/paintbuffer.h' */
/* modified 2021-03-29T02:30:44, size 5006 */
/* modified 2022-11-06T12:45:56, size 5006 */
class QCP_LIB_DECL QCPAbstractPaintBuffer
{
@@ -630,7 +677,7 @@ protected:
/* including file 'src/layer.h' */
/* modified 2021-03-29T02:30:44, size 7038 */
/* modified 2022-11-06T12:45:56, size 7038 */
class QCP_LIB_DECL QCPLayer : public QObject
{
@@ -779,7 +826,7 @@ private:
/* including file 'src/axis/range.h' */
/* modified 2021-03-29T02:30:44, size 5280 */
/* modified 2022-11-06T12:45:56, size 5280 */
class QCP_LIB_DECL QCPRange
{
@@ -897,7 +944,7 @@ inline const QCPRange operator/(const QCPRange& range, double value)
/* including file 'src/selection.h' */
/* modified 2021-03-29T02:30:44, size 8569 */
/* modified 2022-11-06T12:45:56, size 8569 */
class QCP_LIB_DECL QCPDataRange
{
@@ -1101,7 +1148,7 @@ inline QDebug operator<< (QDebug d, const QCPDataSelection &selection)
/* including file 'src/selectionrect.h' */
/* modified 2021-03-29T02:30:44, size 3354 */
/* modified 2022-11-06T12:45:56, size 3354 */
class QCP_LIB_DECL QCPSelectionRect : public QCPLayerable
{
@@ -1155,7 +1202,7 @@ protected:
/* including file 'src/layout.h' */
/* modified 2021-03-29T02:30:44, size 14279 */
/* modified 2022-11-06T12:45:56, size 14279 */
class QCP_LIB_DECL QCPMarginGroup : public QObject
{
@@ -1476,7 +1523,7 @@ Q_DECLARE_METATYPE(QCPLayoutInset::InsetPlacement)
/* including file 'src/lineending.h' */
/* modified 2021-03-29T02:30:44, size 4426 */
/* modified 2022-11-06T12:45:56, size 4426 */
class QCP_LIB_DECL QCPLineEnding
{
@@ -1540,7 +1587,7 @@ Q_DECLARE_METATYPE(QCPLineEnding::EndingStyle)
/* including file 'src/axis/labelpainter.h' */
/* modified 2021-03-29T02:30:44, size 7086 */
/* modified 2022-11-06T12:45:56, size 7086 */
class QCPLabelPainterPrivate
{
@@ -1680,7 +1727,7 @@ Q_DECLARE_METATYPE(QCPLabelPainterPrivate::AnchorSide)
/* including file 'src/axis/axisticker.h' */
/* modified 2021-03-29T02:30:44, size 4230 */
/* modified 2022-11-06T12:45:56, size 4230 */
class QCP_LIB_DECL QCPAxisTicker
{
@@ -1745,7 +1792,7 @@ Q_DECLARE_METATYPE(QSharedPointer<QCPAxisTicker>)
/* including file 'src/axis/axistickerdatetime.h' */
/* modified 2021-03-29T02:30:44, size 3600 */
/* modified 2022-11-06T12:45:56, size 3600 */
class QCP_LIB_DECL QCPAxisTickerDateTime : public QCPAxisTicker
{
@@ -1794,7 +1841,7 @@ protected:
/* including file 'src/axis/axistickertime.h' */
/* modified 2021-03-29T02:30:44, size 3542 */
/* modified 2022-11-06T12:45:56, size 3542 */
class QCP_LIB_DECL QCPAxisTickerTime : public QCPAxisTicker
{
@@ -1846,7 +1893,7 @@ Q_DECLARE_METATYPE(QCPAxisTickerTime::TimeUnit)
/* including file 'src/axis/axistickerfixed.h' */
/* modified 2021-03-29T02:30:44, size 3308 */
/* modified 2022-11-06T12:45:56, size 3308 */
class QCP_LIB_DECL QCPAxisTickerFixed : public QCPAxisTicker
{
@@ -1888,7 +1935,7 @@ Q_DECLARE_METATYPE(QCPAxisTickerFixed::ScaleStrategy)
/* including file 'src/axis/axistickertext.h' */
/* modified 2021-03-29T02:30:44, size 3090 */
/* modified 2022-11-06T12:45:56, size 3090 */
class QCP_LIB_DECL QCPAxisTickerText : public QCPAxisTicker
{
@@ -1926,7 +1973,7 @@ protected:
/* including file 'src/axis/axistickerpi.h' */
/* modified 2021-03-29T02:30:44, size 3911 */
/* modified 2022-11-06T12:45:56, size 3911 */
class QCP_LIB_DECL QCPAxisTickerPi : public QCPAxisTicker
{
@@ -1985,7 +2032,7 @@ Q_DECLARE_METATYPE(QCPAxisTickerPi::FractionStyle)
/* including file 'src/axis/axistickerlog.h' */
/* modified 2021-03-29T02:30:44, size 2594 */
/* modified 2022-11-06T12:45:56, size 2594 */
class QCP_LIB_DECL QCPAxisTickerLog : public QCPAxisTicker
{
@@ -2017,7 +2064,7 @@ protected:
/* including file 'src/axis/axis.h' */
/* modified 2021-03-29T02:30:44, size 20913 */
/* modified 2022-11-06T12:45:56, size 20913 */
class QCP_LIB_DECL QCPGrid :public QCPLayerable
{
@@ -2445,7 +2492,7 @@ protected:
/* including file 'src/scatterstyle.h' */
/* modified 2021-03-29T02:30:44, size 7275 */
/* modified 2022-11-06T12:45:56, size 7275 */
class QCP_LIB_DECL QCPScatterStyle
{
@@ -2552,7 +2599,7 @@ Q_DECLARE_METATYPE(QCPScatterStyle::ScatterShape)
/* including file 'src/datacontainer.h' */
/* modified 2021-03-29T02:30:44, size 34070 */
/* modified 2022-11-06T12:45:56, size 34305 */
/*! \relates QCPDataContainer
Returns whether the sort key of \a a is less than the sort key of \a b.
@@ -3221,6 +3268,8 @@ QCPRange QCPDataContainer<DataType>::keyRange(bool &foundRange, QCP::SignDomain
output parameter \a foundRange indicates whether a sensible range was found. If this is false,
you should not use the returned QCPRange (e.g. the data container is empty or all points have the
same value).
Inf and -Inf data values are ignored.
If \a inKeyRange has both lower and upper bound set to zero (is equal to <tt>QCPRange()</tt>),
all data points are considered, without any restriction on the keys.
@@ -3258,12 +3307,12 @@ QCPRange QCPDataContainer<DataType>::valueRange(bool &foundRange, QCP::SignDomai
if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper))
continue;
current = it->valueRange();
if ((current.lower < range.lower || !haveLower) && !qIsNaN(current.lower))
if ((current.lower < range.lower || !haveLower) && !qIsNaN(current.lower) && std::isfinite(current.lower))
{
range.lower = current.lower;
haveLower = true;
}
if ((current.upper > range.upper || !haveUpper) && !qIsNaN(current.upper))
if ((current.upper > range.upper || !haveUpper) && !qIsNaN(current.upper) && std::isfinite(current.upper))
{
range.upper = current.upper;
haveUpper = true;
@@ -3276,12 +3325,12 @@ QCPRange QCPDataContainer<DataType>::valueRange(bool &foundRange, QCP::SignDomai
if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper))
continue;
current = it->valueRange();
if ((current.lower < range.lower || !haveLower) && current.lower < 0 && !qIsNaN(current.lower))
if ((current.lower < range.lower || !haveLower) && current.lower < 0 && !qIsNaN(current.lower) && std::isfinite(current.lower))
{
range.lower = current.lower;
haveLower = true;
}
if ((current.upper > range.upper || !haveUpper) && current.upper < 0 && !qIsNaN(current.upper))
if ((current.upper > range.upper || !haveUpper) && current.upper < 0 && !qIsNaN(current.upper) && std::isfinite(current.upper))
{
range.upper = current.upper;
haveUpper = true;
@@ -3294,12 +3343,12 @@ QCPRange QCPDataContainer<DataType>::valueRange(bool &foundRange, QCP::SignDomai
if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper))
continue;
current = it->valueRange();
if ((current.lower < range.lower || !haveLower) && current.lower > 0 && !qIsNaN(current.lower))
if ((current.lower < range.lower || !haveLower) && current.lower > 0 && !qIsNaN(current.lower) && std::isfinite(current.lower))
{
range.lower = current.lower;
haveLower = true;
}
if ((current.upper > range.upper || !haveUpper) && current.upper > 0 && !qIsNaN(current.upper))
if ((current.upper > range.upper || !haveUpper) && current.upper > 0 && !qIsNaN(current.upper) && std::isfinite(current.upper))
{
range.upper = current.upper;
haveUpper = true;
@@ -3394,7 +3443,7 @@ void QCPDataContainer<DataType>::performAutoSqueeze()
/* including file 'src/plottable.h' */
/* modified 2021-03-29T02:30:44, size 8461 */
/* modified 2022-11-06T12:45:56, size 8461 */
class QCP_LIB_DECL QCPSelectionDecorator
{
@@ -3551,7 +3600,7 @@ private:
/* including file 'src/item.h' */
/* modified 2021-03-29T02:30:44, size 9425 */
/* modified 2022-11-06T12:45:56, size 9425 */
class QCP_LIB_DECL QCPItemAnchor
{
@@ -3736,7 +3785,7 @@ private:
/* including file 'src/core.h' */
/* modified 2021-03-29T02:30:44, size 19304 */
/* modified 2022-11-06T12:45:56, size 19304 */
class QCP_LIB_DECL QCustomPlot : public QWidget
{
@@ -4106,7 +4155,7 @@ ItemType *QCustomPlot::itemAt(const QPointF &pos, bool onlySelectable) const
/* including file 'src/plottable1d.h' */
/* modified 2021-03-29T02:30:44, size 25638 */
/* modified 2022-11-06T12:45:56, size 25638 */
class QCPPlottableInterface1D
{
@@ -4698,7 +4747,7 @@ void QCPAbstractPlottable1D<DataType>::drawPolyline(QCPPainter *painter, const Q
/* including file 'src/colorgradient.h' */
/* modified 2021-03-29T02:30:44, size 7262 */
/* modified 2022-11-06T12:45:56, size 7262 */
class QCP_LIB_DECL QCPColorGradient
{
@@ -4801,7 +4850,7 @@ Q_DECLARE_METATYPE(QCPColorGradient::GradientPreset)
/* including file 'src/selectiondecorator-bracket.h' */
/* modified 2021-03-29T02:30:44, size 4458 */
/* modified 2022-11-06T12:45:56, size 4458 */
class QCP_LIB_DECL QCPSelectionDecoratorBracket : public QCPSelectionDecorator
{
@@ -4870,7 +4919,7 @@ Q_DECLARE_METATYPE(QCPSelectionDecoratorBracket::BracketStyle)
/* including file 'src/layoutelements/layoutelement-axisrect.h' */
/* modified 2021-03-29T02:30:44, size 7529 */
/* modified 2022-11-06T12:45:56, size 7529 */
class QCP_LIB_DECL QCPAxisRect : public QCPLayoutElement
{
@@ -4996,7 +5045,7 @@ private:
/* including file 'src/layoutelements/layoutelement-legend.h' */
/* modified 2021-03-29T02:30:44, size 10425 */
/* modified 2022-11-06T12:45:56, size 10425 */
class QCP_LIB_DECL QCPAbstractLegendItem : public QCPLayoutElement
{
@@ -5214,7 +5263,7 @@ Q_DECLARE_METATYPE(QCPLegend::SelectablePart)
/* including file 'src/layoutelements/layoutelement-textelement.h' */
/* modified 2021-03-29T02:30:44, size 5359 */
/* modified 2022-11-06T12:45:56, size 5359 */
class QCP_LIB_DECL QCPTextElement : public QCPLayoutElement
{
@@ -5301,7 +5350,7 @@ private:
/* including file 'src/layoutelements/layoutelement-colorscale.h' */
/* modified 2021-03-29T02:30:44, size 5939 */
/* modified 2022-11-06T12:45:56, size 5939 */
class QCPColorScaleAxisRectPrivate : public QCPAxisRect
@@ -5409,7 +5458,7 @@ private:
/* including file 'src/plottables/plottable-graph.h' */
/* modified 2021-03-29T02:30:44, size 9316 */
/* modified 2022-11-06T12:45:56, size 9316 */
class QCP_LIB_DECL QCPGraphData
{
@@ -5548,7 +5597,7 @@ Q_DECLARE_METATYPE(QCPGraph::LineStyle)
/* including file 'src/plottables/plottable-curve.h' */
/* modified 2021-03-29T02:30:44, size 7434 */
/* modified 2022-11-06T12:45:56, size 7434 */
class QCP_LIB_DECL QCPCurveData
{
@@ -5663,7 +5712,7 @@ Q_DECLARE_METATYPE(QCPCurve::LineStyle)
/* including file 'src/plottables/plottable-bars.h' */
/* modified 2021-03-29T02:30:44, size 8955 */
/* modified 2022-11-06T12:45:56, size 8955 */
class QCP_LIB_DECL QCPBarsGroup : public QObject
{
@@ -5851,7 +5900,7 @@ Q_DECLARE_METATYPE(QCPBars::WidthType)
/* including file 'src/plottables/plottable-statisticalbox.h' */
/* modified 2021-03-29T02:30:44, size 7522 */
/* modified 2022-11-06T12:45:56, size 7522 */
class QCP_LIB_DECL QCPStatisticalBoxData
{
@@ -5968,7 +6017,7 @@ protected:
/* including file 'src/plottables/plottable-colormap.h' */
/* modified 2021-03-29T02:30:44, size 7092 */
/* modified 2022-11-06T12:45:56, size 7092 */
class QCP_LIB_DECL QCPColorMapData
{
@@ -6104,7 +6153,7 @@ protected:
/* including file 'src/plottables/plottable-financial.h' */
/* modified 2021-03-29T02:30:44, size 8644 */
/* modified 2022-11-06T12:45:56, size 8644 */
class QCP_LIB_DECL QCPFinancialData
{
@@ -6243,7 +6292,7 @@ Q_DECLARE_METATYPE(QCPFinancial::ChartStyle)
/* including file 'src/plottables/plottable-errorbar.h' */
/* modified 2021-03-29T02:30:44, size 7749 */
/* modified 2022-11-06T12:45:56, size 7749 */
class QCP_LIB_DECL QCPErrorBarsData
{
@@ -6368,7 +6417,7 @@ protected:
/* including file 'src/items/item-straightline.h' */
/* modified 2021-03-29T02:30:44, size 3137 */
/* modified 2022-11-06T12:45:56, size 3137 */
class QCP_LIB_DECL QCPItemStraightLine : public QCPAbstractItem
{
@@ -6411,7 +6460,7 @@ protected:
/* including file 'src/items/item-line.h' */
/* modified 2021-03-29T02:30:44, size 3429 */
/* modified 2022-11-06T12:45:56, size 3429 */
class QCP_LIB_DECL QCPItemLine : public QCPAbstractItem
{
@@ -6461,7 +6510,7 @@ protected:
/* including file 'src/items/item-curve.h' */
/* modified 2021-03-29T02:30:44, size 3401 */
/* modified 2022-11-06T12:45:56, size 3401 */
class QCP_LIB_DECL QCPItemCurve : public QCPAbstractItem
{
@@ -6512,7 +6561,7 @@ protected:
/* including file 'src/items/item-rect.h' */
/* modified 2021-03-29T02:30:44, size 3710 */
/* modified 2022-11-06T12:45:56, size 3710 */
class QCP_LIB_DECL QCPItemRect : public QCPAbstractItem
{
@@ -6571,7 +6620,7 @@ protected:
/* including file 'src/items/item-text.h' */
/* modified 2021-03-29T02:30:44, size 5576 */
/* modified 2022-11-06T12:45:56, size 5576 */
class QCP_LIB_DECL QCPItemText : public QCPAbstractItem
{
@@ -6668,7 +6717,7 @@ protected:
/* including file 'src/items/item-ellipse.h' */
/* modified 2021-03-29T02:30:44, size 3890 */
/* modified 2022-11-06T12:45:56, size 3890 */
class QCP_LIB_DECL QCPItemEllipse : public QCPAbstractItem
{
@@ -6730,7 +6779,7 @@ protected:
/* including file 'src/items/item-pixmap.h' */
/* modified 2021-03-29T02:30:44, size 4407 */
/* modified 2022-11-06T12:45:56, size 4407 */
class QCP_LIB_DECL QCPItemPixmap : public QCPAbstractItem
{
@@ -6799,7 +6848,7 @@ protected:
/* including file 'src/items/item-tracer.h' */
/* modified 2021-03-29T02:30:44, size 4811 */
/* modified 2022-11-06T12:45:56, size 4811 */
class QCP_LIB_DECL QCPItemTracer : public QCPAbstractItem
{
@@ -6885,7 +6934,7 @@ Q_DECLARE_METATYPE(QCPItemTracer::TracerStyle)
/* including file 'src/items/item-bracket.h' */
/* modified 2021-03-29T02:30:44, size 3991 */
/* modified 2022-11-06T12:45:56, size 3991 */
class QCP_LIB_DECL QCPItemBracket : public QCPAbstractItem
{
@@ -6952,7 +7001,7 @@ Q_DECLARE_METATYPE(QCPItemBracket::BracketStyle)
/* including file 'src/polar/radialaxis.h' */
/* modified 2021-03-29T02:30:44, size 12227 */
/* modified 2022-11-06T12:45:56, size 12227 */
class QCP_LIB_DECL QCPPolarAxisRadial : public QCPLayerable
@@ -7204,7 +7253,7 @@ Q_DECLARE_METATYPE(QCPPolarAxisRadial::SelectablePart)
/* including file 'src/polar/layoutelement-angularaxis.h' */
/* modified 2021-03-29T02:30:44, size 13461 */
/* modified 2022-11-06T12:45:56, size 13461 */
class QCP_LIB_DECL QCPPolarAxisAngular : public QCPLayoutElement
{
@@ -7477,7 +7526,7 @@ Q_DECLARE_METATYPE(QCPPolarAxisAngular::SelectablePart)
/* including file 'src/polar/polargrid.h' */
/* modified 2021-03-29T02:30:44, size 4506 */
/* modified 2022-11-06T12:45:56, size 4506 */
class QCP_LIB_DECL QCPPolarGrid :public QCPLayerable
{
@@ -7557,7 +7606,7 @@ Q_DECLARE_METATYPE(QCPPolarGrid::GridType)
/* including file 'src/polar/polargraph.h' */
/* modified 2021-03-29T02:30:44, size 9606 */
/* modified 2022-11-06T12:45:56, size 9606 */
class QCP_LIB_DECL QCPPolarLegendItem : public QCPAbstractLegendItem
+3 -1
View File
@@ -6,7 +6,7 @@
#include "qcpaxistickerhex.h"
const QColor FlowViewWindow::graphColors[8] = {Qt::blue, Qt::green, Qt::black, Qt::red, //0 1 2 3
Qt::gray, Qt::yellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7
Qt::gray, Qt::darkYellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7
FlowViewWindow::FlowViewWindow(const QVector<CANFrame> *frames, QWidget *parent) :
@@ -86,6 +86,8 @@ FlowViewWindow::FlowViewWindow(const QVector<CANFrame> *frames, QWidget *parent)
ui->graphView->setAntialiasedElements(QCP::aeNone);
}
ui->graphView->setBufferDevicePixelRatio(1);
connect(ui->btnBackOne, SIGNAL(clicked(bool)), this, SLOT(btnBackOneClick()));
connect(ui->btnPause, SIGNAL(clicked(bool)), this, SLOT(btnPauseClick()));
connect(ui->btnReverse, SIGNAL(clicked(bool)), this, SLOT(btnReverseClick()));
+219 -99
View File
@@ -8,7 +8,7 @@
#include "qcpaxistickerhex.h"
const QColor FrameInfoWindow::byteGraphColors[8] = {Qt::blue, Qt::green, Qt::black, Qt::red, //0 1 2 3
Qt::gray, Qt::yellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7
Qt::gray, Qt::darkYellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7
QPen FrameInfoWindow::bytePens[8];
const int numIntervalHistBars = 20;
@@ -34,85 +34,78 @@ FrameInfoWindow::FrameInfoWindow(const QVector<CANFrame> *frames, QWidget *paren
connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &FrameInfoWindow::updatedFrames);
connect(ui->btnSave, &QAbstractButton::clicked, this, &FrameInfoWindow::saveDetails);
connect(ui->check_0, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
connect(ui->check_1, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
connect(ui->check_2, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
connect(ui->check_3, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
connect(ui->check_4, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
connect(ui->check_5, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
connect(ui->check_6, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
connect(ui->check_7, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility);
ui->splitter->setStretchFactor(0, 1); //idx, stretch factor
ui->splitter->setStretchFactor(1, 4); //goal is to make right hand side larger by default
ui->graphHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
QCP::iSelectLegend | QCP::iSelectPlottables);
ui->graphHistogram->xAxis->setRange(0, 63);
ui->graphHistogram->yAxis->setRange(0, 100);
QSharedPointer<QCPAxisTickerLog> graphHistoLogTicker(new QCPAxisTickerLog);
ui->graphHistogram->yAxis->setTicker(graphHistoLogTicker);
ui->graphHistogram->yAxis2->setTicker(graphHistoLogTicker);
ui->graphHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers
ui->graphHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision
ui->graphHistogram->axisRect()->setupFullAxesBox();
ui->graphHistogram->xAxis->setLabel("Bits");
ui->graphHistogram->yAxis->setLabel("Instances");
ui->graphHistogram->legend->setVisible(false);
ui->graphBytes->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
QCP::iSelectLegend | QCP::iSelectPlottables);
ui->graphBytes->xAxis->setRange(0, 63);
ui->graphBytes->yAxis->setRange(0, 265);
if (useHexTicker)
for (int i = 0; i < 8; i++)
{
QSharedPointer<QCPAxisTickerHex> hexTicker(new QCPAxisTickerHex);
ui->graphBytes->yAxis->setTicker(hexTicker);
graphByte[i] = new QCustomPlot();
setupByteGraph(graphByte[i], i);
ui->gridLower->addWidget(graphByte[i], i / 4, i & 3);
}
ui->graphBytes->axisRect()->setupFullAxesBox();
ui->graphBytes->xAxis->setLabel("Time");
if (useHexTicker) ui->graphBytes->yAxis->setLabel("Value (HEX)");
else ui->graphBytes->yAxis->setLabel("Value (Dec)");
ui->gridUpper->addWidget(new QLabel("Heatmap"), 0, 0);
heatmap = new CANDataGrid();
heatmap->setMode(GridMode::HEAT_VIEW);
ui->gridUpper->addWidget(heatmap, 1, 0);
ui->graphBytes->legend->setVisible(false);
ui->gridUpper->addWidget(new QLabel("Bit Histogram"), 0, 1);
graphHistogram = new QCustomPlot();
ui->gridUpper->addWidget(graphHistogram, 1, 1);
ui->gridUpper->setRowMinimumHeight(0, 20);
ui->gridUpper->setRowStretch(0, 1);
ui->gridUpper->setRowStretch(1, 10);
graphHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
QCP::iSelectLegend);
graphHistogram->xAxis->setRange(0, 63);
graphHistogram->yAxis->setRange(0, 100);
QSharedPointer<QCPAxisTickerLog> graphHistoLogTicker(new QCPAxisTickerLog);
graphHistogram->yAxis->setTicker(graphHistoLogTicker);
//graphHistogram->yAxis2->setTicker(graphHistoLogTicker);
graphHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers
graphHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision
//graphHistogram->axisRect()->setupFullAxesBox();
graphHistogram->setBufferDevicePixelRatio(1);
graphHistogram->xAxis->setLabel("Bits");
graphHistogram->yAxis->setLabel("Instances");
graphHistogram->legend->setVisible(false);
ui->timeHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
QCP::iSelectLegend | QCP::iSelectPlottables);
QCP::iSelectLegend);
ui->timeHistogram->xAxis->setRange(0, numIntervalHistBars);
ui->timeHistogram->yAxis->setRange(0, 100);
QSharedPointer<QCPAxisTickerLog> logTicker(new QCPAxisTickerLog);
ui->timeHistogram->yAxis->setTicker(logTicker);
ui->timeHistogram->yAxis2->setTicker(logTicker);
//ui->timeHistogram->yAxis2->setTicker(logTicker);
ui->timeHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic);
ui->timeHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers
ui->timeHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision
ui->timeHistogram->axisRect()->setupFullAxesBox();
//ui->timeHistogram->axisRect()->setupFullAxesBox();
ui->timeHistogram->xAxis->setLabel("Interval (ms)");
ui->timeHistogram->yAxis->setLabel("Occurrences");
ui->timeHistogram->legend->setVisible(false);
ui->timeHistogram->setBufferDevicePixelRatio(1);
if (useOpenGL)
{
ui->graphHistogram->setAntialiasedElements(QCP::aeAll);
ui->graphHistogram->setOpenGl(true);
ui->graphBytes->setAntialiasedElements(QCP::aeAll);
ui->graphBytes->setOpenGl(true);
graphHistogram->setAntialiasedElements(QCP::aeAll);
graphHistogram->setOpenGl(true);
ui->timeHistogram->setAntialiasedElements(QCP::aeAll);
ui->timeHistogram->setOpenGl(true);
}
else
{
ui->graphHistogram->setOpenGl(false);
ui->graphHistogram->setAntialiasedElements(QCP::aeNone);
ui->graphBytes->setOpenGl(false);
ui->graphBytes->setAntialiasedElements(QCP::aeNone);
graphHistogram->setOpenGl(false);
graphHistogram->setAntialiasedElements(QCP::aeNone);
ui->timeHistogram->setOpenGl(false);
ui->timeHistogram->setAntialiasedElements(QCP::aeNone);
}
@@ -128,9 +121,113 @@ FrameInfoWindow::FrameInfoWindow(const QVector<CANFrame> *frames, QWidget *paren
bytePens[i].setWidth(1);
}
connect(graphHistogram, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress()));
connect(graphHistogram, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel()));
connect(ui->timeHistogram, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress()));
connect(ui->timeHistogram, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel()));
dbcHandler = DBCHandler::getReference();
}
void FrameInfoWindow::setupByteGraph(QCustomPlot *plot, int num)
{
plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
QCP::iSelectLegend);
plot->xAxis->setRange(0, 63);
plot->yAxis->setRange(0, 265);
if (useHexTicker)
{
QSharedPointer<QCPAxisTickerHex> hexTicker(new QCPAxisTickerHex);
plot->yAxis->setTicker(hexTicker);
}
//plot->axisRect()->setupFullAxesBox();
plot->xAxis->setLabel("Time [" + QString::number(num) + "]");
//if (useHexTicker) plot->yAxis->setLabel("Value (HEX)");
//else plot->yAxis->setLabel("Value (Dec)");
plot->yAxis->setLabel("");
plot->legend->setVisible(false);
plot->setBufferDevicePixelRatio(1);
if (useOpenGL)
{
plot->setAntialiasedElements(QCP::aeAll);
plot->setOpenGl(true);
}
else
{
plot->setOpenGl(false);
plot->setAntialiasedElements(QCP::aeNone);
}
connect(plot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress()));
connect(plot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel()));
connect(plot, &QCustomPlot::mouseDoubleClick, this, &FrameInfoWindow::mouseDoubleClick);
}
void FrameInfoWindow::mousePress()
{
QCustomPlot *plot = qobject_cast<QCustomPlot *>(sender());
if (!plot) return;
// 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 (plot->xAxis->selectedParts().testFlag(QCPAxis::spAxis))
plot->axisRect()->setRangeDrag(plot->xAxis->orientation());
else if (plot->yAxis->selectedParts().testFlag(QCPAxis::spAxis))
plot->axisRect()->setRangeDrag(plot->yAxis->orientation());
else
plot->axisRect()->setRangeDrag(Qt::Horizontal|Qt::Vertical);
}
void FrameInfoWindow::mouseWheel()
{
QCustomPlot *plot = qobject_cast<QCustomPlot *>(sender());
if (!plot) return;
// 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 (plot->xAxis->selectedParts().testFlag(QCPAxis::spAxis))
plot->axisRect()->setRangeZoom(plot->xAxis->orientation());
else if (plot->yAxis->selectedParts().testFlag(QCPAxis::spAxis))
plot->axisRect()->setRangeZoom(plot->yAxis->orientation());
else
plot->axisRect()->setRangeZoom(Qt::Horizontal|Qt::Vertical);
}
//two modes here. If none of the 8 sub graphs are hidden then hide all except the one the user
//just double clicked on. Otherwise unhide the 7 hidden ones
void FrameInfoWindow::mouseDoubleClick()
{
QCustomPlot *plot = qobject_cast<QCustomPlot *>(sender());
bool hideMode = true;
for (int i = 0; i < 8; i++)
{
if (ui->gridLower->itemAt(i)->widget()->isHidden()) hideMode = false;
}
if (hideMode)
{
for (int i = 0; i < 8; i++)
{
if (ui->gridLower->itemAt(i)->widget() == (plot)) qDebug() << "Idx " << i << " matched!";
else ui->gridLower->itemAt(i)->widget()->setHidden(true);
}
}
else
{
for (int i = 0; i < 8; i++)
{
ui->gridLower->itemAt(i)->widget()->setHidden(false);
}
}
}
void FrameInfoWindow::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
@@ -267,7 +364,7 @@ void FrameInfoWindow::updatedFrames(int numFrames)
}
//default is to sort in ascending order
ui->listFrameID->sortItems();
ui->lblFrameID->setText(tr("Frame IDs: (") + QString::number(ui->listFrameID->count()) + tr(" unique ids)"));
ui->lblUniqueID->setText(")" + QString::number(ui->listFrameID->count()) + tr(" unique ids)"));
}
}
@@ -290,6 +387,12 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
double maxY = -1000.0;
uint8_t changedBits[8];
uint8_t referenceBits[8];
uint8_t heatVals[512];
//these two used by bitflip heatmap functionality
uint8_t refByte[8];
double bitFlipHeat[64];
QTreeWidgetItem *baseNode, *dataBase, *histBase, *tempItem;
if (modelFrames->count() == 0) return;
@@ -310,6 +413,8 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
if (thisFrame.frameId() == static_cast<uint32_t>(targettedID)) frameCache.append(thisFrame);
}
if (frameCache.count() == 0) return; //nothing to do if there are no frames!
const unsigned char *data = reinterpret_cast<const unsigned char *>(frameCache.at(0).payload().constData());
int dataLen = frameCache.at(0).payload().length();
@@ -397,7 +502,11 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
maxData[i] = -1;
for (int k = 0; k < 256; k++) dataHistogram[k][i] = 0;
}
for (int j = 0; j < 64; j++) bitfieldHistogram[j] = 0;
for (int j = 0; j < 64; j++)
{
bitfieldHistogram[j] = 0;
bitFlipHeat[j] = 0;
}
signalInstances.clear();
data = reinterpret_cast<const unsigned char *>(frameCache.at(0).payload().constData());
@@ -407,6 +516,7 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
{
changedBits[c] = 0;
referenceBits[c] = data[c];
refByte[c] = data[c];
//qDebug() << referenceBits[c];
}
@@ -460,6 +570,16 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
}
}
changedBits[c] |= referenceBits[c] ^ dat;
if (refByte[c] != dat) //if this byte doesn't match the value it last had
{
uint8_t newBits = refByte[c] ^ dat; //get changed bits since last ref
for (int l = 0; l < 8; l++)
{
if (newBits & (1 << l)) bitFlipHeat[(c * 8) + l]++;
}
refByte[c] = dat; //set the reference to this current byte now that processing is done
}
}
//Search every signal in the selected message and give output of the range the signal took and
@@ -485,6 +605,9 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
}
}
//Divide all the bit flip heat values by the number of frames to get a ratio
for (int j = 0; j < 64; j++) bitFlipHeat[j] /= (double)frameCache.count();
std::sort(sortedIntervals.begin(), sortedIntervals.end());
int64_t intervalStdDiv = 0, intervalPctl5 = 0, intervalPctl95 = 0, intervalMean = 0, intervalVariance = 0;
@@ -530,6 +653,8 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
avgInterval = avgInterval / (frameCache.count() - 1);
else avgInterval = 0;
//now that data processing is done, create all of our output
tempItem = new QTreeWidgetItem();
if (minLen < maxLen)
@@ -596,7 +721,7 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
{
tempItem = new QTreeWidgetItem();
tempItem->setText(0, QString::number(c) + " (Byte " + QString::number(c / 8) + " Bit "
+ QString::number(c % 8) + ") :" + QString::number(bitfieldHistogram[c]));
+ QString::number(c % 8) + ") : " + QString::number(bitfieldHistogram[c]));
dataBase->addChild(tempItem);
histGraphX.append(c);
@@ -605,6 +730,28 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
}
baseNode->addChild(dataBase);
//heat map output
dataBase = new QTreeWidgetItem();
dataBase->setText(0, tr("Bitchange Heatmap"));
memset(heatVals, 0, 512); //always clear the array before populating it.
for (int c = 0; c < 8 * maxLen; c++)
{
tempItem = new QTreeWidgetItem();
tempItem->setText(0, QString::number(c) + " (Byte " + QString::number(c / 8) + " Bit "
+ QString::number(c % 8) + ") : " + QString::number(bitFlipHeat[c] * 100.0, 'f', 2));
dataBase->addChild(tempItem);
histGraphX.append(c);
histGraphY.append(bitfieldHistogram[c]);
if (bitfieldHistogram[c] > maxY) maxY = bitfieldHistogram[c];
uint8_t heat = bitFlipHeat[c] * 255;
if ((heat < 1) && (bitFlipHeat[c] > 0.0001)) heat = 1; //make sure any little bit of heat causes at least some output
//qDebug() << "Heat for bit " << c << " is " << heat;
heatVals[c] = heat;
}
baseNode->addChild(dataBase);
heatmap->setHeat(heatVals);
QHash<QString, QHash<QString, int>>::const_iterator it = signalInstances.constBegin();
while (it != signalInstances.constEnd()) {
dataBase = new QTreeWidgetItem();
@@ -623,29 +770,29 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
ui->treeDetails->insertTopLevelItem(0, baseNode);
ui->graphHistogram->clearGraphs();
ui->graphHistogram->addGraph();
ui->graphHistogram->graph()->setData(histGraphX, histGraphY);
ui->graphHistogram->graph()->setLineStyle(QCPGraph::lsStepLeft); //connect points with lines
graphHistogram->clearGraphs();
graphHistogram->addGraph();
graphHistogram->graph()->setData(histGraphX, histGraphY);
graphHistogram->graph()->setLineStyle(QCPGraph::lsStepLeft); //connect points with lines
QBrush graphBrush;
graphBrush.setColor(Qt::red);
graphBrush.setStyle(Qt::SolidPattern);
ui->graphHistogram->graph()->setPen(Qt::NoPen);
ui->graphHistogram->graph()->setBrush(graphBrush);
ui->graphHistogram->yAxis->setRange(0.8, maxY * 1.2);
ui->graphHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic);
ui->graphHistogram->axisRect()->setupFullAxesBox();
ui->graphHistogram->replot();
graphHistogram->graph()->setPen(Qt::NoPen);
graphHistogram->graph()->setBrush(graphBrush);
graphHistogram->yAxis->setRange(0.8, maxY * 1.2);
graphHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic);
graphHistogram->axisRect()->setupFullAxesBox();
graphHistogram->replot();
ui->graphBytes->clearGraphs();
for (int graphs = 0; graphs < 8; graphs++)
{
graphRef[graphs] = ui->graphBytes->addGraph();
ui->graphBytes->graph()->setData(byteGraphX, byteGraphY[graphs]);
ui->graphBytes->graph()->setPen(bytePens[graphs]);
graphByte[graphs]->clearGraphs();
graphRef[graphs] = graphByte[graphs]->addGraph();
graphByte[graphs]->graph()->setData(byteGraphX, byteGraphY[graphs]);
graphByte[graphs]->graph()->setPen(bytePens[graphs]);
graphByte[graphs]->xAxis->setRange(0, byteGraphX.count());
graphByte[graphs]->replot();
}
ui->graphBytes->xAxis->setRange(0, byteGraphX.count());
ui->graphBytes->replot();
ui->timeHistogram->clearGraphs();
ui->timeHistogram->addGraph();
@@ -661,15 +808,6 @@ void FrameInfoWindow::updateDetailsWindow(QString newID)
ui->timeHistogram->axisRect()->setupFullAxesBox();
ui->timeHistogram->rescaleAxes();
ui->timeHistogram->replot();
ui->check_0->setChecked(true);
ui->check_1->setChecked(true);
ui->check_2->setChecked(true);
ui->check_3->setChecked(true);
ui->check_4->setChecked(true);
ui->check_5->setChecked(true);
ui->check_6->setChecked(true);
ui->check_7->setChecked(true);
}
else
{
@@ -697,7 +835,7 @@ void FrameInfoWindow::refreshIDList()
}
//default is to sort in ascending order
ui->listFrameID->sortItems();
ui->lblFrameID->setText(tr("Frame IDs: (") + QString::number(ui->listFrameID->count()) + tr(" unique ids)"));
ui->lblUniqueID->setText("(" + QString::number(ui->listFrameID->count()) + tr(" unique ids)"));
}
void FrameInfoWindow::saveDetails()
@@ -744,24 +882,6 @@ void FrameInfoWindow::saveDetails()
}
}
void FrameInfoWindow::changeGraphVisibility(int state){
QCheckBox *sender = qobject_cast<QCheckBox *>(QObject::sender());
if(sender){
sender->objectName();
int graphId = sender->objectName().right(1).toInt();
for (int k = 0; k < 8; k++)
{
if (k == graphId && graphRef[k] && graphRef[k]->data()){
graphRef[k]->setVisible(state);
qDebug() << graphId << state << k;
}
}
qDebug() << graphId << state;
ui->graphBytes->replot();
}
}
void FrameInfoWindow::dumpNode(QTreeWidgetItem* item, QFile *file, int indent)
{
for (int i = 0; i < indent; i++) file->write("\t");
+8 -1
View File
@@ -5,6 +5,7 @@
#include <QFile>
#include <QListWidget>
#include <QTreeWidget>
#include <candatagrid.h>
#include "can_structs.h"
#include "bus_protocols/j1939_handler.h"
#include "dbc/dbchandler.h"
@@ -28,10 +29,15 @@ private slots:
void updateDetailsWindow(QString);
void updatedFrames(int);
void saveDetails();
void changeGraphVisibility(int state);
void mousePress();
void mouseWheel();
void mouseDoubleClick();
private:
Ui::FrameInfoWindow *ui;
QCustomPlot *graphByte[8];
QCustomPlot *graphHistogram;
CANDataGrid *heatmap;
QList<int> foundID;
QList<CANFrame> frameCache;
@@ -47,6 +53,7 @@ private:
void refreshIDList();
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
void setupByteGraph(QCustomPlot *plot, int num);
void readSettings();
void writeSettings();
void dumpNode(QTreeWidgetItem* item, QFile *file, int indent);
+28 -7
View File
@@ -3,6 +3,7 @@
#include "newgraphdialog.h"
#include "mainwindow.h"
#include "helpwindow.h"
#include "utility.h"
#include <QDebug>
#include <algorithm>
@@ -33,9 +34,16 @@ GraphingWindow::GraphingWindow(const QVector<CANFrame> *frames, QWidget *parent)
ui->graphingView->xAxis->setLabel("Time Axis");
ui->graphingView->yAxis->setLabel("Value Axis");
ui->graphingView->xAxis->setNumberFormat("f");
if (secondsMode) ui->graphingView->xAxis->setNumberPrecision(6);
if (Utility::timeStyle == TS_SECONDS) ui->graphingView->xAxis->setNumberPrecision(6);
else ui->graphingView->xAxis->setNumberPrecision(0);
if (Utility::timeStyle == TS_CLOCK)
{
QSharedPointer<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime);
timeTicker->setTimeFormat("%h:%m:%s.%z");
ui->graphingView->xAxis->setTicker(timeTicker);
}
ui->graphingView->legend->setVisible(true);
QFont legendFont = font();
legendFont.setPointSize(10);
@@ -61,9 +69,9 @@ GraphingWindow::GraphingWindow(const QVector<CANFrame> *frames, QWidget *parent)
// connect slot that ties some axis selections together (especially opposite axes):
connect(ui->graphingView, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged()));
//connect up the mouse controls
connect(ui->graphingView, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress()));
connect(ui->graphingView, SIGNAL(plottableDoubleClick(QCPAbstractPlottable*,int,QMouseEvent*)), this, SLOT(plottableDoubleClick(QCPAbstractPlottable*,int,QMouseEvent*)));
connect(ui->graphingView, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)), this, SLOT(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)));
connect(ui->graphingView, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress()));
connect(ui->graphingView, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel()));
// make bottom and left axes transfer their ranges to top and right axes:
@@ -89,6 +97,9 @@ GraphingWindow::GraphingWindow(const QVector<CANFrame> *frames, QWidget *parent)
if (useOpenGL)
{
//Fix the device pixel ratio for openGL so the graph doesn't render double sized
ui->graphingView->setBufferDevicePixelRatio(1);
ui->graphingView->setAntialiasedElements(QCP::aeAll);
//ui->graphingView->setNoAntialiasingOnDrag(true);
ui->graphingView->setOpenGl(true);
@@ -151,7 +162,7 @@ void GraphingWindow::readSettings()
resize(settings.value("Graphing/WindowSize", QSize(800, 600)).toSize());
move(Utility::constrainedWindowPos(settings.value("Graphing/WindowPos", QPoint(50, 50)).toPoint()));
}
secondsMode = settings.value("Main/TimeSeconds", false).toBool();
useOpenGL = settings.value("Main/UseOpenGL", false).toBool();
}
@@ -264,7 +275,7 @@ void GraphingWindow::plottableDoubleClick(QCPAbstractPlottable* plottable, int d
//apply transforms to get the X axis value where we double clicked
double coord = plottable->keyAxis()->pixelToCoord(event->localPos().x());
id = plottable->property("id").toInt();
if (secondsMode) emit sendCenterTimeID(id, coord);
if (Utility::timeStyle == TS_SECONDS) emit sendCenterTimeID(id, coord);
else emit sendCenterTimeID(id, coord / 1000000.0);
double x, y;
@@ -295,7 +306,7 @@ void GraphingWindow::gotCenterTimeID(uint32_t ID, double timestamp)
QCPRange range = ui->graphingView->xAxis->range();
double offset = range.size() / 2.0;
if (!secondsMode) timestamp *= 1000000.0; //timestamp is always in seconds when being passed so convert if necessary
if (Utility::timeStyle != TS_SECONDS) timestamp *= 1000000.0; //timestamp is always in seconds when being passed so convert if necessary
ui->graphingView->xAxis->setRange(timestamp - offset, timestamp + offset);
ui->graphingView->replot();
}
@@ -1239,10 +1250,15 @@ void GraphingWindow::appendToGraph(GraphParams &params, CANFrame &frame, QVector
int64_t tempVal; //64 bit temp value.
tempVal = Utility::processIntegerSignal(frame.payload(), params.startBit, params.numBits, params.intelFormat, params.isSigned); //& params.mask;
double xVal, yVal;
if (secondsMode)
if (Utility::timeStyle == TS_SECONDS)
{
xVal = ((double)(frame.timeStamp().microSeconds()) / 1000000.0 - params.xbias);
}
else if (Utility::timeStyle == TS_CLOCK)
{
QDateTime dt = QDateTime::fromMSecsSinceEpoch((frame.timeStamp().microSeconds() / 1000) - params.xbias);
xVal = (dt.time().second() + dt.time().minute() * 60 + dt.time().hour() * 3600);
}
else
{
xVal = (frame.timeStamp().microSeconds() - params.xbias);
@@ -1372,10 +1388,15 @@ void GraphingWindow::createGraph(GraphParams &params, bool createGraphParam)
y = (tempVal * params.scale) + params.bias;
params.y.append( y );
if (secondsMode)
if (Utility::timeStyle == TS_SECONDS)
{
x = (frameCache[k].timeStamp().microSeconds()) / 1000000.0;
}
else if (Utility::timeStyle == TS_CLOCK)
{
QDateTime dt = QDateTime::fromMSecsSinceEpoch((frameCache[k].timeStamp().microSeconds() / 1000) - params.xbias);
x = (dt.time().msecsSinceStartOfDay() / 1000.0);
}
else
{
x = frameCache[k].timeStamp().microSeconds();
-1
View File
@@ -102,7 +102,6 @@ private:
QCPItemText *locationText;
QCPItemTracer *itemTracer;
bool needScaleSetup; //do we need to set x,y graphing extents?
bool secondsMode;
bool useOpenGL;
bool followGraphEnd;
+61 -17
View File
@@ -77,9 +77,16 @@ UDSScanWindow::UDSScanWindow(const QVector<CANFrame> *frames, QWidget *parent) :
//not handling show no reply, max reply delay, reply offset, increment
int numBuses = CANConManager::getInstance()->getNumBuses();
for (int n = 0; n < numBuses; n++) ui->cbBuses->addItem(QString::number(n));
connect(ui->cbBuses, &QComboBox::currentTextChanged, this, &UDSScanWindow::setBusToScan);
installEventFilter(this);
addNewScan();
checkIDRange();
checkServiceRange();
checkSubFuncRange();
}
UDSScanWindow::~UDSScanWindow()
@@ -109,10 +116,18 @@ bool UDSScanWindow::eventFilter(QObject *obj, QEvent *event)
return false;
}
void UDSScanWindow::setControlState(QWidget & widget, bool valid)
{
QPalette pal = widget.palette();
pal.setColor(QPalette::ColorRole::Base , valid ? Qt::white : Qt::red);
widget.setPalette(pal);
}
void UDSScanWindow::displayScanEntry(int idx)
{
if (inhibitUpdates) return;
if (idx == -1) return;
inhibitUpdates = true; //need to do this so it doesn't mess things up as we fill in the fields
currEditEntry = &scanEntries.data()[idx];
ui->spinStartID->setValue(currEditEntry->startID);
ui->spinEndID->setValue(currEditEntry->endID);
@@ -129,6 +144,7 @@ void UDSScanWindow::displayScanEntry(int idx)
ui->spinLowerSubfunc->setValue(currEditEntry->subfunctLower);
ui->spinUpperSubfunc->setValue(currEditEntry->subfunctUpper);
ui->spinIncrement->setValue(currEditEntry->subfunctIncrement);
inhibitUpdates = false;
}
void UDSScanWindow::deleteSelectedScan()
@@ -186,7 +202,7 @@ void UDSScanWindow::loadScans()
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setNameFilters(filters);
dialog.setViewMode(QFileDialog::Detail);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
if (dialog.exec() == QDialog::Accepted)
{
@@ -201,7 +217,7 @@ void UDSScanWindow::loadScans()
QMessageBox::warning(this, "Cannot Load File", "File is not a supported version.\nCannot load it!");
return;
}
inhibitUpdates = true;;
inhibitUpdates = true;
scanEntries.clear();
ui->listScansToRun->clear();
int numEntries;
@@ -209,12 +225,22 @@ void UDSScanWindow::loadScans()
for (int i = 0; i < numEntries; i++)
{
ScanEntry entry;
load >> entry.startID >> entry.endID;
load >> entry.idOffset >> entry.bAdaptiveOffset >> entry.bShowNoReplies;
load >> entry.busToScan >> entry.maxWaitTime >> entry.scanType;
load >> entry.sessType >> entry.subfunctLen >> entry.subfunctLower;
load >> entry.subfunctUpper >> entry.subfunctIncrement;
load >> entry.serviceLower >> entry.serviceUpper;
load >> entry.startID;
load >> entry.endID;
load >> entry.idOffset;
load >> entry.bAdaptiveOffset;
load >> entry.bShowNoReplies;
load >> entry.busToScan;
load >> entry.maxWaitTime;
load >> entry.scanType;
load >> entry.sessType;
load >> entry.subfunctLen;
load >> entry.subfunctLower;
load >> entry.subfunctUpper;
load >> entry.subfunctIncrement;
load >> entry.serviceLower;
load >> entry.serviceUpper;
scanEntries.append(entry);
ui->listScansToRun->addItem(generateListDesc(scanEntries.length() - 1));
}
@@ -239,6 +265,7 @@ void UDSScanWindow::saveScans()
dialog.setNameFilters(filters);
dialog.setViewMode(QFileDialog::Detail);
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix(".uds");
if (dialog.exec() == QDialog::Accepted)
{
@@ -334,6 +361,12 @@ void UDSScanWindow::setSessType()
if (currEditEntry) currEditEntry->sessType = ui->cbSessType->currentIndex();
}
void UDSScanWindow::setBusToScan()
{
if (inhibitUpdates) return;
if (currEditEntry) currEditEntry->busToScan = ui->cbBuses->currentIndex();
}
void UDSScanWindow::setNoReplyVal()
{
if (inhibitUpdates) return;
@@ -439,25 +472,33 @@ void UDSScanWindow::numBytesChanged()
uint64_t upperBound = (1ull << (8ull * ui->spinNumBytes->value())) - 1;
if (upperBound > 0x7FFFFFFF) upperBound = 0x7FFFFFFF;
ui->spinUpperSubfunc->setMaximum(upperBound);
ui->spinLowerSubfunc->setMaximum(upperBound);
if (currEditEntry) currEditEntry->subfunctLen = ui->spinNumBytes->value();
}
void UDSScanWindow::checkIDRange()
{
if (inhibitUpdates) return;
ui->spinStartID->setMaximum(ui->spinEndID->value());
ui->spinEndID->setMinimum(ui->spinStartID->value());
const bool isValid = ui->spinStartID->value() <= ui->spinEndID->value();
setControlState(*ui->spinStartID, isValid);
setControlState(*ui->spinEndID, isValid);
if (currEditEntry) currEditEntry->startID = ui->spinStartID->value();
if (currEditEntry) currEditEntry->endID = ui->spinEndID->value();
QListWidgetItem* item = ui->listScansToRun->currentItem();
item->setText(generateListDesc(ui->listScansToRun->currentRow()));
if (QListWidgetItem* item = ui->listScansToRun->currentItem())
{
item->setText(generateListDesc(ui->listScansToRun->currentRow()));
}
}
void UDSScanWindow::checkServiceRange()
{
if (inhibitUpdates) return;
ui->spinLowerService->setMaximum(ui->spinUpperService->value());
ui->spinUpperService->setMinimum(ui->spinLowerService->value());
const bool isValid = ui->spinLowerService->value() <= ui->spinUpperService->value();
setControlState(*ui->spinLowerService, isValid);
setControlState(*ui->spinUpperService, isValid);
if (currEditEntry) currEditEntry->serviceLower = ui->spinLowerService->value();
if (currEditEntry) currEditEntry->serviceUpper = ui->spinUpperService->value();
}
@@ -465,8 +506,9 @@ void UDSScanWindow::checkServiceRange()
void UDSScanWindow::checkSubFuncRange()
{
if (inhibitUpdates) return;
ui->spinLowerSubfunc->setMaximum(ui->spinUpperSubfunc->value());
ui->spinUpperSubfunc->setMinimum(ui->spinLowerSubfunc->value());
const bool isValid = ui->spinLowerSubfunc->value() <= ui->spinUpperSubfunc->value();
setControlState(*ui->spinLowerSubfunc, isValid);
setControlState(*ui->spinUpperSubfunc, isValid);
if (currEditEntry) currEditEntry->subfunctLower = ui->spinLowerSubfunc->value();
if (currEditEntry) currEditEntry->subfunctUpper = ui->spinUpperSubfunc->value();
}
@@ -547,6 +589,8 @@ void UDSScanWindow::scanSelected()
void UDSScanWindow::startScan()
{
if (sendingFrames.isEmpty()) return;
udsHandler->setReception(true);
udsHandler->setProcessAllIDs(true);
udsHandler->setFlowCtrl(true);
@@ -643,7 +687,7 @@ void UDSScanWindow::setupScan(int idx)
case ST_READ_DTC:
test.service = UDS_SERVICES::READ_DTC;
test.subFuncLen = 2;
test.subFunc = 0x8702; //get DTCs by mask (87 is the mask)
test.subFunc = 0x02FF; //get DTCs by mask (FF is the mask)
sendOnBuses(test, scanEntries[idx].busToScan);
break;
case ST_SEC_ACCESS:
+3
View File
@@ -81,6 +81,7 @@ private slots:
void setIncrementVal();
void setReplyOffset();
void setSessType();
void setBusToScan();
private:
Ui::UDSScanWindow *ui;
@@ -107,5 +108,7 @@ private:
void setupNodes(uint32_t replyID);
void dumpNode(QTreeWidgetItem* item, QFile *file, int indent);
bool eventFilter(QObject *obj, QEvent *event);
static void setControlState(QWidget & widget, bool valid);
};
#endif // UDSSCANWINDOW_H
+3 -3
View File
@@ -263,7 +263,7 @@ void CANScriptHelper::gotTargettedFrame(const CANFrame &frame)
if (filters[i].checkFilter(frame.frameId(), frame.bus))
{
QJSValueList args;
args << frame.bus << frame.frameId() << frame.payload().length();
args << frame.bus << frame.frameId() << static_cast<uint>(frame.payload().length());
QJSValue dataBytes = scriptEngine->newArray(dataLen);
for (int j = 0; j < dataLen; j++) dataBytes.setProperty(j, QJSValue(data[j]));
@@ -341,7 +341,7 @@ void ISOTPScriptHelper::newISOMessage(ISOTP_MESSAGE msg)
//qDebug() << "Got frame in script interface";
QJSValueList args;
args << msg.bus << msg.frameId() << msg.payload().length();
args << msg.bus << msg.frameId() << static_cast<uint>(msg.payload().length());
QJSValue dataBytes = scriptEngine->newArray(static_cast<uint>(msg.payload().length()));
for (int j = 0; j < msg.payload().length(); j++) dataBytes.setProperty(static_cast<quint32>(j), QJSValue((unsigned char)msg.payload()[j]));
@@ -420,7 +420,7 @@ void UDSScriptHelper::newUDSMessage(UDS_MESSAGE msg)
qDebug() << "Got frame in script interface";
QJSValueList args;
args << msg.bus << msg.frameId() << msg.service << msg.subFunc << msg.payload().length();
args << msg.bus << msg.frameId() << msg.service << msg.subFunc << static_cast<uint>(msg.payload().length());
QJSValue dataBytes = scriptEngine->newArray(static_cast<unsigned int>(msg.payload().length()));
for (int j = 0; j < msg.payload().length(); j++) dataBytes.setProperty(static_cast<quint32>(j), QJSValue((unsigned char)msg.payload()[j]));
+89
View File
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CANBridgeWindow</class>
<widget class="QDialog" name="CANBridgeWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>480</height>
</rect>
</property>
<property name="windowTitle">
<string>CAN Bridger</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Side 1</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbSide1"/>
</item>
<item>
<widget class="QCheckBox" name="ckEnableSide1">
<property name="text">
<string>Enable forwarding</string>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>IDs to forward to side 2</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listSide1"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Side 2</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbSide2"/>
</item>
<item>
<widget class="QCheckBox" name="ckEnableSide2">
<property name="text">
<string>Enable forwarding</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>IDs to forward to side 1</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listSide2"/>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+32 -41
View File
@@ -17,46 +17,38 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="2,1">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Signal Parameters</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txtName"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Start Bit:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="CANDataGrid" name="bitfield" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>300</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txtName"/>
</item>
</layout>
</item>
<item>
<widget class="CANDataGrid" name="bitfield" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>300</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
@@ -258,7 +250,6 @@
</customwidget>
</customwidgets>
<tabstops>
<tabstop>txtName</tabstop>
<tabstop>txtBitLength</tabstop>
<tabstop>cbIntelFormat</tabstop>
<tabstop>comboType</tabstop>
+7 -16
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>906</width>
<height>568</height>
<height>706</height>
</rect>
</property>
<property name="windowTitle">
@@ -1037,9 +1037,9 @@
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>252</red>
<green>233</green>
<blue>79</blue>
<red>167</red>
<green>154</green>
<blue>52</blue>
</color>
</brush>
</colorrole>
@@ -1048,9 +1048,9 @@
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>252</red>
<green>233</green>
<blue>79</blue>
<red>35</red>
<green>38</green>
<blue>41</blue>
</color>
</brush>
</colorrole>
@@ -1071,7 +1071,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1131,7 +1130,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1191,7 +1189,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1251,7 +1248,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1311,7 +1307,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1371,7 +1366,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1431,7 +1425,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1491,7 +1484,6 @@
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -1553,7 +1545,6 @@
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
+112 -538
View File
@@ -6,567 +6,141 @@
<rect>
<x>0</x>
<y>0</y>
<width>794</width>
<height>494</height>
<width>1142</width>
<height>651</height>
</rect>
</property>
<property name="windowTitle">
<string>Detailed Frame Information</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,2">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>8</number>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,4">
<item>
<widget class="QLabel" name="lblFrameID">
<property name="text">
<string>Frame IDs:</string>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="lblFrameID">
<property name="text">
<string>Frame IDs:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblUniqueID">
<property name="text">
<string>Unique IDs</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listFrameID">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="listFrameID">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Details:</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="treeDetails">
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="text">
<string>Save details to file</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_time">
<property name="text">
<string>Interval Histogram</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCustomPlot" name="timeHistogram" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Details:</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="treeDetails">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="text">
<string>Save details to file</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_time">
<property name="text">
<string>Interval Histogram</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCustomPlot" name="timeHistogram" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,1,0,1,0">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Bit Histograph</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCustomPlot" name="graphHistogram" native="true"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Bytes Graph</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCustomPlot" name="graphBytes" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="2,0,2">
<item>
<widget class="QCheckBox" name="check_0">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>52</red>
<green>101</green>
<blue>164</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>52</red>
<green>101</green>
<blue>164</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<layout class="QGridLayout" name="gridUpper"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>0</string>
<string>Bytes Graph</string>
</property>
<property name="checked">
<bool>true</bool>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_1">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>78</red>
<green>154</green>
<blue>6</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>78</red>
<green>154</green>
<blue>6</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>1</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_2">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>2</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_3">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>204</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>204</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>3</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_4">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>136</red>
<green>138</green>
<blue>133</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>136</red>
<green>138</green>
<blue>133</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>4</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_5">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>252</red>
<green>233</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>252</red>
<green>233</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>5</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_6">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>114</red>
<green>159</green>
<blue>207</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>114</red>
<green>159</green>
<blue>207</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>6</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_7">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>173</red>
<green>127</green>
<blue>168</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>173</red>
<green>127</green>
<blue>168</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>7</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<layout class="QGridLayout" name="gridLower" rowstretch="0"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
+7
View File
@@ -107,6 +107,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="ckWaitForTraffic">
<property name="text">
<string>Wait for traffic before starting playback</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
View File
+4 -4
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>965</width>
<height>815</height>
<height>852</height>
</rect>
</property>
<property name="windowTitle">
@@ -115,7 +115,7 @@
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>Maximum Frames to Capture</string>
<string>CAN Frame Pre-allocation Size</string>
</property>
</widget>
</item>
@@ -355,14 +355,14 @@
<item>
<widget class="QCheckBox" name="cbFilterLabeling">
<property name="text">
<string>Label DBC Signals by default</string>
<string>Label DBC messages by default</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbIgnoreDBCColors">
<property name="text">
<string>Ignore DBC signal colors</string>
<string>Ignore DBC message colors</string>
</property>
</widget>
</item>
+10 -4
View File
@@ -32,7 +32,6 @@
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
@@ -81,7 +80,6 @@
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
@@ -138,7 +136,6 @@
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<italic>true</italic>
<bold>true</bold>
</font>
@@ -338,6 +335,9 @@
<height>16777215</height>
</size>
</property>
<property name="alternatingRowColors">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@@ -368,7 +368,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menu_RE_Tools">
@@ -399,6 +399,7 @@
<addaction name="actionUDS_Scanner"/>
<addaction name="actionFirmware_Update"/>
<addaction name="actionMotorControlConfig"/>
<addaction name="actionCAN_Bridge"/>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
@@ -621,6 +622,11 @@
<string>Save Decoded Frames CSV</string>
</property>
</action>
<action name="actionCAN_Bridge">
<property name="text">
<string>CAN Bridge</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
+7
View File
@@ -71,6 +71,13 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QRadioButton" name="rbCANserver">
<property name="text">
<string>CANserver</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+1 -1
View File
@@ -345,7 +345,7 @@
<number>1024</number>
</property>
<property name="singleStep">
<number>8</number>
<number>1</number>
</property>
<property name="displayIntegerBase">
<number>16</number>
+1 -3
View File
@@ -1,7 +1,5 @@
#include "utility.h"
bool Utility::decimalMode = false;
bool Utility::secondsMode = true;
bool Utility::sysTimeMode = false;
bool Utility::millisMode = false;
QString Utility::timeFormat = "MMM-dd HH:mm:ss.zzz";
TimeStyle Utility::timeStyle = TS_MICROS;
+24 -12
View File
@@ -10,14 +10,20 @@
#include <QRect>
//#include <QDesktopWidget>
enum TimeStyle
{
TS_SECONDS,
TS_MICROS,
TS_MILLIS,
TS_CLOCK
};
class Utility
{
public:
static bool decimalMode;
static bool secondsMode;
static bool millisMode;
static bool sysTimeMode;
static TimeStyle timeStyle;
static QString timeFormat;
//determines whether the window position is within any available screens. If it is not we default
@@ -153,15 +159,21 @@ public:
static QVariant formatTimestamp(uint64_t timestamp)
{
if (!sysTimeMode) {
if (millisMode) return (double)timestamp / 1000.0;
if (!secondsMode) return (unsigned long long)(timestamp);
else
{
return (double)timestamp / 1000000.0;
}
switch (timeStyle)
{
case TS_CLOCK:
return QDateTime::fromMSecsSinceEpoch(timestamp / 1000);
break;
case TS_MILLIS:
return (double)timestamp / 1000.0;
break;
case TS_MICROS:
return (unsigned long long)(timestamp);
break;
case TS_SECONDS:
return (double)timestamp / 1000000.0;
break;
}
else return QDateTime::fromMSecsSinceEpoch(timestamp / 1000);
}
//parses the input string to grab as much of it as possible while staying alpha numeric
@@ -216,7 +228,7 @@ public:
}
/* A unified function that can extract a signal from the (up to) 64 bits of data bytes in a CAN frame
* handles both little and big endian signals (and floats too but that's untested).
* handles both little and big endian signals (and floats too).
*/
static int64_t processIntegerSignal(const QByteArray data, int startBit, int sigSize, bool littleEndian, bool isSigned)
{