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
vw-mqb-aeb
parent
745f49b9e0
commit
d2d27243e4
7 changed files with 265 additions and 121 deletions
@ -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(); |
||||||
|
} |
@ -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; |
||||||
|
}; |
Loading…
Reference in new issue