mirror of https://github.com/commaai/openpilot.git
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:
parent
b320ac6c23
commit
7c922eafe9
|
@ -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'):
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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); }
|
|
@ -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 = {};
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue