cabana: support display hex bytes in multiple lines. (#27901)

* display hex bytes in multiple lines

* default is true

* cleanup
old-commit-hash: 860e843af6
beeps
Dean Lee 2 years ago committed by GitHub
parent 9ab3f160fb
commit 3e13b0be9d
  1. 70
      tools/cabana/messageswidget.cc
  2. 18
      tools/cabana/messageswidget.h
  3. 2
      tools/cabana/settings.cc
  4. 1
      tools/cabana/settings.h
  5. 48
      tools/cabana/util.cc
  6. 10
      tools/cabana/util.h

@ -1,6 +1,7 @@
#include "tools/cabana/messageswidget.h"
#include <QHBoxLayout>
#include <QPainter>
#include <QPushButton>
#include <QVBoxLayout>
@ -9,29 +10,30 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
main_layout->setContentsMargins(0 ,0, 0, 0);
// message filter
filter = new QLineEdit(this);
QHBoxLayout *title_layout = new QHBoxLayout();
title_layout->addWidget(filter = new QLineEdit(this));
QRegularExpression re("\\S+");
filter->setValidator(new QRegularExpressionValidator(re, this));
filter->setClearButtonEnabled(true);
filter->setPlaceholderText(tr("filter messages"));
main_layout->addWidget(filter);
title_layout->addWidget(multiple_lines_bytes = new QCheckBox(tr("Multiple Lines Bytes"), this));
multiple_lines_bytes->setToolTip(tr("Display bytes in multiple lines"));
multiple_lines_bytes->setChecked(settings.multiple_lines_bytes);
main_layout->addLayout(title_layout);
// message table
table_widget = new QTableView(this);
view = new MessageView(this);
model = new MessageListModel(this);
table_widget->setModel(model);
table_widget->setItemDelegateForColumn(5, new MessageBytesDelegate(table_widget));
table_widget->setSelectionBehavior(QAbstractItemView::SelectRows);
table_widget->setSelectionMode(QAbstractItemView::SingleSelection);
table_widget->setSortingEnabled(true);
table_widget->sortByColumn(0, Qt::AscendingOrder);
table_widget->setColumnWidth(0, 150);
table_widget->setColumnWidth(1, 50);
table_widget->setColumnWidth(2, 50);
table_widget->setColumnWidth(3, 50);
table_widget->horizontalHeader()->setStretchLastSection(true);
table_widget->verticalHeader()->hide();
main_layout->addWidget(table_widget);
auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes);
view->setItemDelegateForColumn(5, delegate);
view->setModel(model);
view->setSortingEnabled(true);
view->sortByColumn(0, Qt::AscendingOrder);
view->setItemsExpandable(false);
view->setIndentation(0);
view->setRootIsDecorated(false);
view->header()->setStretchLastSection(true);
main_layout->addWidget(view);
// suppress
QHBoxLayout *suppress_layout = new QHBoxLayout();
@ -43,6 +45,11 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
// signals/slots
QObject::connect(filter, &QLineEdit::textEdited, model, &MessageListModel::setFilterString);
QObject::connect(multiple_lines_bytes, &QCheckBox::stateChanged, [=](int state) {
settings.multiple_lines_bytes = (state == Qt::Checked);
delegate->setMultipleLines(settings.multiple_lines_bytes);
model->sortMessages();
});
QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(can, &AbstractStream::streamStarted, this, &MessagesWidget::reset);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages);
@ -53,7 +60,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
selectMessage(*current_msg_id);
}
});
QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
QObject::connect(view->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
if (current.isValid() && current.row() < model->msgs.size()) {
auto &id = model->msgs[current.row()];
if (!current_msg_id || id != *current_msg_id) {
@ -85,7 +92,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
void MessagesWidget::selectMessage(const MessageId &msg_id) {
if (int row = model->msgs.indexOf(msg_id); row != -1) {
table_widget->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
view->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
}
}
@ -101,7 +108,7 @@ void MessagesWidget::updateSuppressedButtons() {
void MessagesWidget::reset() {
current_msg_id = std::nullopt;
table_widget->selectionModel()->clear();
view->selectionModel()->clear();
model->reset();
filter->clear();
updateSuppressedButtons();
@ -111,8 +118,10 @@ void MessagesWidget::reset() {
// MessageListModel
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return (QString[]){"Name", "Bus", "ID", "Freq", "Count", "Bytes"}[section];
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
static const QString titles[] = {"Name", "Bus", "ID", "Freq", "Count", "Bytes"};
return titles[section];
}
return {};
}
@ -254,3 +263,22 @@ void MessageListModel::reset() {
clearSuppress();
endResetModel();
}
// MessageView
void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QTreeView::drawRow(painter, option, index);
painter->save();
const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
const QColor gridColor = QColor::fromRgba(static_cast<QRgb>(gridHint));
painter->setPen(gridColor);
painter->drawLine(option.rect.left(), option.rect.bottom(), option.rect.right(), option.rect.bottom());
auto y = option.rect.y();
painter->translate(visualRect(model()->index(0, 0)).x() - indentation() - .5, -.5);
for (int i = 0; i < header()->count(); ++i) {
painter->translate(header()->sectionSize(i), 0);
painter->drawLine(0, y, 0, y + option.rect.height());
}
painter->restore();
}

@ -1,10 +1,11 @@
#pragma once
#include <QAbstractTableModel>
#include <QCheckBox>
#include <QHeaderView>
#include <QLineEdit>
#include <QSet>
#include <QTableView>
#include <QTreeView>
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
@ -34,14 +35,21 @@ private:
Qt::SortOrder sort_order = Qt::AscendingOrder;
};
class MessageView : public QTreeView {
Q_OBJECT
public:
MessageView(QWidget *parent) : QTreeView(parent) {}
void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
class MessagesWidget : public QWidget {
Q_OBJECT
public:
MessagesWidget(QWidget *parent);
void selectMessage(const MessageId &message_id);
QByteArray saveHeaderState() const { return table_widget->horizontalHeader()->saveState(); }
bool restoreHeaderState(const QByteArray &state) const { return table_widget->horizontalHeader()->restoreState(state); }
QByteArray saveHeaderState() const { return view->header()->saveState(); }
bool restoreHeaderState(const QByteArray &state) const { return view->header()->restoreState(state); }
void updateSuppressedButtons();
void reset();
@ -49,11 +57,11 @@ signals:
void msgSelectionChanged(const MessageId &message_id);
protected:
QTableView *table_widget;
MessageView *view;
std::optional<MessageId> current_msg_id;
QLineEdit *filter;
QCheckBox *multiple_lines_bytes;
MessageListModel *model;
QPushButton *suppress_add;
QPushButton *suppress_clear;
};

@ -31,6 +31,7 @@ void Settings::save() {
s.setValue("chart_series_type", chart_series_type);
s.setValue("theme", theme);
s.setValue("sparkline_range", sparkline_range);
s.setValue("multiple_lines_bytes", multiple_lines_bytes);
}
void Settings::load() {
@ -50,6 +51,7 @@ void Settings::load() {
chart_series_type = s.value("chart_series_type", 0).toInt();
theme = s.value("theme", 0).toInt();
sparkline_range = s.value("sparkline_range", 15).toInt();
multiple_lines_bytes = s.value("multiple_lines_bytes", true).toBool();
}
// SettingsDlg

@ -24,6 +24,7 @@ public:
int chart_series_type = 0;
int theme = 0;
int sparkline_range = 15; // 15 seconds
bool multiple_lines_bytes = true;
QString last_dir;
QString last_route_dir;
QByteArray geometry;

@ -41,9 +41,35 @@ std::pair<double, double> SegmentTree::get_minmax(int n, int left, int right, in
// MessageBytesDelegate
MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) {
MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) : multiple_lines(multiple_lines), QStyledItemDelegate(parent) {
fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
byte_width = QFontMetrics(fixed_font).width("00 ");
byte_size = QFontMetrics(fixed_font).size(Qt::TextSingleLine, "00 ") + QSize(0, 2);
}
void MessageBytesDelegate::setMultipleLines(bool v) {
if (std::exchange(multiple_lines, v) != multiple_lines) {
std::fill_n(size_cache, std::size(size_cache), QSize{});
}
}
QSize MessageBytesDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
int n = index.data(BytesRole).toByteArray().size();
if (n <= 0 || n > 64) return {};
QSize size = size_cache[n - 1];
if (size.isEmpty()) {
int h_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
int v_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 1;
if (!multiple_lines) {
size.setWidth(h_margin * 2 + n * byte_size.width());
size.setHeight(byte_size.height() + 2 * v_margin);
} else {
size.setWidth(h_margin * 2 + 8 * byte_size.width());
size.setHeight(byte_size.height() * std::max(1, n / 8) + 2 * v_margin);
}
size_cache[n - 1] = size;
}
return size;
}
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
@ -52,18 +78,24 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
QRect rc{option.rect.left() + h_margin, option.rect.top() + v_margin, byte_width, option.rect.height() - 2 * v_margin};
painter->save();
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
painter->setPen(option.palette.color(QPalette::HighlightedText));
}
auto color_role = option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text;
painter->setPen(option.palette.color(color_role));
const QPoint pt{option.rect.left() + h_margin, option.rect.top() + v_margin};
painter->setFont(fixed_font);
for (int i = 0; i < byte_list.size(); ++i) {
int row = !multiple_lines ? 0 : i / 8;
int column = !multiple_lines ? i : i % 8;
QRect r = QRect({pt.x() + column * byte_size.width(), pt.y() + row * byte_size.height()}, byte_size);
if (i < colors.size() && colors[i].alpha() > 0) {
painter->fillRect(rc, colors[i]);
painter->fillRect(r, colors[i]);
}
painter->drawText(rc, Qt::AlignCenter, toHex(byte_list[i]));
rc.moveLeft(rc.right() + 1);
painter->drawText(r, Qt::AlignCenter, toHex(byte_list[i]));
}
painter->restore();
}
QColor getColor(const cabana::Signal *sig) {

@ -63,10 +63,16 @@ private:
class MessageBytesDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
MessageBytesDelegate(QObject *parent);
MessageBytesDelegate(QObject *parent, bool multiple_lines = false);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setMultipleLines(bool v);
private:
QFont fixed_font;
int byte_width;
QSize byte_size = {};
bool multiple_lines = false;
mutable QSize size_cache[64] = {};
};
inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); }

Loading…
Cancel
Save