diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index b7321e1f8d..3ff4862800 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -19,7 +19,7 @@ prev_moc_path = cabana_env['QT_MOCHPREFIX'] cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_env.Execute('./generate_dbc_json.py --out car_fingerprint_to_dbc.json') cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', - 'canmessages.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + 'canmessages.cc', 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('test'): diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 678fe5f876..bcd2b88a81 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -165,14 +165,14 @@ void BinaryViewModel::setMessage(const QString &message_id) { if ((dbc_msg = dbc()->msg(msg_id))) { row_count = dbc_msg->size; items.resize(row_count * column_count); - for (int i = 0; i < dbc_msg->sigs.size(); ++i) { - const auto &sig = dbc_msg->sigs[i]; + int i = 0; + for (auto &[name, sig] : dbc_msg->sigs) { auto [start, end] = getSignalRange(&sig); for (int j = start; j <= end; ++j) { int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j; int idx = column_count * (bit_index / 8) + bit_index % 8; if (idx >= items.size()) { - qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size; + qWarning() << "signal " << name << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size; break; } if (j == start) sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; @@ -180,6 +180,7 @@ void BinaryViewModel::setMessage(const QString &message_id) { items[idx].bg_color = getColor(i); items[idx].sigs.push_back(&sig); } + ++i; } } else { row_count = can->lastMessage(msg_id).dat.size(); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 05bfe7e79f..2d6fc5c18b 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -50,7 +50,7 @@ public: private: QString msg_id; - const Msg *dbc_msg; + const DBCMsg *dbc_msg; int row_count = 0; const int column_count = 9; }; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 3a170bccdc..875ed80ac5 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -240,7 +240,7 @@ void ChartView::resizeEvent(QResizeEvent *event) { void ChartView::updateTitle() { chart()->setTitle(signal->name.c_str()); - msg_title->setHtml(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); + msg_title->setHtml(tr("%1 %2").arg(dbc()->msg(id)->name).arg(id)); } void ChartView::updateFromSettings() { diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc new file mode 100644 index 0000000000..b3f5cb1c66 --- /dev/null +++ b/tools/cabana/commands.cc @@ -0,0 +1,75 @@ +#include "tools/cabana/commands.h" + +// EditMsgCommand + +EditMsgCommand::EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent) + : id(id), new_title(title), new_size(size), QUndoCommand(parent) { + if (auto msg = dbc()->msg(id)) { + old_title = msg->name; + old_size = msg->size; + } + setText(QObject::tr("Edit message %1:%2").arg(DBCManager::parseId(id).second).arg(title)); +} + +void EditMsgCommand::undo() { + if (old_title.isEmpty()) + dbc()->removeMsg(id); + else + dbc()->updateMsg(id, old_title, old_size); +} + +void EditMsgCommand::redo() { + dbc()->updateMsg(id, new_title, new_size); +} + +// RemoveMsgCommand + +RemoveMsgCommand::RemoveMsgCommand(const QString &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) { + if (auto msg = dbc()->msg(id)) { + message = *msg; + setText(QObject::tr("Remove message %1:%2").arg(DBCManager::parseId(id).second).arg(message.name)); + } +} + +void RemoveMsgCommand::undo() { + if (!message.name.isEmpty()) { + dbc()->updateMsg(id, message.name, message.size); + for (auto &[name, s] : message.sigs) + dbc()->addSignal(id, s); + } +} + +void RemoveMsgCommand::redo() { + if (!message.name.isEmpty()) + dbc()->removeMsg(id); +} + +// AddSigCommand + +AddSigCommand::AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent) + : id(id), signal(sig), QUndoCommand(parent) { + setText(QObject::tr("Add signal %1 to %2").arg(sig.name.c_str()).arg(DBCManager::parseId(id).second)); +} + +void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name.c_str()); } +void AddSigCommand::redo() { dbc()->addSignal(id, signal); } + +// RemoveSigCommand + +RemoveSigCommand::RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent) + : id(id), signal(*sig), QUndoCommand(parent) { + setText(QObject::tr("Remove signal %1 from %2").arg(signal.name.c_str()).arg(DBCManager::parseId(id).second)); +} + +void RemoveSigCommand::undo() { dbc()->addSignal(id, signal); } +void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name.c_str()); } + +// EditSignalCommand + +EditSignalCommand::EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent) + : id(id), old_signal(*sig), new_signal(new_sig), QUndoCommand(parent) { + setText(QObject::tr("Edit signal %1").arg(old_signal.name.c_str())); +} + +void EditSignalCommand::undo() { dbc()->updateSignal(id, new_signal.name.c_str(), old_signal); } +void EditSignalCommand::redo() { dbc()->updateSignal(id, old_signal.name.c_str(), new_signal); } diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h new file mode 100644 index 0000000000..7ea1f66653 --- /dev/null +++ b/tools/cabana/commands.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "tools/cabana/canmessages.h" +#include "tools/cabana/dbcmanager.h" + +class EditMsgCommand : public QUndoCommand { +public: + EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const QString id; + QString old_title, new_title; + int old_size = 0, new_size = 0; +}; + +class RemoveMsgCommand : public QUndoCommand { +public: + RemoveMsgCommand(const QString &id, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const QString id; + DBCMsg message; +}; + +class AddSigCommand : public QUndoCommand { +public: + AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const QString id; + Signal signal = {}; +}; + +class RemoveSigCommand : public QUndoCommand { +public: + RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const QString id; + Signal signal = {}; +}; + +class EditSignalCommand : public QUndoCommand { +public: + EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const QString id; + Signal old_signal = {}; + Signal new_signal = {}; +}; diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index abdd9a08df..ebad2387cf 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -10,32 +10,36 @@ DBCManager::~DBCManager() {} void DBCManager::open(const QString &dbc_file_name) { dbc = const_cast(dbc_lookup(dbc_file_name.toStdString())); - updateMsgMap(); - emit DBCFileChanged(); + initMsgMap(); } void DBCManager::open(const QString &name, const QString &content) { std::istringstream stream(content.toStdString()); dbc = const_cast(dbc_parse_from_stream(name.toStdString(), stream)); - updateMsgMap(); - emit DBCFileChanged(); + initMsgMap(); } -void DBCManager::updateMsgMap() { - msg_map.clear(); - for (auto &msg : dbc->msgs) - msg_map[msg.address] = &msg; +void DBCManager::initMsgMap() { + msgs.clear(); + for (auto &msg : dbc->msgs) { + auto &m = msgs[msg.address]; + m.name = msg.name.c_str(); + m.size = msg.size; + for (auto &s : msg.sigs) + m.sigs[QString::fromStdString(s.name)] = s; + } + emit DBCFileChanged(); } QString DBCManager::generateDBC() { if (!dbc) return {}; QString dbc_string; - for (auto &m : dbc->msgs) { - dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(m.address).arg(m.name.c_str()).arg(m.size); - for (auto &sig : m.sigs) { + for (auto &[address, m] : msgs) { + dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size); + for (auto &[name, sig] : m.sigs) { dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [0|0] \"\" XXX\n") - .arg(sig.name.c_str()) + .arg(name) .arg(sig.start_bit) .arg(sig.size) .arg(sig.is_little_endian ? '1' : '0') @@ -49,48 +53,45 @@ QString DBCManager::generateDBC() { } void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) { - auto [bus, address] = parseId(id); - if (auto m = const_cast(msg(address))) { - m->name = name.toStdString(); - m->size = size; - } else { - m = &dbc->msgs.emplace_back(Msg{.address = address, .name = name.toStdString(), .size = size}); - msg_map[address] = m; - } + auto [_, address] = parseId(id); + auto &m = msgs[address]; + m.name = name; + m.size = size; emit msgUpdated(address); } void DBCManager::removeMsg(const QString &id) { uint32_t address = parseId(id).second; - auto it = std::find_if(dbc->msgs.begin(), dbc->msgs.end(), [address](auto &m) { return m.address == address; }); - if (it != dbc->msgs.end()) { - dbc->msgs.erase(it); - updateMsgMap(); - emit msgRemoved(address); - } + msgs.erase(address); + emit msgRemoved(address); } void DBCManager::addSignal(const QString &id, const Signal &sig) { - if (Msg *m = const_cast(msg(id))) { - emit signalAdded(&m->sigs.emplace_back(sig)); + if (auto m = const_cast(msg(id))) { + auto &s = m->sigs[sig.name.c_str()]; + s = sig; + emit signalAdded(&s); } } void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) { - if (Msg *m = const_cast(msg(id))) { - auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); - if (it != m->sigs.end()) { - *it = sig; - emit signalUpdated(&(*it)); - } + if (auto m = const_cast(msg(id))) { + // change key name + QString new_name = QString::fromStdString(sig.name); + auto node = m->sigs.extract(sig_name); + node.key() = new_name; + auto it = m->sigs.insert(std::move(node)); + auto &s = m->sigs[new_name]; + s = sig; + emit signalUpdated(&s); } } void DBCManager::removeSignal(const QString &id, const QString &sig_name) { - if (Msg *m = const_cast(msg(id))) { - auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); + if (auto m = const_cast(msg(id))) { + auto it = m->sigs.find(sig_name); if (it != m->sigs.end()) { - emit signalRemoved(&(*it)); + emit signalRemoved(&(it->second)); m->sigs.erase(it); } } diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 81c723a20d..d2262527d4 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -1,9 +1,16 @@ #pragma once +#include #include - +#include #include "opendbc/can/common_dbc.h" +struct DBCMsg { + QString name; + uint32_t size; + std::map sigs; +}; + class DBCManager : public QObject { Q_OBJECT @@ -24,11 +31,11 @@ public: void updateMsg(const QString &id, const QString &name, uint32_t size); void removeMsg(const QString &id); - inline const DBC *getDBC() const { return dbc; } - inline const Msg *msg(const QString &id) const { return msg(parseId(id).second); } - inline const Msg *msg(uint32_t address) const { - auto it = msg_map.find(address); - return it != msg_map.end() ? it->second : nullptr; + inline const std::map &messages() const { return msgs; } + inline const DBCMsg *msg(const QString &id) const { return msg(parseId(id).second); } + inline const DBCMsg *msg(uint32_t address) const { + auto it = msgs.find(address); + return it != msgs.end() ? &it->second : nullptr; } signals: @@ -40,9 +47,9 @@ signals: void DBCFileChanged(); private: - void updateMsgMap(); + void initMsgMap(); DBC *dbc = nullptr; - std::unordered_map msg_map; + std::map msgs; }; // TODO: Add helper function in dbc.h @@ -54,5 +61,5 @@ std::pair getSignalRange(const Signal *s); DBCManager *dbc(); inline QString msgName(const QString &id, const char *def = "untitled") { auto msg = dbc()->msg(id); - return msg ? msg->name.c_str() : def; + return msg ? msg->name : def; } diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index f96b60847b..6d07727303 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -10,16 +10,19 @@ #include "selfdrive/ui/qt/util.h" #include "tools/cabana/canmessages.h" +#include "tools/cabana/commands.h" #include "tools/cabana/dbcmanager.h" // DetailWidget DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { + undo_stack = new QUndoStack(this); + QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setSpacing(0); - // tabbar + // tabbar tabbar = new QTabBar(this); tabbar->setTabsClosable(true); tabbar->setDrawBase(false); @@ -99,6 +102,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab); QObject::connect(charts, &ChartsWidget::chartOpened, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, true); }); QObject::connect(charts, &ChartsWidget::chartClosed, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, false); }); + QObject::connect(undo_stack, &QUndoStack::indexChanged, [this]() { dbcMsgChanged(); }); } void DetailWidget::showTabBarContextMenu(const QPoint &pt) { @@ -143,12 +147,17 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { if (msg_id.isEmpty()) return; setUpdatesEnabled(false); + + binary_view->setMessage(msg_id); + history_log->setMessage(msg_id); + QStringList warnings; for (auto f : signal_list) f->hide(); - const Msg *msg = dbc()->msg(msg_id); + const DBCMsg *msg = dbc()->msg(msg_id); if (msg) { - for (int i = 0; i < msg->sigs.size(); ++i) { + int i = 0; + for (auto &[name, sig] : msg->sigs) { SignalEdit *form = i < signal_list.size() ? signal_list[i] : nullptr; if (!form) { form = new SignalEdit(i); @@ -161,9 +170,10 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { signals_container->layout()->addWidget(form); signal_list.push_back(form); } - form->setSignal(msg_id, &(msg->sigs[i]), i == show_form_idx); - form->setChartOpened(charts->isChartOpened(msg_id, &(msg->sigs[i]))); + form->setSignal(msg_id, &sig, i == show_form_idx); + form->setChartOpened(charts->isChartOpened(msg_id, &sig)); form->show(); + ++i; } if (msg->size != can->lastMessage(msg_id).dat.size()) warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); @@ -173,9 +183,6 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { remove_msg_act->setEnabled(msg != nullptr); name_label->setText(msgName(msg_id)); - binary_view->setMessage(msg_id); - history_log->setMessage(msg_id); - // Check overlapping bits if (auto overlapping = binary_view->getOverlappingSignals(); !overlapping.isEmpty()) { for (auto s : overlapping) @@ -215,19 +222,13 @@ void DetailWidget::editMsg() { int size = msg ? msg->size : can->lastMessage(id).dat.size(); EditMessageDialog dlg(id, msgName(id), size, this); if (dlg.exec()) { - dbc()->updateMsg(id, dlg.name_edit->text(), dlg.size_spin->value()); - dbcMsgChanged(); + undo_stack->push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value())); } } void DetailWidget::removeMsg() { - QString id = msg_id; - if (auto msg = dbc()->msg(id)) { - QString text = tr("Are you sure you want to remove '%1'").arg(msg->name.c_str()); - if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove Message"), text)) { - dbc()->removeMsg(id); - dbcMsgChanged(); - } + if (auto msg = dbc()->msg(msg_id)) { + undo_stack->push(new RemoveMsgCommand(msg_id)); } } @@ -235,10 +236,10 @@ void DetailWidget::addSignal(int start_bit, int size, bool little_endian) { auto msg = dbc()->msg(msg_id); if (!msg) { for (int i = 1; /**/; ++i) { - std::string name = "NEW_MSG_" + std::to_string(i); - auto it = std::find_if(dbc()->getDBC()->msgs.begin(), dbc()->getDBC()->msgs.end(), [&](auto &m) { return m.name == name; }); - if (it == dbc()->getDBC()->msgs.end()) { - dbc()->updateMsg(msg_id, name.c_str(), can->lastMessage(msg_id).dat.size()); + QString name = QString("NEW_MSG_%1").arg(i); + auto it = std::find_if(dbc()->messages().begin(), dbc()->messages().end(), [&](auto &m) { return m.second.name == name; }); + if (it == dbc()->messages().end()) { + undo_stack->push(new EditMsgCommand(msg_id, name, can->lastMessage(msg_id).dat.size())); msg = dbc()->msg(msg_id); break; } @@ -247,13 +248,12 @@ void DetailWidget::addSignal(int start_bit, int size, bool little_endian) { Signal sig = {}; for (int i = 1; /**/; ++i) { sig.name = "NEW_SIGNAL_" + std::to_string(i); - auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig.name == s.name; }); + auto it = msg->sigs.find(sig.name.c_str()); if (it == msg->sigs.end()) break; } sig.is_little_endian = little_endian; updateSigSizeParamsFromRange(sig, start_bit, size); - dbc()->addSignal(msg_id, sig); - dbcMsgChanged(msg->sigs.size() - 1); + undo_stack->push(new AddSigCommand(msg_id, sig)); } void DetailWidget::resizeSignal(const Signal *sig, int start_bit, int size) { @@ -265,14 +265,13 @@ void DetailWidget::resizeSignal(const Signal *sig, int start_bit, int size) { void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) { auto msg = dbc()->msg(msg_id); if (new_sig.name != sig->name) { - auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return s.name == new_sig.name; }); + auto it = msg->sigs.find(new_sig.name.c_str()); if (it != msg->sigs.end()) { QString warning_str = tr("There is already a signal with the same name '%1'").arg(new_sig.name.c_str()); QMessageBox::warning(this, tr("Failed to save signal"), warning_str); return; } } - auto [start, end] = getSignalRange(&new_sig); if (start < 0 || end >= msg->size * 8) { QString warning_str = tr("Signal size [%1] exceed limit").arg(new_sig.size); @@ -280,16 +279,11 @@ void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) { return; } - dbc()->updateSignal(msg_id, sig->name.c_str(), new_sig); - dbcMsgChanged(); + undo_stack->push(new EditSignalCommand(msg_id, sig, new_sig)); } void DetailWidget::removeSignal(const Signal *sig) { - QString text = tr("Are you sure you want to remove signal '%1'").arg(sig->name.c_str()); - if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove signal"), text)) { - dbc()->removeSignal(msg_id, sig->name.c_str()); - dbcMsgChanged(); - } + undo_stack->push(new RemoveSigCommand(msg_id, sig)); } // EditMessageDialog diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 915e0bde60..dc40eae159 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "tools/cabana/binaryview.h" #include "tools/cabana/chartswidget.h" @@ -26,6 +27,7 @@ public: DetailWidget(ChartsWidget *charts, QWidget *parent); void setMessage(const QString &message_id); void dbcMsgChanged(int show_form_idx = -1); + QUndoStack *undo_stack = nullptr; private: void updateChartState(const QString &id, const Signal *sig, bool opened); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 4b1818cf68..28e344a46e 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -4,6 +4,10 @@ // HistoryLogModel +inline const Signal &get_signal(const DBCMsg *m, int index) { + return std::next(m->sigs.begin(), index)->second; +} + QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { bool has_signal = dbc_msg && !dbc_msg->sigs.empty(); if (role == Qt::DisplayRole) { @@ -11,7 +15,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { if (index.column() == 0) { return QString::number(m.ts, 'f', 2); } - return has_signal ? QString::number(get_raw_value((uint8_t *)m.dat.begin(), m.dat.size(), dbc_msg->sigs[index.column() - 1])) + return has_signal ? QString::number(get_raw_value((uint8_t *)m.dat.begin(), m.dat.size(), get_signal(dbc_msg, index.column() - 1))) : toHex(m.dat); } else if (role == Qt::FontRole && index.column() == 1 && !has_signal) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); @@ -37,7 +41,7 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i if (section == 0) { return "Time"; } - return has_signal ? QString::fromStdString(dbc_msg->sigs[section - 1].name).replace('_', ' ') : "Data"; + return has_signal ? QString::fromStdString(get_signal(dbc_msg, section - 1).name).replace('_', ' ') : "Data"; } else if (role == Qt::BackgroundRole && section > 0 && has_signal) { return QBrush(QColor(getColor(section - 1))); } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index e8b0f5a35b..21be8fc129 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -28,7 +28,7 @@ private: QString msg_id; int row_count = 0; int column_count = 2; - const Msg *dbc_msg = nullptr; + const DBCMsg *dbc_msg = nullptr; std::deque messages; }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 9b85ba7e8d..b8913a3aa7 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include #include "tools/replay/util.h" @@ -41,9 +43,7 @@ MainWindow::MainWindow() : QMainWindow() { dbc_combo->addItem(QString::fromStdString(name)); } dbc_combo->model()->sort(0); - dbc_combo->setEditable(true); dbc_combo->setInsertPolicy(QComboBox::NoInsert); - dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion); messages_layout->addWidget(dbc_combo); messages_widget = new MessagesWidget(this); @@ -102,9 +102,13 @@ MainWindow::MainWindow() : QMainWindow() { QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged); QObject::connect(can, &CANMessages::streamStarted, this, &MainWindow::loadDBCFromFingerprint); QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { + detail_widget->undo_stack->clear(); dbc_combo->setCurrentText(QFileInfo(dbc()->name()).baseName()); setWindowTitle(tr("%1 - Cabana").arg(dbc()->name())); }); + QObject::connect(detail_widget->undo_stack, &QUndoStack::indexChanged, [this](int index) { + setWindowTitle(tr("%1%2 - Cabana").arg(index > 0 ? "* " : "").arg(dbc()->name())); + }); } void MainWindow::createActions() { @@ -116,6 +120,23 @@ void MainWindow::createActions() { file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard); file_menu->addSeparator(); file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption); + + QMenu *edit_menu = menuBar()->addMenu(tr("&Edit")); + auto undo_act = detail_widget->undo_stack->createUndoAction(this, tr("&Undo")); + undo_act->setShortcuts(QKeySequence::Undo); + edit_menu->addAction(undo_act); + auto redo_act = detail_widget->undo_stack->createRedoAction(this, tr("&Rndo")); + redo_act->setShortcuts(QKeySequence::Redo); + edit_menu->addAction(redo_act); + edit_menu->addSeparator(); + + QMenu *commands_menu = edit_menu->addMenu(tr("Command &List")); + auto undo_view = new QUndoView(detail_widget->undo_stack); + undo_view->setWindowTitle(tr("Command List")); + QWidgetAction *commands_act = new QWidgetAction(this); + commands_act->setDefaultWidget(undo_view); + commands_menu->addAction(commands_act); + QMenu *help_menu = menuBar()->addMenu(tr("&Help")); help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); } diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index d0aa2cbb4f..d2d9dc1d2a 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -10,26 +10,25 @@ TEST_CASE("DBCManager::generateDBC") { DBCManager dbc_from_generated(nullptr); dbc_from_generated.open("", dbc_string); - auto dbc = dbc_origin.getDBC(); - auto new_dbc = dbc_from_generated.getDBC(); - REQUIRE(dbc->msgs.size() == new_dbc->msgs.size()); - for (int i = 0; i < dbc->msgs.size(); ++i) { - REQUIRE(dbc->msgs[i].name == new_dbc->msgs[i].name); - REQUIRE(dbc->msgs[i].address == new_dbc->msgs[i].address); - REQUIRE(dbc->msgs[i].size == new_dbc->msgs[i].size); - REQUIRE(dbc->msgs[i].sigs.size() == new_dbc->msgs[i].sigs.size()); - auto &sig = dbc->msgs[i].sigs; - auto &new_sig = new_dbc->msgs[i].sigs; - for (int j = 0; j < sig.size(); ++j) { - REQUIRE(sig[j].name == new_sig[j].name); - REQUIRE(sig[j].start_bit == new_sig[j].start_bit); - REQUIRE(sig[j].msb == new_sig[j].msb); - REQUIRE(sig[j].lsb == new_sig[j].lsb); - REQUIRE(sig[j].size == new_sig[j].size); - REQUIRE(sig[j].is_signed == new_sig[j].is_signed); - REQUIRE(sig[j].factor == new_sig[j].factor); - REQUIRE(sig[j].offset == new_sig[j].offset); - REQUIRE(sig[j].is_little_endian == new_sig[j].is_little_endian); + auto &msgs = dbc_origin.messages(); + auto &new_msgs = dbc_from_generated.messages(); + REQUIRE(msgs.size() == new_msgs.size()); + for (auto &[address, m] : msgs) { + auto new_m = new_msgs.at(address); + REQUIRE(m.name == new_m.name); + REQUIRE(m.size == new_m.size); + REQUIRE(m.sigs.size() == new_m.sigs.size()); + for (auto &[name, sig] : m.sigs) { + auto &new_sig = new_m.sigs[name]; + REQUIRE(sig.name == new_sig.name); + REQUIRE(sig.start_bit == new_sig.start_bit); + REQUIRE(sig.msb == new_sig.msb); + REQUIRE(sig.lsb == new_sig.lsb); + REQUIRE(sig.size == new_sig.size); + REQUIRE(sig.is_signed == new_sig.is_signed); + REQUIRE(sig.factor == new_sig.factor); + REQUIRE(sig.offset == new_sig.offset); + REQUIRE(sig.is_little_endian == new_sig.is_little_endian); } } }