#include "tools/cabana/detailwidget.h" #include #include #include #include #include #include #include #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(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(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(); }