#include "tools/cabana/messageswidget.h" #include #include #include MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0 ,0, 0, 0); // message filter 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); // message table table_widget = new QTableView(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); // suppress QHBoxLayout *suppress_layout = new QHBoxLayout(); suppress_add = new QPushButton("Suppress Highlighted"); suppress_clear = new QPushButton(); suppress_layout->addWidget(suppress_add); suppress_layout->addWidget(suppress_clear); main_layout->addLayout(suppress_layout); // signals/slots QObject::connect(filter, &QLineEdit::textEdited, model, &MessageListModel::setFilterString); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); QObject::connect(can, &AbstractStream::streamStarted, this, &MessagesWidget::reset); QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages); QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages); QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages); QObject::connect(model, &MessageListModel::modelReset, [this]() { if (current_msg_id) { selectMessage(*current_msg_id); } }); QObject::connect(table_widget->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) { current_msg_id = id; emit msgSelectionChanged(*current_msg_id); } } }); QObject::connect(suppress_add, &QPushButton::clicked, [=]() { model->suppress(); updateSuppressedButtons(); }); QObject::connect(suppress_clear, &QPushButton::clicked, [=]() { model->clearSuppress(); updateSuppressedButtons(); }); updateSuppressedButtons(); setWhatsThis(tr(R"( Message View
Byte color
constant changing
increasing
decreasing )")); } 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); } } void MessagesWidget::updateSuppressedButtons() { if (model->suppressed_bytes.empty()) { suppress_clear->setEnabled(false); suppress_clear->setText("Clear Suppressed"); } else { suppress_clear->setEnabled(true); suppress_clear->setText(QString("Clear Suppressed (%1)").arg(model->suppressed_bytes.size())); } } void MessagesWidget::reset() { current_msg_id = std::nullopt; table_widget->selectionModel()->clear(); model->reset(); filter->clear(); updateSuppressedButtons(); } // 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]; return {}; } QVariant MessageListModel::data(const QModelIndex &index, int role) const { const auto &id = msgs[index.row()]; auto &can_data = can->lastMessage(id); if (role == Qt::DisplayRole) { switch (index.column()) { case 0: return msgName(id); case 1: return id.source; case 2: return QString::number(id.address, 16);; case 3: return can_data.freq; case 4: return can_data.count; case 5: return toHex(can_data.dat); } } else if (role == Qt::UserRole && index.column() == 5) { QVector colors = can_data.colors; if (!suppressed_bytes.empty()) { for (int i = 0; i < colors.size(); i++) { if (suppressed_bytes.contains({id, i})) { colors[i] = QColor(255, 255, 255, 0); } } } return QVariant::fromValue(colors); } return {}; } void MessageListModel::setFilterString(const QString &string) { auto contains = [](const MessageId &id, const QString &txt) { auto cs = Qt::CaseInsensitive; if (id.toString().contains(txt, cs) || msgName(id).contains(txt, cs)) return true; // Search by signal name if (const auto msg = dbc()->msg(id)) { for (auto s : msg->getSignals()) { if (s->name.contains(txt, cs)) return true; } } return false; }; filter_str = string; msgs.clear(); for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) { if (filter_str.isEmpty() || contains(it.key(), filter_str)) { msgs.push_back(it.key()); } } sortMessages(); } void MessageListModel::sortMessages() { beginResetModel(); if (sort_column == 0) { std::sort(msgs.begin(), msgs.end(), [this](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 == 1) { std::sort(msgs.begin(), msgs.end(), [this](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 == 2) { std::sort(msgs.begin(), msgs.end(), [this](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 == 3) { std::sort(msgs.begin(), msgs.end(), [this](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 == 4) { std::sort(msgs.begin(), msgs.end(), [this](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; }); } endResetModel(); } void MessageListModel::msgsReceived(const QHash *new_msgs) { int prev_row_count = msgs.size(); if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) { msgs = can->can_msgs.keys(); } if (msgs.size() != prev_row_count) { sortMessages(); return; } for (int i = 0; i < msgs.size(); ++i) { if (new_msgs->contains(msgs[i])) { for (int col = 3; col < columnCount(); ++col) emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole}); } } } void MessageListModel::sort(int column, Qt::SortOrder order) { if (column != columnCount() - 1) { sort_column = column; sort_order = order; sortMessages(); } } void MessageListModel::suppress() { const double cur_ts = can->currentSec(); for (auto &id : msgs) { auto &can_data = can->lastMessage(id); for (int i = 0; i < can_data.dat.size(); i++) { const double dt = cur_ts - can_data.last_change_t[i]; if (dt < 2.0) { suppressed_bytes.insert({id, i}); } } } } void MessageListModel::clearSuppress() { suppressed_bytes.clear(); } void MessageListModel::reset() { beginResetModel(); filter_str = ""; msgs.clear(); clearSuppress(); endResetModel(); }