Cabana: Added support for undo & redo (#26440)

* undo/redo

* display command list to rolling the state backwards or forward

* update detailview after rolling states

* add * to title bar to indicate dbc has changed

* fix signal pointer address changed after removed

* cleanup

* fix id error

* clear undo stack after dbc file changed

* cleanup

* use map

* cleanup

* typo
This commit is contained in:
Dean Lee 2022-11-11 02:37:38 +08:00 committed by GitHub
parent b320ac6c23
commit 7c922eafe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 277 additions and 110 deletions

View File

@ -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'):

View File

@ -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();

View File

@ -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;
};

View File

@ -240,7 +240,7 @@ void ChartView::resizeEvent(QResizeEvent *event) {
void ChartView::updateTitle() {
chart()->setTitle(signal->name.c_str());
msg_title->setHtml(tr("%1 <font color=\"gray\">%2</font>").arg(dbc()->msg(id)->name.c_str()).arg(id));
msg_title->setHtml(tr("%1 <font color=\"gray\">%2</font>").arg(dbc()->msg(id)->name).arg(id));
}
void ChartView::updateFromSettings() {

75
tools/cabana/commands.cc Normal file
View File

@ -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); }

63
tools/cabana/commands.h Normal file
View File

@ -0,0 +1,63 @@
#pragma once
#include <QUndoCommand>
#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 = {};
};

View File

@ -10,32 +10,36 @@ DBCManager::~DBCManager() {}
void DBCManager::open(const QString &dbc_file_name) {
dbc = const_cast<DBC *>(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 *>(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 *>(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 *>(msg(id))) {
emit signalAdded(&m->sigs.emplace_back(sig));
if (auto m = const_cast<DBCMsg *>(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 *>(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<DBCMsg *>(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 *>(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<DBCMsg *>(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);
}
}

View File

@ -1,9 +1,16 @@
#pragma once
#include <map>
#include <QObject>
#include <QString>
#include "opendbc/can/common_dbc.h"
struct DBCMsg {
QString name;
uint32_t size;
std::map<QString, Signal> 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<uint32_t, DBCMsg> &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<uint32_t, const Msg *> msg_map;
std::map<uint32_t, DBCMsg> msgs;
};
// TODO: Add helper function in dbc.h
@ -54,5 +61,5 @@ std::pair<int, int> 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;
}

View File

@ -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

View File

@ -3,6 +3,7 @@
#include <QScrollArea>
#include <QTabBar>
#include <QToolBar>
#include <QUndoStack>
#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);

View File

@ -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)));
}

View File

@ -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<CanData> messages;
};

View File

@ -12,7 +12,9 @@
#include <QMessageBox>
#include <QScreen>
#include <QToolBar>
#include <QUndoView>
#include <QVBoxLayout>
#include <QWidgetAction>
#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);
}

View File

@ -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);
}
}
}