mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-06-20 21:42:05 +08:00
cabana: color bytes based on activity (#26970)
* cabana: color bytes based on activity * newlines * fix text color when selected * fix indent * add colors to binary view * no need to check contains * whitespace * Apply suggestions from code review Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
This commit is contained in:
@@ -177,7 +177,9 @@ void BinaryViewModel::setMessage(const QString &message_id) {
|
||||
|
||||
void BinaryViewModel::updateState() {
|
||||
auto prev_items = items;
|
||||
const auto &binary = can->lastMessage(msg_id).dat;
|
||||
const auto &last_msg = can->lastMessage(msg_id);
|
||||
const auto &binary = last_msg.dat;
|
||||
|
||||
// data size may changed.
|
||||
if (binary.size() > row_count) {
|
||||
beginInsertRows({}, row_count, binary.size() - 1);
|
||||
@@ -193,6 +195,7 @@ void BinaryViewModel::updateState() {
|
||||
hex[0] = toHex(binary[i] >> 4);
|
||||
hex[1] = toHex(binary[i] & 0xf);
|
||||
items[i * column_count + 8].val = hex;
|
||||
items[i * column_count + 8].bg_color = last_msg.colors[i];
|
||||
}
|
||||
for (int i = binary.size(); i < row_count; ++i) {
|
||||
for (int j = 0; j < column_count; ++j) {
|
||||
@@ -201,7 +204,7 @@ void BinaryViewModel::updateState() {
|
||||
}
|
||||
|
||||
for (int i = 0; i < row_count * column_count; ++i) {
|
||||
if (i >= prev_items.size() || prev_items[i].val != items[i].val) {
|
||||
if (i >= prev_items.size() || prev_items[i].val != items[i].val || prev_items[i].bg_color != items[i].bg_color) {
|
||||
auto idx = index(i / column_count, i % column_count);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
@@ -234,6 +237,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
||||
|
||||
if (index.column() == 8) {
|
||||
painter->setFont(hex_font);
|
||||
painter->fillRect(option.rect, item->bg_color);
|
||||
} else if (option.state & QStyle::State_Selected) {
|
||||
painter->fillRect(option.rect, selection_color);
|
||||
painter->setPen(option.palette.color(QPalette::BrightText));
|
||||
|
||||
@@ -18,6 +18,10 @@ static bool event_filter(const Event *e, void *opaque) {
|
||||
return c->eventFilter(e);
|
||||
}
|
||||
|
||||
static QColor blend(QColor a, QColor b) {
|
||||
return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2);
|
||||
}
|
||||
|
||||
bool CANMessages::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) {
|
||||
replay = new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this);
|
||||
replay->setSegmentCacheLimit(settings.cached_segment_limit);
|
||||
@@ -49,6 +53,9 @@ void CANMessages::process(QHash<QString, CanData> *messages) {
|
||||
|
||||
bool CANMessages::eventFilter(const Event *event) {
|
||||
static std::unique_ptr new_msgs = std::make_unique<QHash<QString, CanData>>();
|
||||
static QHash<QString, QByteArray> prev_dat;
|
||||
static QHash<QString, QList<QColor>> colors;
|
||||
static QHash<QString, QList<double>> last_change_t;
|
||||
static double prev_update_ts = 0;
|
||||
|
||||
if (event->which == cereal::Event::Which::CAN) {
|
||||
@@ -71,6 +78,59 @@ bool CANMessages::eventFilter(const Event *event) {
|
||||
if (double delta = (current_sec - counters_begin_sec); delta > 0) {
|
||||
data.freq = data.count / delta;
|
||||
}
|
||||
|
||||
// Init colors
|
||||
if (colors[id].size() != data.dat.size()) {
|
||||
colors[id].clear();
|
||||
for (int i = 0; i < data.dat.size(); i++){
|
||||
colors[id].append(QColor(0, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Init last_change_t
|
||||
if (last_change_t[id].size() != data.dat.size()) {
|
||||
last_change_t[id].clear();
|
||||
for (int i = 0; i < data.dat.size(); i++){
|
||||
last_change_t[id].append(data.ts);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute background color for the bytes based on changes
|
||||
if (prev_dat[id].size() == data.dat.size()) {
|
||||
for (int i = 0; i < data.dat.size(); i++){
|
||||
uint8_t last = prev_dat[id][i];
|
||||
uint8_t cur = data.dat[i];
|
||||
|
||||
const int periodic_threshold = 10;
|
||||
const int start_alpha = 128;
|
||||
const float fade_time = 2.0;
|
||||
|
||||
if (last != cur) {
|
||||
double delta_t = data.ts - last_change_t[id][i];
|
||||
|
||||
if (delta_t * data.freq > periodic_threshold) {
|
||||
// Last change was while ago, choose color based on delta up or down
|
||||
if (cur > last) {
|
||||
colors[id][i] = QColor(0, 187, 255, start_alpha); // Cyan
|
||||
} else {
|
||||
colors[id][i] = QColor(255, 0, 0, start_alpha); // Red
|
||||
}
|
||||
} else {
|
||||
// Periodic changes
|
||||
colors[id][i] = blend(colors[id][i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue
|
||||
}
|
||||
|
||||
last_change_t[id][i] = data.ts;
|
||||
} else {
|
||||
// Fade out
|
||||
float alpha_delta = 1.0 / (data.freq + 1) / fade_time;
|
||||
colors[id][i].setAlphaF(std::max(0.0, colors[id][i].alphaF() - alpha_delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.colors = colors[id];
|
||||
prev_dat[id] = data.dat;
|
||||
}
|
||||
|
||||
double ts = millis_since_boot();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <QColor>
|
||||
#include <QHash>
|
||||
#include <QApplication>
|
||||
|
||||
#include "opendbc/can/common_dbc.h"
|
||||
#include "tools/cabana/settings.h"
|
||||
@@ -16,6 +17,7 @@ struct CanData {
|
||||
uint32_t count = 0;
|
||||
uint32_t freq = 0;
|
||||
QByteArray dat;
|
||||
QList<QColor> colors;
|
||||
};
|
||||
|
||||
class CANMessages : public QObject {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <QHeaderView>
|
||||
#include <QLineEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPainter>
|
||||
#include <QApplication>
|
||||
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
@@ -21,6 +23,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
table_widget = new QTableView(this);
|
||||
model = new MessageListModel(this);
|
||||
table_widget->setModel(model);
|
||||
table_widget->setItemDelegateForColumn(4, new MessageBytesDelegate(table_widget));
|
||||
table_widget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
table_widget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
table_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
@@ -31,6 +34,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
table_widget->setColumnWidth(2, 80);
|
||||
table_widget->horizontalHeader()->setStretchLastSection(true);
|
||||
table_widget->verticalHeader()->hide();
|
||||
|
||||
main_layout->addWidget(table_widget);
|
||||
|
||||
// signals/slots
|
||||
@@ -65,9 +69,10 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
|
||||
}
|
||||
|
||||
QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
const auto &id = msgs[index.row()];
|
||||
auto &can_data = can->lastMessage(id);
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
const auto &id = msgs[index.row()];
|
||||
auto &can_data = can->lastMessage(id);
|
||||
switch (index.column()) {
|
||||
case 0: return msgName(id);
|
||||
case 1: return id;
|
||||
@@ -77,6 +82,13 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
}
|
||||
} else if (role == Qt::FontRole && index.column() == columnCount() - 1) {
|
||||
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
} else if (role == Qt::UserRole && index.column() == 4) {
|
||||
|
||||
QList<QVariant> colors;
|
||||
for (int i = 0; i < can_data.dat.size(); i++){
|
||||
colors.append(can_data.colors[i]);
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -146,3 +158,40 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
|
||||
sortMessages();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) {
|
||||
}
|
||||
|
||||
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
QList<QVariant> colors = index.data(Qt::UserRole).toList();
|
||||
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
|
||||
const QFont font = index.data(Qt::FontRole).value<QFont>();
|
||||
painter->setFont(font);
|
||||
|
||||
QRect rect = opt.rect;
|
||||
QString bytes = QString(opt.text);
|
||||
|
||||
QRect pos = rect;
|
||||
QRect space = painter->boundingRect(pos, opt.displayAlignment, " ");
|
||||
pos.setX(pos.x() + space.width());
|
||||
|
||||
if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) {
|
||||
painter->setPen(option.palette.color(QPalette::HighlightedText));
|
||||
} else {
|
||||
painter->setPen(option.palette.color(QPalette::Text));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (auto &byte : bytes.split(" ")) {
|
||||
QRect sz = painter->boundingRect(pos, opt.displayAlignment, byte);
|
||||
const int m = space.width() / 2;
|
||||
painter->fillRect(sz.marginsAdded(QMargins(m + 1, m, m, m)), colors[i].value<QColor>());
|
||||
painter->drawText(pos, opt.displayAlignment, byte);
|
||||
pos.setX(pos.x() + sz.width() + space.width());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QTableView>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "tools/cabana/canmessages.h"
|
||||
|
||||
@@ -41,3 +42,10 @@ protected:
|
||||
QString current_msg_id;
|
||||
MessageListModel *model;
|
||||
};
|
||||
|
||||
class MessageBytesDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessageBytesDelegate(QObject *parent);
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user