cabana: support display hex bytes in multiple lines. (#27901)
* display hex bytes in multiple lines * default is true * cleanup old-commit-hash: 860e843af6a3afd153790b7d8e3060b9b586299f
This commit is contained in:
@@ -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 ¤t, const QModelIndex &previous) {
|
||||
QObject::connect(view->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, 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(); }
|
||||
|
||||
Reference in New Issue
Block a user