diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 1a83a0c446..6a94e013f6 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,9 +1,6 @@ #include "tools/cabana/messageswidget.h" -#include #include -#include -#include #include #include @@ -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 &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(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 &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 &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 &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 new_msgs; new_msgs.reserve(can->last_msgs.size() + dbc_address.size()); @@ -344,7 +347,7 @@ void MessageListModel::fetchData() { void MessageListModel::msgsReceived(const QHash *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 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; } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index f6d71a5a2e..11d95dd3f2 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -5,13 +5,12 @@ #include #include -#include -#include #include #include #include #include #include +#include #include #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 &filters); void msgsReceived(const QHash *new_msgs, bool has_new_ids); - void fetchData(); + void filterAndSort(); void suppress(); void clearSuppress(); - void forceResetModel(); void dbcModified(); std::vector msgs; QSet> 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 &roles = QVector()) 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 &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 current_msg_id; - QCheckBox *multiple_lines_bytes; MessageListModel *model; QPushButton *suppress_add; QPushButton *suppress_clear; QLabel *num_msg_label; + QMenu *menu; };