Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Highlight changed bytes in Frames view when Overwrite mode is enabled #708

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions SavvyCAN.pro
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ SOURCES += main.cpp\
connections/newconnectiondialog.cpp \
re/temporalgraphwindow.cpp \
filterutility.cpp \
pcaplite.cpp
pcaplite.cpp \
candataitemdelegate.cpp

HEADERS += mainwindow.h \
can_structs.h \
Expand Down Expand Up @@ -194,7 +195,8 @@ HEADERS += mainwindow.h \
connections/newconnectiondialog.h \
re/temporalgraphwindow.h \
filterutility.h \
pcaplite.h
pcaplite.h \
candataitemdelegate.h

FORMS += ui/candatagrid.ui \
triggerdialog.ui \
Expand Down
8 changes: 7 additions & 1 deletion can_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <QVector>
#include <stdint.h>
#include <QCanBusFrame>
#include <QMetaType>

//Now inherits from the built-in CAN frame class from Qt. This should be more future proof and easier to integrate with other code

Expand All @@ -14,7 +15,10 @@ struct CANFrame : public QCanBusFrame
int bus;
bool isReceived; //did we receive this or send it?
uint64_t timedelta;
uint32_t frameCount; //used in overwrite mode

//used in overwrite mode
uint32_t frameCount;
uint64_t changedPayloadBytes;

friend bool operator<(const CANFrame& l, const CANFrame& r)
{
Expand All @@ -35,6 +39,8 @@ struct CANFrame : public QCanBusFrame
}
};

Q_DECLARE_METATYPE(CANFrame);

class CANFltObserver
{
public:
Expand Down
147 changes: 147 additions & 0 deletions candataitemdelegate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@


#include "candataitemdelegate.h"
#include <QPainter>
#include <QDebug>
#include "can_structs.h"

#define DATA_ITEM_LEFT_PADDING 5

CanDataItemDelegate::CanDataItemDelegate(CANFrameModel *_model) : QItemDelegate() {

dbcHandler = DBCHandler::getReference();
model = _model;
}

void CanDataItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
const auto frame = index.data().value<CANFrame>();
const unsigned char *data = reinterpret_cast<const unsigned char *>(frame.payload().constData());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to continue to use QByteArray?


bool overwriteDups = model->getOverwriteMode();
bool markChangedBytes = model->getMarkChangedBytes();
int bytesPerLine = model->getBytesPerLine();
bool useHexMode = model->getHexMode();
Comment on lines +22 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool overwriteDups = model->getOverwriteMode();
bool markChangedBytes = model->getMarkChangedBytes();
int bytesPerLine = model->getBytesPerLine();
bool useHexMode = model->getHexMode();
const bool overwriteDups = model->getOverwriteMode();
const bool markChangedBytes = model->getMarkChangedBytes();
const int bytesPerLine = model->getBytesPerLine();
const bool useHexMode = model->getHexMode();


// cache the painter state so it can be restored after this item is done painting
painter->save();
QStyleOptionViewItem opt = setOptions(index, option);
drawBackground(painter, opt, index);

auto pen = painter->pen();
const auto defaultColor = pen.color();
Comment on lines +32 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto pen = painter->pen();
const auto defaultColor = pen.color();
const auto defaultColor = painter->pen().color();


// FontMetrics are used to measure the bounding rects for drawn text, allowing
// us to properly position subsequent draw calls
const auto fm = painter->fontMetrics();

int dataLen = frame.payload().count();
if (dataLen < 0) dataLen = 0;
Comment on lines +39 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int dataLen = frame.payload().count();
if (dataLen < 0) dataLen = 0;
int dataLen = frame.payload().count();
if (dataLen < 0) dataLen = 0;
const auto dataLen = std::max(frame.payload().count(), qsizetype{0});

if (frame.frameType() != QCanBusFrame::RemoteRequestFrame) {
QRect boundingRect;
QPoint startPoint = option.rect.topLeft();
const auto charBounds = fm.boundingRect("0");
int x = startPoint.x() + DATA_ITEM_LEFT_PADDING;
int y = startPoint.y() + charBounds.height();

// Draw each data byte manually, advancing the start point as we go
// This allows us to change the styling of individual draw calls.
// We use this to highlight data bytes that have changed since the last
// frame when overwriteDups mode is on.
for (int i = 0; i < dataLen; i++)
{
if((frame.changedPayloadBytes & (1 << i)) && overwriteDups && markChangedBytes) {
painter->setPen(Qt::red);
}
else {
painter->setPen(defaultColor);
}

QString dataStr =
useHexMode ?
QString::number(data[i], 16).toUpper().rightJustified(2, '0') :
QString::number(data[i], 10);
const auto bounds = fm.boundingRect(dataStr);

painter->drawText(x, y, dataStr);

x += bounds.width();

if (!((i+1) % bytesPerLine) && (i != (dataLen - 1))) {
// Wrap to next line
y += bounds.height();
x = startPoint.x() + DATA_ITEM_LEFT_PADDING;
}
else {
// Advance a character width for a space
x += charBounds.width();
}
}

painter->setPen(defaultColor);

QString tempString;
buildStringFromFrame(frame, tempString);

if (tempString.length())
painter->drawText(x, y, tempString);
}

drawFocus(painter, opt, option.rect);
painter->restore();
}

void CanDataItemDelegate::buildStringFromFrame(CANFrame frame, QString &tempString) const {
bool interpretFrames = model->getInterpretMode();
bool overwriteDups = model->getOverwriteMode();
tempString.append("");
if (frame.frameType() == frame.ErrorFrame)
{
if (frame.error() & frame.TransmissionTimeoutError) tempString.append("\nTX Timeout");
if (frame.error() & frame.LostArbitrationError) tempString.append("\nLost Arbitration");
if (frame.error() & frame.ControllerError) tempString.append("\nController Error");
if (frame.error() & frame.ProtocolViolationError) tempString.append("\nProtocol Violation");
if (frame.error() & frame.TransceiverError) tempString.append("\nTransceiver Error");
if (frame.error() & frame.MissingAcknowledgmentError) tempString.append("\nMissing ACK");
if (frame.error() & frame.BusOffError) tempString.append("\nBus OFF");
if (frame.error() & frame.BusError) tempString.append("\nBus ERR");
if (frame.error() & frame.ControllerRestartError) tempString.append("\nController restart err");
if (frame.error() & frame.UnknownError) tempString.append("\nUnknown error type");
}
//TODO: technically the actual returned bytes for an error frame encode some more info. Not interpreting it yet.

//now, if we're supposed to interpret the data and the DBC handler is loaded then use it
if ( (dbcHandler != nullptr) && interpretFrames && (frame.frameType() == frame.DataFrame) )
{
DBC_MESSAGE *msg = dbcHandler->findMessage(frame);
if (msg != nullptr)
{
tempString.append(" <" + msg->name + ">\n");
if (msg->comment.length() > 1) tempString.append(msg->comment + "\n");
for (int j = 0; j < msg->sigHandler->getCount(); j++)
{
QString sigString;
DBC_SIGNAL* sig = msg->sigHandler->findSignalByIdx(j);

if ( (sig->multiplexParent == nullptr) && sig->processAsText(frame, sigString))
{
tempString.append(sigString);
tempString.append("\n");
if (sig->isMultiplexor)
{
qDebug() << "Multiplexor. Diving into the tree";
tempString.append(sig->processSignalTree(frame));
}
}
else if (sig->isMultiplexed && overwriteDups) //wasn't in this exact frame but is in the message. Use cached value
{
bool isInteger = false;
if (sig->valType == UNSIGNED_INT || sig->valType == SIGNED_INT) isInteger = true;
tempString.append(sig->makePrettyOutput(sig->cachedValue.toDouble(), sig->cachedValue.toLongLong(), true, isInteger));
tempString.append("\n");
}
}
}
}
}
23 changes: 23 additions & 0 deletions candataitemdelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef CANDATAITEMDELEGATE_H
#define CANDATAITEMDELEGATE_H

#include <QItemDelegate>
#include "dbc/dbchandler.h"
#include "canframemodel.h"

class CanDataItemDelegate : public QItemDelegate
{
public:
using QItemDelegate::QItemDelegate;
CanDataItemDelegate(CANFrameModel *model);

void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;

private:
void buildStringFromFrame(CANFrame frame, QString &tempString) const;
DBCHandler *dbcHandler;
CANFrameModel *model;
};

#endif
106 changes: 41 additions & 65 deletions canframemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ void CANFrameModel::setBytesPerLine(int bpl)
bytesPerLine = bpl;
}

int CANFrameModel::getBytesPerLine()
{
return bytesPerLine;
}

void CANFrameModel::setHexMode(bool mode)
{
if (useHexMode != mode)
Expand All @@ -101,6 +106,11 @@ void CANFrameModel::setHexMode(bool mode)
}
}

bool CANFrameModel::getHexMode()
{
return useHexMode;
}

void CANFrameModel::setTimeStyle(TimeStyle newStyle)
{
if (timeStyle != newStyle)
Expand Down Expand Up @@ -195,6 +205,23 @@ void CANFrameModel::setOverwriteMode(bool mode)
endResetModel();
}

bool CANFrameModel::getOverwriteMode()
{
return overwriteDups;
}

void CANFrameModel::setMarkChangedBytes(bool mcf)
{
beginResetModel();
markChangedBytes = mcf;
endResetModel();
}

bool CANFrameModel::getMarkChangedBytes()
{
return markChangedBytes;
}

void CANFrameModel::setClearMode(bool mode)
{
filtersPersistDuringClear = mode;
Expand Down Expand Up @@ -412,7 +439,6 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const

thisFrame = filteredFrames.at(index.row());

const unsigned char *data = reinterpret_cast<const unsigned char *>(thisFrame.payload().constData());
int dataLen = thisFrame.payload().count();

if (role == Qt::BackgroundRole)
Expand Down Expand Up @@ -516,67 +542,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
}
return tempString;
case Column::Data:
if (dataLen < 0) dataLen = 0;
//if (useHexMode) tempString.append("0x ");
if (thisFrame.frameType() == QCanBusFrame::RemoteRequestFrame) {
return tempString;
}
for (int i = 0; i < dataLen; i++)
{
if (useHexMode) tempString.append( QString::number(data[i], 16).toUpper().rightJustified(2, '0'));
else tempString.append(QString::number(data[i], 10));
if (!((i+1) % bytesPerLine) && (i != (dataLen - 1))) tempString.append("\n");
else tempString.append(" ");
}
if (thisFrame.frameType() == thisFrame.ErrorFrame)
{
if (thisFrame.error() & thisFrame.TransmissionTimeoutError) tempString.append("\nTX Timeout");
if (thisFrame.error() & thisFrame.LostArbitrationError) tempString.append("\nLost Arbitration");
if (thisFrame.error() & thisFrame.ControllerError) tempString.append("\nController Error");
if (thisFrame.error() & thisFrame.ProtocolViolationError) tempString.append("\nProtocol Violation");
if (thisFrame.error() & thisFrame.TransceiverError) tempString.append("\nTransceiver Error");
if (thisFrame.error() & thisFrame.MissingAcknowledgmentError) tempString.append("\nMissing ACK");
if (thisFrame.error() & thisFrame.BusOffError) tempString.append("\nBus OFF");
if (thisFrame.error() & thisFrame.BusError) tempString.append("\nBus ERR");
if (thisFrame.error() & thisFrame.ControllerRestartError) tempString.append("\nController restart err");
if (thisFrame.error() & thisFrame.UnknownError) tempString.append("\nUnknown error type");
}
//TODO: technically the actual returned bytes for an error frame encode some more info. Not interpreting it yet.

//now, if we're supposed to interpret the data and the DBC handler is loaded then use it
if ( (dbcHandler != nullptr) && interpretFrames && (thisFrame.frameType() == thisFrame.DataFrame) )
{
DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame);
if (msg != nullptr)
{
tempString.append(" <" + msg->name + ">\n");
if (msg->comment.length() > 1) tempString.append(msg->comment + "\n");
for (int j = 0; j < msg->sigHandler->getCount(); j++)
{
QString sigString;
DBC_SIGNAL* sig = msg->sigHandler->findSignalByIdx(j);

if ( (sig->multiplexParent == nullptr) && sig->processAsText(thisFrame, sigString))
{
tempString.append(sigString);
tempString.append("\n");
if (sig->isMultiplexor)
{
qDebug() << "Multiplexor. Diving into the tree";
tempString.append(sig->processSignalTree(thisFrame));
}
}
else if (sig->isMultiplexed && overwriteDups) //wasn't in this exact frame but is in the message. Use cached value
{
bool isInteger = false;
if (sig->valType == UNSIGNED_INT || sig->valType == SIGNED_INT) isInteger = true;
tempString.append(sig->makePrettyOutput(sig->cachedValue.toDouble(), sig->cachedValue.toLongLong(), true, isInteger));
tempString.append("\n");
}
}
}
}
return tempString;
return QVariant::fromValue(thisFrame);
default:
return tempString;
}
Expand Down Expand Up @@ -719,10 +685,20 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false)
// }
for (int i = 0; i < filteredFrames.count(); i++)
{
if ( (filteredFrames[i].frameId() == tempFrame.frameId()) && (filteredFrames[i].bus == tempFrame.bus) )
CANFrame currentFrame = filteredFrames[i];
if ( (currentFrame.frameId() == tempFrame.frameId()) && (currentFrame.bus == tempFrame.bus) )
{
tempFrame.frameCount = filteredFrames[i].frameCount + 1;
tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - filteredFrames[i].timeStamp().microSeconds();
tempFrame.frameCount = currentFrame.frameCount + 1;
tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - currentFrame.timeStamp().microSeconds();

// Keep track of changed bytes so we can draw them in a different color in CanDataItemDelegate
QByteArray curPayload = currentFrame.payload();
QByteArray tempPayload = tempFrame.payload();

for (int i = 0; i < curPayload.size() && i < tempPayload.size(); i++) {
tempFrame.changedPayloadBytes |= (curPayload[i] != tempPayload[i]) << i;
}

filteredFrames.replace(i, tempFrame);
found = true;
break;
Expand Down
6 changes: 6 additions & 0 deletions canframemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ class CANFrameModel: public QAbstractTableModel
void setInterpretMode(bool);
bool getInterpretMode();
void setOverwriteMode(bool);
bool getOverwriteMode();
void setMarkChangedBytes(bool);
bool getMarkChangedBytes();
void setHexMode(bool);
bool getHexMode();
void setClearMode(bool mode);
void setTimeStyle(TimeStyle newStyle);
void setIgnoreDBCColors(bool mode);
Expand All @@ -55,6 +59,7 @@ class CANFrameModel: public QAbstractTableModel
void setAllFilters(bool state);
void setTimeFormat(QString);
void setBytesPerLine(int bpl);
int getBytesPerLine();
void loadFilterFile(QString filename);
void saveFilterFile(QString filename);
void normalizeTiming();
Expand Down Expand Up @@ -90,6 +95,7 @@ public slots:
QMutex mutex;
bool interpretFrames; //should we use the dbcHandler?
bool overwriteDups; //should we display all frames or only the newest for each ID?
bool markChangedBytes; //should we highlight bytes that have been changed from the baseline?
bool filtersPersistDuringClear;
QString timeFormat;
TimeStyle timeStyle;
Expand Down
Loading