mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-30 19:12:07 +08:00
cabana: add Node column to message table (#30317)
* add Node column * add views menu to toolbar * match NODE * rename fetchData to filterAndSort * simplify sortMessages old-commit-hash: 691c7c2c66785e9b06aae02bf278e343b8c9397b
This commit is contained in:
+88
-131
@@ -1,9 +1,6 @@
|
||||
#include "tools/cabana/messageswidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QPainter>
|
||||
@@ -13,34 +10,24 @@
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
|
||||
MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
static QString msg_node_from_id(const MessageId &id) {
|
||||
auto msg = dbc()->msg(id);
|
||||
return msg ? msg->transmitter : QString();
|
||||
}
|
||||
|
||||
MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QHBoxLayout *title_layout = new QHBoxLayout();
|
||||
num_msg_label = new QLabel(this);
|
||||
title_layout->addSpacing(10);
|
||||
title_layout->addWidget(num_msg_label);
|
||||
|
||||
title_layout->addStretch();
|
||||
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);
|
||||
QPushButton *clear_filters = new QPushButton(tr("&Clear Filters"));
|
||||
clear_filters->setEnabled(false);
|
||||
title_layout->addWidget(clear_filters);
|
||||
main_layout->addLayout(title_layout);
|
||||
|
||||
main_layout->setSpacing(0);
|
||||
// toolbar
|
||||
main_layout->addWidget(createToolBar());
|
||||
// message table
|
||||
view = new MessageView(this);
|
||||
model = new MessageListModel(this);
|
||||
header = new MessageViewHeader(this);
|
||||
auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes);
|
||||
|
||||
view->setItemDelegate(delegate);
|
||||
view->setItemDelegate(delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes));
|
||||
view->setHeader(header);
|
||||
view->setModel(model);
|
||||
view->setHeader(header);
|
||||
view->setSortingEnabled(true);
|
||||
view->sortByColumn(MessageListModel::Column::NAME, Qt::AscendingOrder);
|
||||
view->setAllColumnsShowFocus(true);
|
||||
@@ -51,13 +38,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
// Must be called before setting any header parameters to avoid overriding
|
||||
restoreHeaderState(settings.message_header_state);
|
||||
view->header()->setSectionsMovable(true);
|
||||
view->header()->setSectionResizeMode(MessageListModel::Column::DATA, QHeaderView::Fixed);
|
||||
view->header()->setStretchLastSection(true);
|
||||
|
||||
// Header context menu
|
||||
view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
QObject::connect(view->header(), &QHeaderView::customContextMenuRequested, view, &MessageView::headerContextMenuEvent);
|
||||
header->setSectionsMovable(true);
|
||||
header->setSectionResizeMode(MessageListModel::Column::DATA, QHeaderView::Fixed);
|
||||
header->setStretchLastSection(true);
|
||||
header->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
main_layout->addWidget(view);
|
||||
|
||||
@@ -73,20 +57,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
main_layout->addLayout(suppress_layout);
|
||||
|
||||
// signals/slots
|
||||
QObject::connect(menu, &QMenu::aboutToShow, this, &MessagesWidget::menuAboutToShow);
|
||||
QObject::connect(header, &MessageViewHeader::filtersUpdated, model, &MessageListModel::setFilterStrings);
|
||||
QObject::connect(header, &MessageViewHeader::filtersUpdated, [=](const QMap<int, QString> &filters) {
|
||||
clear_filters->setEnabled(!filters.isEmpty());
|
||||
});
|
||||
QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent);
|
||||
QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions);
|
||||
QObject::connect(clear_filters, &QPushButton::clicked, header, &MessageViewHeader::clearFilters);
|
||||
QObject::connect(multiple_lines_bytes, &QCheckBox::stateChanged, [=](int state) {
|
||||
settings.multiple_lines_bytes = (state == Qt::Checked);
|
||||
delegate->setMultipleLines(settings.multiple_lines_bytes);
|
||||
view->setUniformRowHeights(!settings.multiple_lines_bytes);
|
||||
|
||||
// Reset model to force recalculation of the width of the bytes column
|
||||
model->forceResetModel();
|
||||
});
|
||||
QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, [=](int state) {
|
||||
settings.suppress_defined_signals = (state == Qt::Checked);
|
||||
emit settings.changed();
|
||||
@@ -130,6 +104,21 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
)"));
|
||||
}
|
||||
|
||||
QToolBar *MessagesWidget::createToolBar() {
|
||||
QToolBar *toolbar = new QToolBar(this);
|
||||
toolbar->setIconSize({12, 12});
|
||||
toolbar->addWidget(num_msg_label = new QLabel(this));
|
||||
num_msg_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
|
||||
auto views_btn = toolbar->addAction(utils::icon("three-dots"), tr("View..."));
|
||||
views_btn->setMenu(menu);
|
||||
auto view_button = qobject_cast<QToolButton *>(toolbar->widgetForAction(views_btn));
|
||||
view_button->setPopupMode(QToolButton::InstantPopup);
|
||||
view_button->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
view_button->setStyleSheet("QToolButton::menu-indicator { image: none; }");
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
void MessagesWidget::dbcModified() {
|
||||
num_msg_label->setText(tr("%1 Messages, %2 Signals").arg(dbc()->msgCount()).arg(dbc()->signalCount()));
|
||||
model->dbcModified();
|
||||
@@ -152,6 +141,34 @@ void MessagesWidget::updateSuppressedButtons() {
|
||||
}
|
||||
}
|
||||
|
||||
void MessagesWidget::headerContextMenuEvent(const QPoint &pos) {
|
||||
menu->exec(header->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void MessagesWidget::menuAboutToShow() {
|
||||
menu->clear();
|
||||
for (int i = 0; i < header->count(); ++i) {
|
||||
int logical_index = header->logicalIndex(i);
|
||||
auto action = menu->addAction(model->headerData(logical_index, Qt::Horizontal).toString(),
|
||||
[=](bool checked) { header->setSectionHidden(logical_index, !checked); });
|
||||
action->setCheckable(true);
|
||||
action->setChecked(!header->isSectionHidden(logical_index));
|
||||
// Can't hide the name column
|
||||
action->setEnabled(logical_index > 0);
|
||||
}
|
||||
menu->addSeparator();
|
||||
auto action = menu->addAction(tr("Mutlti-Line bytes"), this, &MessagesWidget::setMultiLineBytes);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(settings.multiple_lines_bytes);
|
||||
}
|
||||
|
||||
void MessagesWidget::setMultiLineBytes(bool multi) {
|
||||
settings.multiple_lines_bytes = multi;
|
||||
delegate->setMultipleLines(multi);
|
||||
view->updateBytesSectionSize();
|
||||
view->doItemsLayout();
|
||||
}
|
||||
|
||||
// MessageListModel
|
||||
|
||||
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
@@ -160,6 +177,7 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
|
||||
case Column::NAME: return tr("Name");
|
||||
case Column::SOURCE: return tr("Bus");
|
||||
case Column::ADDRESS: return tr("ID");
|
||||
case Column::NODE: return tr("Node");
|
||||
case Column::FREQ: return tr("Freq");
|
||||
case Column::COUNT: return tr("Count");
|
||||
case Column::DATA: return tr("Bytes");
|
||||
@@ -171,22 +189,22 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
|
||||
QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid() || index.row() >= msgs.size()) return {};
|
||||
|
||||
const auto &id = msgs[index.row()];
|
||||
auto &can_data = can->lastMessage(id);
|
||||
|
||||
auto getFreq = [](const CanData &d) -> QString {
|
||||
auto getFreq = [](const CanData &d) {
|
||||
if (d.freq > 0 && (can->currentSec() - d.ts - 1.0 / settings.fps) < (5.0 / d.freq)) {
|
||||
return d.freq >= 0.95 ? QString::number(std::nearbyint(d.freq)) : QString::number(d.freq, 'f', 2);
|
||||
} else {
|
||||
return "--";
|
||||
return QStringLiteral("--");
|
||||
}
|
||||
};
|
||||
|
||||
const auto &id = msgs[index.row()];
|
||||
auto &can_data = can->lastMessage(id);
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (index.column()) {
|
||||
case Column::NAME: return msgName(id);
|
||||
case Column::SOURCE: return id.source != INVALID_SOURCE ? QString::number(id.source) : "N/A";
|
||||
case Column::ADDRESS: return QString::number(id.address, 16);
|
||||
case Column::NODE: return msg_node_from_id(id);
|
||||
case Column::FREQ: return id.source != INVALID_SOURCE ? getFreq(can_data) : "N/A";
|
||||
case Column::COUNT: return id.source != INVALID_SOURCE ? QString::number(can_data.count) : "N/A";
|
||||
case Column::DATA: return id.source != INVALID_SOURCE ? toHex(can_data.dat) : "N/A";
|
||||
@@ -214,7 +232,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
|
||||
void MessageListModel::setFilterStrings(const QMap<int, QString> &filters) {
|
||||
filter_str = filters;
|
||||
fetchData();
|
||||
filterAndSort();
|
||||
}
|
||||
|
||||
void MessageListModel::dbcModified() {
|
||||
@@ -222,40 +240,22 @@ void MessageListModel::dbcModified() {
|
||||
for (const auto &[_, m] : dbc()->getMessages(-1)) {
|
||||
dbc_address.insert(m.address);
|
||||
}
|
||||
fetchData();
|
||||
filterAndSort();
|
||||
}
|
||||
|
||||
void MessageListModel::sortMessages(std::vector<MessageId> &new_msgs) {
|
||||
if (sort_column == Column::NAME) {
|
||||
std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
|
||||
auto ll = std::pair{msgName(l), l};
|
||||
auto rr = std::pair{msgName(r), r};
|
||||
return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
|
||||
});
|
||||
} else if (sort_column == Column::SOURCE) {
|
||||
std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
|
||||
auto ll = std::pair{l.source, l};
|
||||
auto rr = std::pair{r.source, r};
|
||||
return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
|
||||
});
|
||||
} else if (sort_column == Column::ADDRESS) {
|
||||
std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
|
||||
auto ll = std::pair{l.address, l};
|
||||
auto rr = std::pair{r.address, r};
|
||||
return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
|
||||
});
|
||||
} else if (sort_column == Column::FREQ) {
|
||||
std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
|
||||
auto ll = std::pair{can->lastMessage(l).freq, l};
|
||||
auto rr = std::pair{can->lastMessage(r).freq, r};
|
||||
return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
|
||||
});
|
||||
} else if (sort_column == Column::COUNT) {
|
||||
std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
|
||||
auto ll = std::pair{can->lastMessage(l).count, l};
|
||||
auto rr = std::pair{can->lastMessage(r).count, r};
|
||||
return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
|
||||
auto do_sort = [order = sort_order](std::vector<MessageId> &m, auto proj) {
|
||||
std::sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) {
|
||||
return order == Qt::AscendingOrder ? proj(l) < proj(r) : proj(l) > proj(r);
|
||||
});
|
||||
};
|
||||
switch (sort_column) {
|
||||
case Column::NAME: do_sort(new_msgs, [](auto &id) { return std::make_pair(msgName(id), id); }); break;
|
||||
case Column::SOURCE: do_sort(new_msgs, [](auto &id) { return std::tie(id.source, id); }); break;
|
||||
case Column::ADDRESS: do_sort(new_msgs, [](auto &id) { return std::tie(id.address, id);}); break;
|
||||
case Column::NODE: do_sort(new_msgs, [](auto &id) { return std::make_pair(msg_node_from_id(id), id);}); break;
|
||||
case Column::FREQ: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).freq, id); }); break;
|
||||
case Column::COUNT: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).count, id); }); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +295,9 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co
|
||||
match = match || parseRange(txt, id.address, 16);
|
||||
break;
|
||||
}
|
||||
case Column::NODE:
|
||||
match = re.match(msg_node_from_id(id)).hasMatch();
|
||||
break;
|
||||
case Column::FREQ:
|
||||
// TODO: Hide stale messages?
|
||||
match = parseRange(txt, data.freq);
|
||||
@@ -313,7 +316,7 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co
|
||||
return match;
|
||||
}
|
||||
|
||||
void MessageListModel::fetchData() {
|
||||
void MessageListModel::filterAndSort() {
|
||||
std::vector<MessageId> new_msgs;
|
||||
new_msgs.reserve(can->last_msgs.size() + dbc_address.size());
|
||||
|
||||
@@ -344,7 +347,7 @@ void MessageListModel::fetchData() {
|
||||
|
||||
void MessageListModel::msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids) {
|
||||
if (has_new_ids || filter_str.contains(Column::FREQ) || filter_str.contains(Column::COUNT) || filter_str.contains(Column::DATA)) {
|
||||
fetchData();
|
||||
filterAndSort();
|
||||
}
|
||||
for (int i = 0; i < msgs.size(); ++i) {
|
||||
if (new_msgs->contains(msgs[i])) {
|
||||
@@ -358,7 +361,7 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
|
||||
if (column != columnCount() - 1) {
|
||||
sort_column = column;
|
||||
sort_order = order;
|
||||
fetchData();
|
||||
filterAndSort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,11 +383,6 @@ void MessageListModel::clearSuppress() {
|
||||
suppressed_bytes.clear();
|
||||
}
|
||||
|
||||
void MessageListModel::forceResetModel() {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
// MessageView
|
||||
|
||||
void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
@@ -425,35 +423,7 @@ void MessageView::updateBytesSectionSize() {
|
||||
}
|
||||
}
|
||||
|
||||
void MessageView::headerContextMenuEvent(const QPoint &pos) {
|
||||
QMenu menu(this);
|
||||
int cur_index = header()->logicalIndexAt(pos);
|
||||
|
||||
QAction *action;
|
||||
for (int visual_index = 0; visual_index < header()->count(); visual_index++) {
|
||||
int logical_index = header()->logicalIndex(visual_index);
|
||||
QString column_name = model()->headerData(logical_index, Qt::Horizontal).toString();
|
||||
|
||||
// Hide show action
|
||||
if (header()->isSectionHidden(logical_index)) {
|
||||
action = menu.addAction(tr(" %1").arg(column_name), [=]() { header()->showSection(logical_index); });
|
||||
} else {
|
||||
action = menu.addAction(tr("✓ %1").arg(column_name), [=]() { header()->hideSection(logical_index); });
|
||||
}
|
||||
|
||||
// Can't hide the name column
|
||||
action->setEnabled(logical_index > 0);
|
||||
|
||||
// Make current column bold
|
||||
if (logical_index == cur_index) {
|
||||
QFont font = action->font();
|
||||
font.setBold(true);
|
||||
action->setFont(font);
|
||||
}
|
||||
}
|
||||
|
||||
menu.exec(header()->mapToGlobal(pos));
|
||||
}
|
||||
// MessageViewHeader
|
||||
|
||||
MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) {
|
||||
QObject::connect(this, &QHeaderView::sectionResized, this, &MessageViewHeader::updateHeaderPositions);
|
||||
@@ -463,22 +433,13 @@ MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizont
|
||||
void MessageViewHeader::updateFilters() {
|
||||
QMap<int, QString> filters;
|
||||
for (int i = 0; i < count(); i++) {
|
||||
if (editors[i]) {
|
||||
QString filter = editors[i]->text();
|
||||
if (!filter.isEmpty()) {
|
||||
filters[i] = filter;
|
||||
}
|
||||
if (editors[i] && !editors[i]->text().isEmpty()) {
|
||||
filters[i] = editors[i]->text();
|
||||
}
|
||||
}
|
||||
emit filtersUpdated(filters);
|
||||
}
|
||||
|
||||
void MessageViewHeader::clearFilters() {
|
||||
for (QLineEdit *editor : editors) {
|
||||
editor->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageViewHeader::updateHeaderPositions() {
|
||||
QSize sz = QHeaderView::sizeHint();
|
||||
for (int i = 0; i < count(); i++) {
|
||||
@@ -508,11 +469,7 @@ void MessageViewHeader::updateGeometries() {
|
||||
updateHeaderPositions();
|
||||
}
|
||||
|
||||
|
||||
QSize MessageViewHeader::sizeHint() const {
|
||||
QSize sz = QHeaderView::sizeHint();
|
||||
if (editors[0]) {
|
||||
sz.setHeight(sz.height() + editors[0]->minimumSizeHint().height() + 1);
|
||||
}
|
||||
return sz;
|
||||
return editors[0] ? QSize(sz.width(), sz.height() + editors[0]->height() + 1) : sz;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QCheckBox>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QSet>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
|
||||
#include "tools/cabana/dbc/dbcmanager.h"
|
||||
@@ -21,11 +20,11 @@ class MessageListModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
enum Column {
|
||||
NAME = 0,
|
||||
SOURCE,
|
||||
ADDRESS,
|
||||
NODE,
|
||||
FREQ,
|
||||
COUNT,
|
||||
DATA,
|
||||
@@ -39,10 +38,9 @@ public:
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
void setFilterStrings(const QMap<int, QString> &filters);
|
||||
void msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids);
|
||||
void fetchData();
|
||||
void filterAndSort();
|
||||
void suppress();
|
||||
void clearSuppress();
|
||||
void forceResetModel();
|
||||
void dbcModified();
|
||||
std::vector<MessageId> msgs;
|
||||
QSet<std::pair<MessageId, int>> suppressed_bytes;
|
||||
@@ -65,23 +63,17 @@ public:
|
||||
void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override {}
|
||||
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
|
||||
void updateBytesSectionSize();
|
||||
void headerContextMenuEvent(const QPoint &pos);
|
||||
};
|
||||
|
||||
class MessageViewHeader : public QHeaderView {
|
||||
// https://stackoverflow.com/a/44346317
|
||||
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessageViewHeader(QWidget *parent);
|
||||
void updateHeaderPositions();
|
||||
|
||||
void updateGeometries() override;
|
||||
QSize sizeHint() const override;
|
||||
|
||||
public slots:
|
||||
void clearFilters();
|
||||
|
||||
signals:
|
||||
void filtersUpdated(const QMap<int, QString> &filters);
|
||||
|
||||
@@ -108,12 +100,18 @@ signals:
|
||||
void msgSelectionChanged(const MessageId &message_id);
|
||||
|
||||
protected:
|
||||
QToolBar *createToolBar();
|
||||
void headerContextMenuEvent(const QPoint &pos);
|
||||
void menuAboutToShow();
|
||||
void setMultiLineBytes(bool multi);
|
||||
|
||||
MessageView *view;
|
||||
MessageViewHeader *header;
|
||||
MessageBytesDelegate *delegate;
|
||||
std::optional<MessageId> current_msg_id;
|
||||
QCheckBox *multiple_lines_bytes;
|
||||
MessageListModel *model;
|
||||
QPushButton *suppress_add;
|
||||
QPushButton *suppress_clear;
|
||||
QLabel *num_msg_label;
|
||||
QMenu *menu;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user