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.
264 lines
7.8 KiB
264 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();
|
|
}
|
|
|