mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-19 03:03:57 +08:00
Cabana: show MSB/LSB of signals in the BinaryView (#26103)
* dispaly msb and lsb in binary view
* draw at bottom
* move binaryview to seperate files
* increase the width of vertical header
* re-draw changed cells only
* correct lsb/msb position
* check bounds
* todo
old-commit-hash: 99b16151fc
This commit is contained in:
@@ -12,5 +12,5 @@ else:
|
||||
|
||||
qt_libs = ['qt_util', 'Qt5Charts'] + base_libs
|
||||
cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs
|
||||
qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
|
||||
qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
|
||||
'canmessages.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
|
||||
184
tools/cabana/binaryview.cc
Normal file
184
tools/cabana/binaryview.cc
Normal file
@@ -0,0 +1,184 @@
|
||||
#include "tools/cabana/binaryview.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QHeaderView>
|
||||
#include <QPainter>
|
||||
|
||||
#include "tools/cabana/canmessages.h"
|
||||
|
||||
// BinaryView
|
||||
|
||||
const int CELL_HEIGHT = 35;
|
||||
|
||||
BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
|
||||
model = new BinaryViewModel(this);
|
||||
setModel(model);
|
||||
setItemDelegate(new BinaryItemDelegate(this));
|
||||
horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
horizontalHeader()->hide();
|
||||
verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
// replace selection model
|
||||
auto old_model = selectionModel();
|
||||
setSelectionModel(new BinarySelectionModel(model));
|
||||
delete old_model;
|
||||
|
||||
QObject::connect(model, &QAbstractItemModel::modelReset, [this]() {
|
||||
setFixedHeight((CELL_HEIGHT + 1) * std::min(model->rowCount(), 8) + 2);
|
||||
});
|
||||
}
|
||||
|
||||
void BinaryView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
QTableView::mouseReleaseEvent(event);
|
||||
|
||||
if (auto indexes = selectedIndexes(); !indexes.isEmpty()) {
|
||||
int start_bit = indexes.first().row() * 8 + indexes.first().column();
|
||||
int size = indexes.back().row() * 8 + indexes.back().column() - start_bit + 1;
|
||||
emit cellsSelected(start_bit, size);
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryView::setMessage(const QString &message_id) {
|
||||
msg_id = message_id;
|
||||
model->setMessage(message_id);
|
||||
resizeRowsToContents();
|
||||
clearSelection();
|
||||
updateState();
|
||||
}
|
||||
|
||||
void BinaryView::updateState() {
|
||||
model->updateState();
|
||||
}
|
||||
|
||||
// BinaryViewModel
|
||||
|
||||
void BinaryViewModel::setMessage(const QString &message_id) {
|
||||
msg_id = message_id;
|
||||
|
||||
beginResetModel();
|
||||
items.clear();
|
||||
row_count = 0;
|
||||
|
||||
dbc_msg = dbc()->msg(msg_id);
|
||||
if (dbc_msg) {
|
||||
row_count = dbc_msg->size;
|
||||
items.resize(row_count * column_count);
|
||||
for (int i = 0; i < dbc_msg->sigs.size(); ++i) {
|
||||
const auto &sig = dbc_msg->sigs[i];
|
||||
const int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit);
|
||||
const int end = start + sig.size - 1;
|
||||
for (int j = start; j <= end; ++j) {
|
||||
int idx = column_count * (j / 8) + j % 8;
|
||||
if (idx >= items.size()) {
|
||||
qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size;
|
||||
break;
|
||||
}
|
||||
if (j == start) {
|
||||
sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
|
||||
} else if (j == end) {
|
||||
sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true;
|
||||
}
|
||||
items[idx].bg_color = QColor(getColor(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QModelIndex BinaryViewModel::index(int row, int column, const QModelIndex &parent) const {
|
||||
return createIndex(row, column, (void *)&items[row * column_count + column]);
|
||||
}
|
||||
|
||||
Qt::ItemFlags BinaryViewModel::flags(const QModelIndex &index) const {
|
||||
return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
void BinaryViewModel::updateState() {
|
||||
auto prev_items = items;
|
||||
|
||||
const auto &binary = can->lastMessage(msg_id).dat;
|
||||
// data size may changed.
|
||||
if (!dbc_msg && binary.size() != row_count) {
|
||||
beginResetModel();
|
||||
row_count = binary.size();
|
||||
items.clear();
|
||||
items.resize(row_count * column_count);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
char hex[3] = {'\0'};
|
||||
for (int i = 0; i < std::min(binary.size(), row_count); ++i) {
|
||||
for (int j = 0; j < column_count - 1; ++j) {
|
||||
items[i * column_count + j].val = QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0');
|
||||
}
|
||||
hex[0] = toHex(binary[i] >> 4);
|
||||
hex[1] = toHex(binary[i] & 0xf);
|
||||
items[i * column_count + 8].val = hex;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.size(); ++i) {
|
||||
if (i >= prev_items.size() || prev_items[i].val != items[i].val) {
|
||||
auto idx = index(i / column_count, i % column_count);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (orientation == Qt::Vertical) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: return section + 1;
|
||||
case Qt::SizeHintRole: return QSize(30, CELL_HEIGHT);
|
||||
case Qt::TextAlignmentRole: return Qt::AlignCenter;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// BinarySelectionModel
|
||||
|
||||
void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) {
|
||||
QItemSelection new_selection = selection;
|
||||
if (auto indexes = selection.indexes(); !indexes.isEmpty()) {
|
||||
auto [begin_idx, end_idx] = (QModelIndex[]){indexes.first(), indexes.back()};
|
||||
for (int row = begin_idx.row(); row <= end_idx.row(); ++row) {
|
||||
int left_col = (row == begin_idx.row()) ? begin_idx.column() : 0;
|
||||
int right_col = (row == end_idx.row()) ? end_idx.column() : 7;
|
||||
new_selection.merge({model()->index(row, left_col), model()->index(row, right_col)}, command);
|
||||
}
|
||||
}
|
||||
QItemSelectionModel::select(new_selection, command);
|
||||
}
|
||||
|
||||
// BinaryItemDelegate
|
||||
|
||||
BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
|
||||
// cache fonts and color
|
||||
small_font.setPointSize(7);
|
||||
bold_font.setBold(true);
|
||||
highlight_color = QApplication::style()->standardPalette().color(QPalette::Active, QPalette::Highlight);
|
||||
}
|
||||
|
||||
QSize BinaryItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
QSize sz = QStyledItemDelegate::sizeHint(option, index);
|
||||
return {sz.width(), CELL_HEIGHT};
|
||||
}
|
||||
|
||||
void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
auto item = (const BinaryViewModel::Item *)index.internalPointer();
|
||||
painter->save();
|
||||
// TODO: highlight signal cells on mouse over
|
||||
painter->fillRect(option.rect, option.state & QStyle::State_Selected ? highlight_color : item->bg_color);
|
||||
if (index.column() == 8) {
|
||||
painter->setFont(bold_font);
|
||||
}
|
||||
painter->drawText(option.rect, Qt::AlignCenter, item->val);
|
||||
if (item->is_msb || item->is_lsb) {
|
||||
painter->setFont(small_font);
|
||||
painter->drawText(option.rect, Qt::AlignHCenter | Qt::AlignBottom, item->is_msb ? "MSB" : "LSB");
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
72
tools/cabana/binaryview.h
Normal file
72
tools/cabana/binaryview.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QTableView>
|
||||
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
class BinaryItemDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BinaryItemDelegate(QObject *parent);
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
|
||||
private:
|
||||
QFont small_font, bold_font;
|
||||
QColor highlight_color;
|
||||
};
|
||||
|
||||
class BinaryViewModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {}
|
||||
void setMessage(const QString &message_id);
|
||||
void updateState();
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; }
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; }
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; }
|
||||
|
||||
struct Item {
|
||||
QColor bg_color = QColor(Qt::white);
|
||||
bool is_msb = false;
|
||||
bool is_lsb = false;
|
||||
QString val = "0";
|
||||
};
|
||||
|
||||
private:
|
||||
QString msg_id;
|
||||
const Msg *dbc_msg;
|
||||
int row_count = 0;
|
||||
const int column_count = 9;
|
||||
std::vector<Item> items;
|
||||
};
|
||||
|
||||
// the default QItemSelectionModel does not support our selection mode.
|
||||
class BinarySelectionModel : public QItemSelectionModel {
|
||||
public:
|
||||
BinarySelectionModel(QAbstractItemModel *model = nullptr) : QItemSelectionModel(model) {}
|
||||
void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override;
|
||||
};
|
||||
|
||||
class BinaryView : public QTableView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BinaryView(QWidget *parent = nullptr);
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void setMessage(const QString &message_id);
|
||||
void updateState();
|
||||
|
||||
signals:
|
||||
void cellsSelected(int start_bit, int size);
|
||||
|
||||
private:
|
||||
QString msg_id;
|
||||
BinaryViewModel *model;
|
||||
};
|
||||
@@ -91,6 +91,7 @@ inline char toHex(uint value) {
|
||||
}
|
||||
|
||||
inline const QString &getColor(int i) {
|
||||
// TODO: add more colors
|
||||
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"};
|
||||
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)];
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "tools/cabana/detailwidget.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFontDatabase>
|
||||
#include <QFormLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QMessageBox>
|
||||
#include <QScrollBar>
|
||||
#include <QTimer>
|
||||
|
||||
#include "tools/cabana/canmessages.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
// DetailWidget
|
||||
|
||||
DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) {
|
||||
@@ -141,98 +141,6 @@ void DetailWidget::removeSignal() {
|
||||
}
|
||||
}
|
||||
|
||||
// BinaryView
|
||||
|
||||
BinaryView::BinaryView(QWidget *parent) : QTableWidget(parent) {
|
||||
horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
horizontalHeader()->hide();
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setColumnCount(9);
|
||||
|
||||
// replace selection model
|
||||
auto old_model = selectionModel();
|
||||
setSelectionModel(new BinarySelectionModel(model()));
|
||||
delete old_model;
|
||||
}
|
||||
|
||||
void BinaryView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
QTableWidget::mouseReleaseEvent(event);
|
||||
|
||||
if (auto items = selectedItems(); !items.isEmpty()) {
|
||||
int start_bit = items.first()->row() * 8 + items.first()->column();
|
||||
int size = items.back()->row() * 8 + items.back()->column() - start_bit + 1;
|
||||
emit cellsSelected(start_bit, size);
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryView::setMessage(const QString &message_id) {
|
||||
msg_id = message_id;
|
||||
if (msg_id.isEmpty()) return;
|
||||
|
||||
const Msg *msg = dbc()->msg(msg_id);
|
||||
int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size();
|
||||
setRowCount(row_count);
|
||||
setColumnCount(9);
|
||||
for (int i = 0; i < rowCount(); ++i) {
|
||||
for (int j = 0; j < columnCount(); ++j) {
|
||||
auto item = new QTableWidgetItem();
|
||||
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
|
||||
item->setTextAlignment(Qt::AlignCenter);
|
||||
if (j == 8) {
|
||||
QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
font.setBold(true);
|
||||
item->setFont(font);
|
||||
item->setFlags(item->flags() ^ Qt::ItemIsSelectable);
|
||||
}
|
||||
setItem(i, j, item);
|
||||
}
|
||||
}
|
||||
|
||||
// set background color
|
||||
if (msg) {
|
||||
for (int i = 0; i < msg->sigs.size(); ++i) {
|
||||
const auto &sig = msg->sigs[i];
|
||||
int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit);
|
||||
for (int j = start; j <= std::min(start + sig.size - 1, rowCount() * columnCount() - 1); ++j) {
|
||||
item(j / 8, j % 8)->setBackground(QColor(getColor(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFixedHeight(rowHeight(0) * std::min(row_count, 8) + 2);
|
||||
clearSelection();
|
||||
updateState();
|
||||
}
|
||||
|
||||
void BinaryView::updateState() {
|
||||
const auto &binary = can->lastMessage(msg_id).dat;
|
||||
setUpdatesEnabled(false);
|
||||
char hex[3] = {'\0'};
|
||||
for (int i = 0; i < binary.size(); ++i) {
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
item(i, j)->setText(QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0'));
|
||||
}
|
||||
hex[0] = toHex(binary[i] >> 4);
|
||||
hex[1] = toHex(binary[i] & 0xf);
|
||||
item(i, 8)->setText(hex);
|
||||
}
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) {
|
||||
QItemSelection new_selection = selection;
|
||||
if (auto indexes = selection.indexes(); !indexes.isEmpty()) {
|
||||
auto [begin_idx, end_idx] = (QModelIndex[]){indexes.first(), indexes.back()};
|
||||
for (int row = begin_idx.row(); row <= end_idx.row(); ++row) {
|
||||
int left_col = (row == begin_idx.row()) ? begin_idx.column() : 0;
|
||||
int right_col = (row == end_idx.row()) ? end_idx.column() : 7;
|
||||
new_selection.merge({model()->index(row, left_col), model()->index(row, right_col)}, command);
|
||||
}
|
||||
}
|
||||
QItemSelectionModel::select(new_selection, command);
|
||||
}
|
||||
|
||||
// EditMessageDialog
|
||||
|
||||
EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) {
|
||||
|
||||
@@ -1,35 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <QTableWidget>
|
||||
|
||||
#include "opendbc/can/common.h"
|
||||
#include "opendbc/can/common_dbc.h"
|
||||
#include "tools/cabana/canmessages.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
#include "tools/cabana/binaryview.h"
|
||||
#include "tools/cabana/historylog.h"
|
||||
#include "tools/cabana/signaledit.h"
|
||||
|
||||
class BinarySelectionModel : public QItemSelectionModel {
|
||||
public:
|
||||
BinarySelectionModel(QAbstractItemModel *model = nullptr) : QItemSelectionModel(model) {}
|
||||
void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override;
|
||||
};
|
||||
|
||||
class BinaryView : public QTableWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BinaryView(QWidget *parent = nullptr);
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void setMessage(const QString &message_id);
|
||||
void updateState();
|
||||
signals:
|
||||
void cellsSelected(int start_bit, int size);
|
||||
|
||||
private:
|
||||
QString msg_id;
|
||||
};
|
||||
|
||||
class EditMessageDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start
|
||||
endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1);
|
||||
form_layout->addRow(tr("Endianness"), endianness);
|
||||
|
||||
form_layout->addRow(tr("lsb"), new QLabel(QString::number(sig.lsb)));
|
||||
form_layout->addRow(tr("msb"), new QLabel(QString::number(sig.msb)));
|
||||
|
||||
sign = new QComboBox();
|
||||
sign->addItems({"Signed", "Unsigned"});
|
||||
sign->setCurrentIndex(sig.is_signed ? 0 : 1);
|
||||
|
||||
Reference in New Issue
Block a user