#include "tools/cabana/messageswidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "tools/cabana/dbcmanager.h" MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); // DBC file selector QHBoxLayout *dbc_file_layout = new QHBoxLayout(); dbc_combo = new QComboBox(this); auto dbc_names = dbc()->allDBCNames(); for (const auto &name : dbc_names) { dbc_combo->addItem(QString::fromStdString(name)); } dbc_combo->model()->sort(0); dbc_combo->setEditable(true); dbc_combo->setCurrentText(QString()); dbc_combo->setInsertPolicy(QComboBox::NoInsert); dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion); QFont font; font.setBold(true); dbc_combo->lineEdit()->setFont(font); dbc_file_layout->addWidget(dbc_combo); QPushButton *load_from_paste = new QPushButton(tr("Load from paste"), this); dbc_file_layout->addWidget(load_from_paste); dbc_file_layout->addStretch(); QPushButton *save_btn = new QPushButton(tr("Save DBC"), this); dbc_file_layout->addWidget(save_btn); main_layout->addLayout(dbc_file_layout); // message filter QLineEdit *filter = new QLineEdit(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->setSelectionBehavior(QAbstractItemView::SelectRows); table_widget->setSelectionMode(QAbstractItemView::SingleSelection); table_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); table_widget->setSortingEnabled(true); table_widget->sortByColumn(0, Qt::AscendingOrder); table_widget->setColumnWidth(0, 250); table_widget->setColumnWidth(1, 80); table_widget->setColumnWidth(2, 80); table_widget->horizontalHeader()->setStretchLastSection(true); table_widget->verticalHeader()->hide(); main_layout->addWidget(table_widget); // signals/slots QObject::connect(filter, &QLineEdit::textChanged, model, &MessageListModel::setFilterString); QObject::connect(can, &CANMessages::eventsMerged, this, &MessagesWidget::loadDBCFromFingerprint); QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); }); QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &))); QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste); QObject::connect(save_btn, &QPushButton::clicked, this, &MessagesWidget::saveDBC); QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { if (current.isValid()) { emit msgSelectionChanged(current.data(Qt::UserRole).toString()); } }); QFile json_file("./car_fingerprint_to_dbc.json"); if (json_file.open(QIODevice::ReadOnly)) { fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); } } void MessagesWidget::loadDBCFromName(const QString &name) { if (name != dbc()->name()) { dbc()->open(name); dbc_combo->setCurrentText(name); // re-sort model to refresh column 'Name' model->updateState(true); } } void MessagesWidget::loadDBCFromPaste() { LoadDBCDialog dlg(this); if (dlg.exec()) { dbc()->open("from paste", dlg.dbc_edit->toPlainText()); dbc_combo->setCurrentText("loaded from paste"); model->updateState(true); } } void MessagesWidget::loadDBCFromFingerprint() { auto fingerprint = can->carFingerprint(); if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) { auto dbc_name = fingerprint_to_dbc[fingerprint]; if (dbc_name != QJsonValue::Undefined) { loadDBCFromName(dbc_name.toString()); } } } void MessagesWidget::saveDBC() { SaveDBCDialog dlg(this); dlg.dbc_edit->setText(dbc()->generateDBC()); dlg.exec(); } // MessageListModel QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return (QString[]){"Name", "ID", "Freq", "Count", "Bytes"}[section]; return {}; } QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { const auto &m = msgs[index.row()]; auto &can_data = can->lastMessage(m->id); switch (index.column()) { case 0: return m->name; case 1: return m->id; case 2: return can_data.freq; case 3: return can_data.count; case 4: return toHex(can_data.dat); } } else if (role == Qt::UserRole) { return msgs[index.row()]->id; } else if (role == Qt::FontRole) { if (index.column() == columnCount() - 1) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); } } return {}; } bool MessageListModel::updateMessages(bool sort) { if (msgs.size() == can->can_msgs.size() && filter_str.isEmpty() && !sort) return false; // update message list int i = 0; bool search_id = filter_str.contains(':'); for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) { const Msg *msg = dbc()->msg(it.key()); QString msg_name = msg ? msg->name.c_str() : "untitled"; if (!filter_str.isEmpty() && !(search_id ? it.key() : msg_name).contains(filter_str, Qt::CaseInsensitive)) continue; auto &m = i < msgs.size() ? msgs[i] : msgs.emplace_back(new Message); m->id = it.key(); m->name = msg_name; ++i; } msgs.resize(i); if (sort_column == 0) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { bool ret = l->name < r->name || (l->name == r->name && l->id < r->id); return sort_order == Qt::AscendingOrder ? ret : !ret; }); } else if (sort_column == 1) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { return sort_order == Qt::AscendingOrder ? l->id < r->id : l->id > r->id; }); } else if (sort_column == 2) { // sort by frequency std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { uint32_t lfreq = can->lastMessage(l->id).freq; uint32_t rfreq = can->lastMessage(r->id).freq; bool ret = lfreq < rfreq || (lfreq == rfreq && l->id < r->id); return sort_order == Qt::AscendingOrder ? ret : !ret; }); } else if (sort_column == 3) { // sort by count std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { uint32_t lcount = can->lastMessage(l->id).count; uint32_t rcount = can->lastMessage(r->id).count; bool ret = lcount < rcount || (lcount == rcount && l->id < r->id); return sort_order == Qt::AscendingOrder ? ret : !ret; }); } return true; } void MessageListModel::updateState(bool sort) { int prev_row_count = msgs.size(); auto prev_idx = persistentIndexList(); QString selected_msg_id = prev_idx.empty() ? "" : prev_idx[0].data(Qt::UserRole).toString(); bool msg_updated = updateMessages(sort); int delta = msgs.size() - prev_row_count; if (delta > 0) { beginInsertRows({}, prev_row_count, msgs.size() - 1); endInsertRows(); } else if (delta < 0) { beginRemoveRows({}, msgs.size(), prev_row_count - 1); endRemoveRows(); } if (!msgs.empty()) { if (msg_updated && !prev_idx.isEmpty()) { // keep selection auto it = std::find_if(msgs.begin(), msgs.end(), [&](auto &m) { return m->id == selected_msg_id; }); if (it != msgs.end()) { for (auto &idx : prev_idx) changePersistentIndex(idx, index(std::distance(msgs.begin(), it), idx.column())); } } emit dataChanged(index(0, 0), index(msgs.size() - 1, 3), {Qt::DisplayRole}); } } void MessageListModel::sort(int column, Qt::SortOrder order) { if (column != columnCount() - 1) { sort_column = column; sort_order = order; updateState(true); } } // LoadDBCDialog LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); dbc_edit = new QTextEdit(this); dbc_edit->setAcceptRichText(false); dbc_edit->setPlaceholderText(tr("paste DBC file here")); main_layout->addWidget(dbc_edit); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); setMinimumSize({640, 480}); QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } // SaveDBCDialog SaveDBCDialog::SaveDBCDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Save DBC")); QVBoxLayout *main_layout = new QVBoxLayout(this); dbc_edit = new QTextEdit(this); dbc_edit->setAcceptRichText(false); main_layout->addWidget(dbc_edit); QPushButton *copy_to_clipboard = new QPushButton(tr("Copy To Clipboard"), this); QPushButton *save_as = new QPushButton(tr("Save As"), this); QHBoxLayout *btn_layout = new QHBoxLayout(); btn_layout->addStretch(); btn_layout->addWidget(copy_to_clipboard); btn_layout->addWidget(save_as); main_layout->addLayout(btn_layout); setMinimumSize({640, 480}); QObject::connect(copy_to_clipboard, &QPushButton::clicked, this, &SaveDBCDialog::copytoClipboard); QObject::connect(save_as, &QPushButton::clicked, this, &SaveDBCDialog::saveAs); } void SaveDBCDialog::copytoClipboard() { dbc_edit->selectAll(); dbc_edit->copy(); QDialog::accept(); } void SaveDBCDialog::saveAs() { QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)")); if (!file_name.isEmpty()) { QFile file(file_name); if (file.open(QIODevice::WriteOnly)) { file.write(dbc_edit->toPlainText().toUtf8()); } QDialog::accept(); } }