openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

289 lines
9.9 KiB

#include "tools/cabana/messageswidget.h"
#include <QCompleter>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFontDatabase>
#include <QHeaderView>
#include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#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 &current, 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();
}
}