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.

265 lines
7.8 KiB

#include "tools/cabana/detailwidget.h"
#include <QDebug>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHeaderView>
#include <QTimer>
#include <QVBoxLayout>
#include <bitset>
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
inline const QString &getColor(int i) {
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"};
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)];
}
// DetailWidget
DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
name_label = new QLabel(this);
name_label->setStyleSheet("font-weight:bold;");
name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
name_label->setAlignment(Qt::AlignCenter);
main_layout->addWidget(name_label);
// title
QHBoxLayout *title_layout = new QHBoxLayout();
time_label = new QLabel(this);
title_layout->addWidget(time_label);
title_layout->addStretch();
edit_btn = new QPushButton(tr("Edit"), this);
edit_btn->setVisible(false);
title_layout->addWidget(edit_btn);
main_layout->addLayout(title_layout);
// binary view
binary_view = new BinaryView(this);
main_layout->addWidget(binary_view);
// scroll area
QHBoxLayout *signals_layout = new QHBoxLayout();
signals_layout->addWidget(new QLabel(tr("Signals")));
signals_layout->addStretch();
add_sig_btn = new QPushButton(tr("Add signal"), this);
add_sig_btn->setVisible(false);
signals_layout->addWidget(add_sig_btn);
main_layout->addLayout(signals_layout);
QWidget *container = new QWidget(this);
QVBoxLayout *container_layout = new QVBoxLayout(container);
signal_edit_layout = new QVBoxLayout();
signal_edit_layout->setSpacing(2);
container_layout->addLayout(signal_edit_layout);
history_log = new HistoryLog(this);
container_layout->addWidget(history_log);
QScrollArea *scroll = new QScrollArea(this);
scroll->setWidget(container);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
main_layout->addWidget(scroll);
QObject::connect(add_sig_btn, &QPushButton::clicked, this, &DetailWidget::addSignal);
QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg);
QObject::connect(parser, &Parser::updated, this, &DetailWidget::updateState);
}
void DetailWidget::setMsg(const CanData *c) {
can_data = c;
clearLayout(signal_edit_layout);
edit_btn->setVisible(true);
if (auto msg = parser->getMsg(can_data->address)) {
name_label->setText(msg->name.c_str());
add_sig_btn->setVisible(true);
for (int i = 0; i < msg->sigs.size(); ++i) {
signal_edit_layout->addWidget(new SignalEdit(can_data->id, msg->sigs[i], getColor(i)));
}
} else {
name_label->setText(tr("untitled"));
add_sig_btn->setVisible(false);
}
binary_view->setMsg(can_data);
history_log->clear();
}
void DetailWidget::updateState() {
if (!can_data) return;
time_label->setText(QString("time: %1").arg(can_data->ts, 0, 'f', 3));
binary_view->setData(can_data->dat);
history_log->updateState();
}
void DetailWidget::editMsg() {
EditMessageDialog dlg(can_data->id, this);
if (dlg.exec()) {
setMsg(can_data);
}
}
void DetailWidget::addSignal() {
AddSignalDialog dlg(can_data->id, this);
if (dlg.exec()) {
setMsg(can_data);
}
}
// BinaryView
BinaryView::BinaryView(QWidget *parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
table = new QTableWidget(this);
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
table->horizontalHeader()->hide();
table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(table);
table->setColumnCount(9);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
}
void BinaryView::setMsg(const CanData *can_data) {
const Msg *msg = parser->getMsg(can_data->address);
int row_count = msg ? msg->size : can_data->dat.size();
table->setRowCount(row_count);
table->setColumnCount(9);
for (int i = 0; i < table->rowCount(); ++i) {
for (int j = 0; j < table->columnCount(); ++j) {
auto item = new QTableWidgetItem();
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
item->setTextAlignment(Qt::AlignCenter);
if (j == 8) {
QFont font;
font.setBold(true);
item->setFont(font);
}
table->setItem(i, j, item);
}
}
if (msg) {
// set background color
for (int i = 0; i < msg->sigs.size(); ++i) {
const auto &sig = msg->sigs[i];
int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit);
for (int j = start; j <= start + sig.size - 1; ++j) {
table->item(j / 8, j % 8)->setBackground(QColor(getColor(i)));
}
}
}
setFixedHeight(table->rowHeight(0) * table->rowCount() + 25);
}
void BinaryView::setData(const QByteArray &binary) {
std::string s;
for (int j = 0; j < binary.size(); ++j) {
s += std::bitset<8>(binary[j]).to_string();
}
setUpdatesEnabled(false);
char hex[3] = {'\0'};
for (int i = 0; i < binary.size(); ++i) {
for (int j = 0; j < 8; ++j) {
table->item(i, j)->setText(QChar(s[i * 8 + j]));
}
sprintf(&hex[0], "%02X", (unsigned char)binary[i]);
table->item(i, 8)->setText(hex);
}
setUpdatesEnabled(true);
}
// HistoryLog
HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
QLabel *title = new QLabel("TIME BYTES");
main_layout->addWidget(title);
QVBoxLayout *message_layout = new QVBoxLayout();
for (int i = 0; i < std::size(labels); ++i) {
labels[i] = new QLabel();
labels[i]->setVisible(false);
message_layout->addWidget(labels[i]);
}
main_layout->addLayout(message_layout);
main_layout->addStretch();
}
void HistoryLog::updateState() {
int i = 0;
for (; i < parser->history_log.size(); ++i) {
const auto &c = parser->history_log[i];
auto label = labels[i];
label->setVisible(true);
label->setText(QString("%1 %2").arg(c.ts, 0, 'f', 3).arg(toHex(c.dat)));
}
for (; i < std::size(labels); ++i) {
labels[i]->setVisible(false);
}
}
void HistoryLog::clear() {
setUpdatesEnabled(false);
for (auto l : labels) l->setVisible(false);
setUpdatesEnabled(true);
}
// EditMessageDialog
EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : id(id), QDialog(parent) {
setWindowTitle(tr("Edit message"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
QFormLayout *form_layout = new QFormLayout();
form_layout->addRow("ID", new QLabel(id));
auto msg = const_cast<Msg *>(parser->getMsg(id));
name_edit = new QLineEdit(this);
name_edit->setText(msg ? msg->name.c_str() : "untitled");
form_layout->addRow(tr("Name"), name_edit);
size_spin = new QSpinBox(this);
size_spin->setValue(msg ? msg->size : parser->can_msgs[id].dat.size());
form_layout->addRow(tr("Size"), size_spin);
main_layout->addLayout(form_layout);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
connect(buttonBox, &QDialogButtonBox::accepted, this, &EditMessageDialog::save);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void EditMessageDialog::save() {
if (size_spin->value() <= 0 || name_edit->text().isEmpty()) return;
if (auto msg = const_cast<Msg *>(parser->getMsg(id))) {
msg->name = name_edit->text().toStdString();
msg->size = size_spin->value();
} else {
Msg m = {};
m.address = Parser::addressFromId(id);
m.name = name_edit->text().toStdString();
m.size = size_spin->value();
parser->addNewMsg(m);
}
QDialog::accept();
}