|
|
|
@ -3,9 +3,9 @@ |
|
|
|
|
#include <QDialogButtonBox> |
|
|
|
|
#include <QFormLayout> |
|
|
|
|
#include <QHeaderView> |
|
|
|
|
#include <QMessageBox> |
|
|
|
|
#include <QScrollBar> |
|
|
|
|
#include <QTimer> |
|
|
|
|
#include <QVBoxLayout> |
|
|
|
|
|
|
|
|
|
// DetailWidget
|
|
|
|
|
|
|
|
|
@ -32,60 +32,52 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { |
|
|
|
|
binary_view = new BinaryView(this); |
|
|
|
|
main_layout->addWidget(binary_view, 0, Qt::AlignTop); |
|
|
|
|
|
|
|
|
|
// signal header
|
|
|
|
|
signals_header = new QWidget(this); |
|
|
|
|
QHBoxLayout *signals_header_layout = new QHBoxLayout(signals_header); |
|
|
|
|
signals_header_layout->addWidget(new QLabel(tr("Signals"))); |
|
|
|
|
signals_header_layout->addStretch(); |
|
|
|
|
QPushButton *add_sig_btn = new QPushButton(tr("Add signal"), this); |
|
|
|
|
signals_header_layout->addWidget(add_sig_btn); |
|
|
|
|
signals_header->setVisible(false); |
|
|
|
|
main_layout->addWidget(signals_header); |
|
|
|
|
|
|
|
|
|
// scroll area
|
|
|
|
|
// signals
|
|
|
|
|
signals_container = new QWidget(this); |
|
|
|
|
signals_container->setLayout(new QVBoxLayout); |
|
|
|
|
signals_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); |
|
|
|
|
|
|
|
|
|
scroll = new ScrollArea(this); |
|
|
|
|
QWidget *container = new QWidget(this); |
|
|
|
|
container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); |
|
|
|
|
QVBoxLayout *container_layout = new QVBoxLayout(container); |
|
|
|
|
signal_edit_layout = new QVBoxLayout(); |
|
|
|
|
signal_edit_layout->setSpacing(2); |
|
|
|
|
container_layout->addLayout(signal_edit_layout); |
|
|
|
|
|
|
|
|
|
scroll->setWidget(container); |
|
|
|
|
scroll->setWidget(signals_container); |
|
|
|
|
scroll->setWidgetResizable(true); |
|
|
|
|
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
|
|
|
main_layout->addWidget(scroll); |
|
|
|
|
|
|
|
|
|
// history log
|
|
|
|
|
history_log = new HistoryLog(this); |
|
|
|
|
main_layout->addWidget(history_log); |
|
|
|
|
|
|
|
|
|
QObject::connect(add_sig_btn, &QPushButton::clicked, this, &DetailWidget::addSignal); |
|
|
|
|
QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); |
|
|
|
|
QObject::connect(binary_view, &BinaryView::cellsSelected, this, &DetailWidget::addSignal); |
|
|
|
|
QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); |
|
|
|
|
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &DetailWidget::dbcMsgChanged); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DetailWidget::setMessage(const QString &message_id) { |
|
|
|
|
msg_id = message_id; |
|
|
|
|
for (auto f : signal_forms) { |
|
|
|
|
f->deleteLater(); |
|
|
|
|
if (msg_id != message_id) { |
|
|
|
|
msg_id = message_id; |
|
|
|
|
dbcMsgChanged(); |
|
|
|
|
} |
|
|
|
|
signal_forms.clear(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DetailWidget::dbcMsgChanged() { |
|
|
|
|
if (msg_id.isEmpty()) return; |
|
|
|
|
|
|
|
|
|
qDeleteAll(signals_container->findChildren<SignalEdit *>()); |
|
|
|
|
QString msg_name = tr("untitled"); |
|
|
|
|
if (auto msg = dbc()->msg(msg_id)) { |
|
|
|
|
for (int i = 0; i < msg->sigs.size(); ++i) { |
|
|
|
|
auto form = new SignalEdit(i, msg_id, msg->sigs[i], getColor(i)); |
|
|
|
|
signal_edit_layout->addWidget(form); |
|
|
|
|
QObject::connect(form, &SignalEdit::showChart, this, &DetailWidget::showChart); |
|
|
|
|
auto form = new SignalEdit(i, msg_id, msg->sigs[i]); |
|
|
|
|
signals_container->layout()->addWidget(form); |
|
|
|
|
QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]]() { emit showChart(msg_id, sig); }); |
|
|
|
|
QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); |
|
|
|
|
signal_forms.push_back(form); |
|
|
|
|
QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); |
|
|
|
|
QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); |
|
|
|
|
} |
|
|
|
|
name_label->setText(msg->name.c_str()); |
|
|
|
|
signals_header->setVisible(true); |
|
|
|
|
} else { |
|
|
|
|
name_label->setText(tr("untitled")); |
|
|
|
|
signals_header->setVisible(false); |
|
|
|
|
msg_name = msg->name.c_str(); |
|
|
|
|
} |
|
|
|
|
edit_btn->setVisible(true); |
|
|
|
|
name_label->setText(msg_name); |
|
|
|
|
|
|
|
|
|
binary_view->setMessage(msg_id); |
|
|
|
|
history_log->setMessage(msg_id); |
|
|
|
@ -99,60 +91,90 @@ void DetailWidget::updateState() { |
|
|
|
|
history_log->updateState(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DetailWidget::editMsg() { |
|
|
|
|
EditMessageDialog dlg(msg_id, this); |
|
|
|
|
if (dlg.exec()) { |
|
|
|
|
setMessage(msg_id); |
|
|
|
|
void DetailWidget::showForm() { |
|
|
|
|
SignalEdit *sender = qobject_cast<SignalEdit *>(QObject::sender()); |
|
|
|
|
for (auto f : signals_container->findChildren<SignalEdit *>()) { |
|
|
|
|
f->setFormVisible(f == sender && !f->isFormVisible()); |
|
|
|
|
if (f == sender) { |
|
|
|
|
QTimer::singleShot(0, [=]() { scroll->ensureWidgetVisible(f); }); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DetailWidget::addSignal() { |
|
|
|
|
AddSignalDialog dlg(msg_id, this); |
|
|
|
|
void DetailWidget::editMsg() { |
|
|
|
|
auto msg = dbc()->msg(msg_id); |
|
|
|
|
QString name = msg ? msg->name.c_str() : "untitled"; |
|
|
|
|
int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); |
|
|
|
|
EditMessageDialog dlg(msg_id, name, size, this); |
|
|
|
|
if (dlg.exec()) { |
|
|
|
|
setMessage(msg_id); |
|
|
|
|
dbc()->updateMsg(msg_id, dlg.name_edit->text(), dlg.size_spin->value()); |
|
|
|
|
dbcMsgChanged(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DetailWidget::showForm() { |
|
|
|
|
SignalEdit *sender = qobject_cast<SignalEdit *>(QObject::sender()); |
|
|
|
|
if (sender->isFormVisible()) { |
|
|
|
|
sender->setFormVisible(false); |
|
|
|
|
} else { |
|
|
|
|
for (auto f : signal_forms) { |
|
|
|
|
f->setFormVisible(f == sender); |
|
|
|
|
if (f == sender) { |
|
|
|
|
// scroll to header
|
|
|
|
|
QTimer::singleShot(0, [=]() { |
|
|
|
|
const QPoint p = f->mapTo(scroll, QPoint(0, 0)); |
|
|
|
|
scroll->verticalScrollBar()->setValue(p.y() + scroll->verticalScrollBar()->value()); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
void DetailWidget::addSignal(int start_bit, int size) { |
|
|
|
|
if (dbc()->msg(msg_id)) { |
|
|
|
|
AddSignalDialog dlg(msg_id, start_bit, size, this); |
|
|
|
|
if (dlg.exec()) { |
|
|
|
|
dbc()->addSignal(msg_id, dlg.form->getSignal()); |
|
|
|
|
dbcMsgChanged(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DetailWidget::saveSignal() { |
|
|
|
|
SignalEdit *sig_form = qobject_cast<SignalEdit *>(QObject::sender()); |
|
|
|
|
auto s = sig_form->form->getSignal(); |
|
|
|
|
dbc()->updateSignal(msg_id, sig_form->sig_name, s); |
|
|
|
|
// update binary view and history log
|
|
|
|
|
binary_view->setMessage(msg_id); |
|
|
|
|
history_log->setMessage(msg_id); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DetailWidget::removeSignal() { |
|
|
|
|
SignalEdit *sig_form = qobject_cast<SignalEdit *>(QObject::sender()); |
|
|
|
|
QString text = tr("Are you sure you want to remove signal '%1'").arg(sig_form->sig_name); |
|
|
|
|
if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove signal"), text)) { |
|
|
|
|
dbc()->removeSignal(msg_id, sig_form->sig_name); |
|
|
|
|
dbcMsgChanged(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BinaryView
|
|
|
|
|
|
|
|
|
|
BinaryView::BinaryView(QWidget *parent) { |
|
|
|
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
|
|
|
|
main_layout->setContentsMargins(0, 0, 0, 0); |
|
|
|
|
table = new QTableWidget(this); |
|
|
|
|
table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
|
|
|
|
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
|
|
|
|
table->horizontalHeader()->hide(); |
|
|
|
|
table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
|
|
|
main_layout->addWidget(table); |
|
|
|
|
table->setColumnCount(9); |
|
|
|
|
BinaryView::BinaryView(QWidget *parent) : QTableWidget(parent) { |
|
|
|
|
horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
|
|
|
|
verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
|
|
|
|
horizontalHeader()->hide(); |
|
|
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
|
|
|
setColumnCount(9); |
|
|
|
|
|
|
|
|
|
// replace selection model
|
|
|
|
|
auto old_model = selectionModel(); |
|
|
|
|
setSelectionModel(new BinarySelectionModel(model())); |
|
|
|
|
delete old_model; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void BinaryView::mouseReleaseEvent(QMouseEvent *event) { |
|
|
|
|
QTableWidget::mouseReleaseEvent(event); |
|
|
|
|
|
|
|
|
|
if (auto items = selectedItems(); !items.isEmpty()) { |
|
|
|
|
int start_bit = items.first()->row() * 8 + items.first()->column(); |
|
|
|
|
int size = items.back()->row() * 8 + items.back()->column() - start_bit + 1; |
|
|
|
|
emit cellsSelected(start_bit, size); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void BinaryView::setMessage(const QString &message_id) { |
|
|
|
|
msg_id = message_id; |
|
|
|
|
if (msg_id.isEmpty()) return; |
|
|
|
|
|
|
|
|
|
const Msg *msg = dbc()->msg(msg_id); |
|
|
|
|
const int row_count = msg ? msg->size : can->lastMessage(msg_id).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) { |
|
|
|
|
int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size(); |
|
|
|
|
setRowCount(row_count); |
|
|
|
|
setColumnCount(9); |
|
|
|
|
for (int i = 0; i < rowCount(); ++i) { |
|
|
|
|
for (int j = 0; j < columnCount(); ++j) { |
|
|
|
|
auto item = new QTableWidgetItem(); |
|
|
|
|
item->setFlags(item->flags() ^ Qt::ItemIsEditable); |
|
|
|
|
item->setTextAlignment(Qt::AlignCenter); |
|
|
|
@ -160,8 +182,9 @@ void BinaryView::setMessage(const QString &message_id) { |
|
|
|
|
QFont font; |
|
|
|
|
font.setBold(true); |
|
|
|
|
item->setFont(font); |
|
|
|
|
item->setFlags(item->flags() ^ Qt::ItemIsSelectable); |
|
|
|
|
} |
|
|
|
|
table->setItem(i, j, item); |
|
|
|
|
setItem(i, j, item); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -170,71 +193,73 @@ void BinaryView::setMessage(const QString &message_id) { |
|
|
|
|
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))); |
|
|
|
|
for (int j = start; j <= std::min(start + sig.size - 1, rowCount() * columnCount() - 1); ++j) { |
|
|
|
|
item(j / 8, j % 8)->setBackground(QColor(getColor(i))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
table->setFixedHeight(table->rowHeight(0) * std::min(row_count, 8) + 2); |
|
|
|
|
|
|
|
|
|
setFixedHeight(rowHeight(0) * std::min(row_count, 8) + 2); |
|
|
|
|
clearSelection(); |
|
|
|
|
updateState(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void BinaryView::updateState() { |
|
|
|
|
if (msg_id.isEmpty()) return; |
|
|
|
|
|
|
|
|
|
const auto &binary = can->lastMessage(msg_id).dat; |
|
|
|
|
|
|
|
|
|
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((binary[i] >> (7 - j)) & 1 ? '1' : '0')); |
|
|
|
|
item(i, j)->setText(QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0')); |
|
|
|
|
} |
|
|
|
|
hex[0] = toHex(binary[i] >> 4); |
|
|
|
|
hex[1] = toHex(binary[i] & 0xf); |
|
|
|
|
table->item(i, 8)->setText(hex); |
|
|
|
|
item(i, 8)->setText(hex); |
|
|
|
|
} |
|
|
|
|
setUpdatesEnabled(true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { |
|
|
|
|
QItemSelection new_selection = selection; |
|
|
|
|
if (auto indexes = selection.indexes(); !indexes.isEmpty()) { |
|
|
|
|
auto [begin_idx, end_idx] = (QModelIndex[]){indexes.first(), indexes.back()}; |
|
|
|
|
for (int row = begin_idx.row(); row <= end_idx.row(); ++row) { |
|
|
|
|
int left_col = (row == begin_idx.row()) ? begin_idx.column() : 0; |
|
|
|
|
int right_col = (row == end_idx.row()) ? end_idx.column() : 7; |
|
|
|
|
new_selection.merge({model()->index(row, left_col), model()->index(row, right_col)}, command); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
QItemSelectionModel::select(new_selection, command); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// EditMessageDialog
|
|
|
|
|
|
|
|
|
|
EditMessageDialog::EditMessageDialog(const QString &msg_id, QWidget *parent) : msg_id(msg_id), QDialog(parent) { |
|
|
|
|
EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) { |
|
|
|
|
setWindowTitle(tr("Edit message")); |
|
|
|
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
|
|
|
|
|
|
|
|
|
QFormLayout *form_layout = new QFormLayout(); |
|
|
|
|
form_layout->addRow("ID", new QLabel(msg_id)); |
|
|
|
|
|
|
|
|
|
const auto msg = dbc()->msg(msg_id); |
|
|
|
|
name_edit = new QLineEdit(this); |
|
|
|
|
name_edit->setText(msg ? msg->name.c_str() : "untitled"); |
|
|
|
|
name_edit = new QLineEdit(title, this); |
|
|
|
|
form_layout->addRow(tr("Name"), name_edit); |
|
|
|
|
|
|
|
|
|
size_spin = new QSpinBox(this); |
|
|
|
|
size_spin->setValue(msg ? msg->size : can->lastMessage(msg_id).dat.size()); |
|
|
|
|
// TODO: limit the maximum?
|
|
|
|
|
size_spin->setMinimum(1); |
|
|
|
|
size_spin->setValue(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); |
|
|
|
|
|
|
|
|
|
setFixedWidth(parent->width() * 0.9); |
|
|
|
|
|
|
|
|
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &EditMessageDialog::save); |
|
|
|
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); |
|
|
|
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void EditMessageDialog::save() { |
|
|
|
|
const QString name = name_edit->text(); |
|
|
|
|
if (size_spin->value() <= 0 || name_edit->text().isEmpty() || name == tr("untitled")) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
dbc()->updateMsg(msg_id, name, size_spin->value()); |
|
|
|
|
QDialog::accept(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ScrollArea
|
|
|
|
|
|
|
|
|
|
bool ScrollArea::eventFilter(QObject *obj, QEvent *ev) { |
|
|
|
|