mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-10 20:02:13 +08:00
cabana: add support for multiple DBC files (#27435)
* split out dbc file logic into separate file * emit dbc file changed * change to list of dbc files * handle missing DBC file * dbcmanager emits signal * close existing files before loading new one * add placeholder open for bus menu * implement open dbc for bus * emit signals relevant for current dbc * move logic into findDBC * fix use after free * show currently loaded names in load dialog * enable/disable load save menus * handle saving * add save as to recents * handle file already open * do not assert * fix return and throw on file open error * fix test maybe * remove todo * typo * fix new dbc * fix loading fingerprint on macos * handle replacing a currently open dbc * fix reference and handle exception * fix indendation old-commit-hash: 4ab2131ec9e21d9784f2f423bd6b21c9914cd661
This commit is contained in:
@@ -28,7 +28,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset
|
||||
|
||||
prev_moc_path = cabana_env['QT_MOCHPREFIX']
|
||||
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
|
||||
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbc.cc', 'dbcmanager.cc',
|
||||
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbc.cc', 'dbcfile.cc', 'dbcmanager.cc',
|
||||
'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "tools/cabana/dbcfile.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextStream>
|
||||
#include <QVector>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent) {
|
||||
QFile file(dbc_file_name);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
name_ = QFileInfo(dbc_file_name).baseName();
|
||||
|
||||
// Remove auto save file extension
|
||||
if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) {
|
||||
filename = dbc_file_name.left(dbc_file_name.length() - AUTO_SAVE_EXTENSION.length());
|
||||
} else {
|
||||
filename = dbc_file_name;
|
||||
}
|
||||
open(file.readAll());
|
||||
} else {
|
||||
throw std::runtime_error("Failed to open file.");
|
||||
}
|
||||
}
|
||||
|
||||
DBCFile::DBCFile(const QString &name, const QString &content, QObject *parent) : QObject(parent), name_(name), filename("") {
|
||||
// Open from clipboard
|
||||
open(content);
|
||||
}
|
||||
|
||||
void DBCFile::open(const QString &content) {
|
||||
std::istringstream stream(content.toStdString());
|
||||
auto dbc = const_cast<DBC *>(dbc_parse_from_stream(name_.toStdString(), stream));
|
||||
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.push_back({});
|
||||
auto &sig = m.sigs.last();
|
||||
sig.name = s.name.c_str();
|
||||
sig.start_bit = s.start_bit;
|
||||
sig.msb = s.msb;
|
||||
sig.lsb = s.lsb;
|
||||
sig.size = s.size;
|
||||
sig.is_signed = s.is_signed;
|
||||
sig.factor = s.factor;
|
||||
sig.offset = s.offset;
|
||||
sig.is_little_endian = s.is_little_endian;
|
||||
sig.updatePrecision();
|
||||
}
|
||||
}
|
||||
parseExtraInfo(content);
|
||||
|
||||
delete dbc;
|
||||
}
|
||||
|
||||
bool DBCFile::save() {
|
||||
assert (!filename.isEmpty());
|
||||
if (writeContents(filename)) {
|
||||
cleanupAutoSaveFile();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DBCFile::saveAs(const QString &new_filename) {
|
||||
filename = new_filename;
|
||||
return save();
|
||||
}
|
||||
|
||||
bool DBCFile::autoSave() {
|
||||
if (!filename.isEmpty()) {
|
||||
return writeContents(filename + AUTO_SAVE_EXTENSION);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DBCFile::cleanupAutoSaveFile() {
|
||||
if (!filename.isEmpty()) {
|
||||
QFile::remove(filename + AUTO_SAVE_EXTENSION);
|
||||
}
|
||||
}
|
||||
|
||||
bool DBCFile::writeContents(const QString &fn) {
|
||||
QFile file(fn);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(generateDBC().toUtf8());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cabana::Signal *DBCFile::addSignal(const MessageId &id, const cabana::Signal &sig) {
|
||||
if (auto m = const_cast<cabana::Msg *>(msg(id.address))) {
|
||||
m->sigs.push_back(sig);
|
||||
return &m->sigs.last();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cabana::Signal *DBCFile::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) {
|
||||
if (auto m = const_cast<cabana::Msg *>(msg(id))) {
|
||||
if (auto s = (cabana::Signal *)m->sig(sig_name)) {
|
||||
*s = sig;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cabana::Signal *DBCFile::getSignal(const MessageId &id, const QString &sig_name) {
|
||||
if (auto m = const_cast<cabana::Msg *>(msg(id))) {
|
||||
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; });
|
||||
if (it != m->sigs.end()) {
|
||||
return &(*it);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DBCFile::removeSignal(const MessageId &id, const QString &sig_name) {
|
||||
if (auto m = const_cast<cabana::Msg *>(msg(id))) {
|
||||
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; });
|
||||
if (it != m->sigs.end()) {
|
||||
m->sigs.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size) {
|
||||
auto &m = msgs[id.address];
|
||||
m.name = name;
|
||||
m.size = size;
|
||||
}
|
||||
|
||||
void DBCFile::removeMsg(const MessageId &id) {
|
||||
msgs.erase(id.address);
|
||||
}
|
||||
|
||||
std::map<uint32_t, cabana::Msg> DBCFile::getMessages() {
|
||||
return msgs;
|
||||
}
|
||||
|
||||
const cabana::Msg *DBCFile::msg(const MessageId &id) const {
|
||||
return msg(id.address);
|
||||
}
|
||||
|
||||
const cabana::Msg *DBCFile::msg(uint32_t address) const {
|
||||
auto it = msgs.find(address);
|
||||
return it != msgs.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
const cabana::Msg* DBCFile::msg(const QString &name) {
|
||||
for (auto &[_, msg] : msgs) {
|
||||
if (msg.name == name) {
|
||||
return &msg;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
QStringList DBCFile::signalNames() const {
|
||||
// Used for autocompletion
|
||||
QStringList ret;
|
||||
for (auto const& [_, msg] : msgs) {
|
||||
for (auto sig: msg.getSignals()) {
|
||||
ret << sig->name;
|
||||
}
|
||||
}
|
||||
ret.sort();
|
||||
ret.removeDuplicates();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int DBCFile::msgCount() const {
|
||||
return msgs.size();
|
||||
}
|
||||
|
||||
QString DBCFile::name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
void DBCFile::parseExtraInfo(const QString &content) {
|
||||
static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))");
|
||||
static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"(.*)\";)");
|
||||
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (.*);)");
|
||||
auto get_sig = [this](uint32_t address, const QString &name) -> cabana::Signal * {
|
||||
auto m = (cabana::Msg *)msg(address);
|
||||
return m ? (cabana::Signal *)m->sig(name) : nullptr;
|
||||
};
|
||||
|
||||
QTextStream stream((QString *)&content);
|
||||
uint32_t address = 0;
|
||||
while (!stream.atEnd()) {
|
||||
QString line = stream.readLine().trimmed();
|
||||
if (line.startsWith("BO_ ")) {
|
||||
if (auto match = bo_regexp.match(line); match.hasMatch()) {
|
||||
address = match.captured(1).toUInt();
|
||||
}
|
||||
} else if (line.startsWith("SG_ ")) {
|
||||
int offset = 0;
|
||||
auto match = sg_regexp.match(line);
|
||||
if (!match.hasMatch()) {
|
||||
match = sgm_regexp.match(line);
|
||||
offset = 1;
|
||||
}
|
||||
if (match.hasMatch()) {
|
||||
if (auto s = get_sig(address, match.captured(1))) {
|
||||
s->min = match.captured(8 + offset);
|
||||
s->max = match.captured(9 + offset);
|
||||
s->unit = match.captured(10 + offset);
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("VAL_ ")) {
|
||||
if (auto match = val_regexp.match(line); match.hasMatch()) {
|
||||
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
|
||||
QStringList desc_list = match.captured(3).trimmed().split('"');
|
||||
for (int i = 0; i < desc_list.size(); i += 2) {
|
||||
auto val = desc_list[i].trimmed();
|
||||
if (!val.isEmpty() && (i + 1) < desc_list.size()) {
|
||||
auto desc = desc_list[i+1].trimmed();
|
||||
s->val_desc.push_back({val, desc});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("CM_ SG_ ")) {
|
||||
if (auto match = sg_comment_regexp.match(line); match.hasMatch()) {
|
||||
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
|
||||
s->comment = match.captured(3).trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString DBCFile::generateDBC() {
|
||||
QString dbc_string, signal_comment, val_desc;
|
||||
for (auto &[address, m] : msgs) {
|
||||
dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size);
|
||||
for (auto sig : m.getSignals()) {
|
||||
dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [%8|%9] \"%10\" XXX\n")
|
||||
.arg(sig->name)
|
||||
.arg(sig->start_bit)
|
||||
.arg(sig->size)
|
||||
.arg(sig->is_little_endian ? '1' : '0')
|
||||
.arg(sig->is_signed ? '-' : '+')
|
||||
.arg(sig->factor, 0, 'g', std::numeric_limits<double>::digits10)
|
||||
.arg(sig->offset, 0, 'g', std::numeric_limits<double>::digits10)
|
||||
.arg(sig->min)
|
||||
.arg(sig->max)
|
||||
.arg(sig->unit);
|
||||
if (!sig->comment.isEmpty()) {
|
||||
signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment);
|
||||
}
|
||||
if (!sig->val_desc.isEmpty()) {
|
||||
QStringList text;
|
||||
for (auto &[val, desc] : sig->val_desc) {
|
||||
text << QString("%1 \"%2\"").arg(val, desc);
|
||||
}
|
||||
val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" "));
|
||||
}
|
||||
}
|
||||
dbc_string += "\n";
|
||||
}
|
||||
return dbc_string + signal_comment + val_desc;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QSet>
|
||||
#include <QDebug>
|
||||
|
||||
#include "tools/cabana/dbc.h"
|
||||
|
||||
const QString AUTO_SAVE_EXTENSION = ".tmp";
|
||||
|
||||
|
||||
class DBCFile : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DBCFile(const QString &dbc_file_name, QObject *parent=nullptr);
|
||||
DBCFile(const QString &name, const QString &content, QObject *parent=nullptr);
|
||||
~DBCFile() {}
|
||||
|
||||
void open(const QString &content);
|
||||
|
||||
bool save();
|
||||
bool saveAs(const QString &new_filename);
|
||||
bool autoSave();
|
||||
bool writeContents(const QString &fn);
|
||||
void cleanupAutoSaveFile();
|
||||
QString generateDBC();
|
||||
|
||||
cabana::Signal *addSignal(const MessageId &id, const cabana::Signal &sig);
|
||||
cabana::Signal *updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig);
|
||||
cabana::Signal *getSignal(const MessageId &id, const QString &sig_name);
|
||||
void removeSignal(const MessageId &id, const QString &sig_name);
|
||||
|
||||
void updateMsg(const MessageId &id, const QString &name, uint32_t size);
|
||||
void removeMsg(const MessageId &id);
|
||||
|
||||
std::map<uint32_t, cabana::Msg> getMessages();
|
||||
const cabana::Msg *msg(const MessageId &id) const;
|
||||
const cabana::Msg *msg(uint32_t address) const;
|
||||
const cabana::Msg* msg(const QString &name);
|
||||
QStringList signalNames() const;
|
||||
int msgCount() const;
|
||||
QString name() const;
|
||||
|
||||
QString filename;
|
||||
|
||||
private:
|
||||
void parseExtraInfo(const QString &content);
|
||||
std::map<uint32_t, cabana::Msg> msgs;
|
||||
QString name_;
|
||||
};
|
||||
+163
-160
@@ -1,5 +1,4 @@
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
#include <QDebug>
|
||||
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
@@ -8,206 +7,210 @@
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
bool DBCManager::open(const QString &dbc_file_name, QString *error) {
|
||||
QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, dbc_file_name);
|
||||
QFile file(opendbc_file_path);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
return open(dbc_file_name, file.readAll(), error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBCManager::open(const QString &name, const QString &content, QString *error) {
|
||||
try {
|
||||
std::istringstream stream(content.toStdString());
|
||||
auto dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream));
|
||||
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.push_back({});
|
||||
auto &sig = m.sigs.last();
|
||||
sig.name = s.name.c_str();
|
||||
sig.start_bit = s.start_bit;
|
||||
sig.msb = s.msb;
|
||||
sig.lsb = s.lsb;
|
||||
sig.size = s.size;
|
||||
sig.is_signed = s.is_signed;
|
||||
sig.factor = s.factor;
|
||||
sig.offset = s.offset;
|
||||
sig.is_little_endian = s.is_little_endian;
|
||||
sig.updatePrecision();
|
||||
bool DBCManager::open(SourceSet s, const QString &dbc_file_name, QString *error) {
|
||||
for (int i = 0; i < dbc_files.size(); i++) {
|
||||
auto [ss, dbc_file] = dbc_files[i];
|
||||
|
||||
// Check if file is already open, and merge sources
|
||||
if (dbc_file->filename == dbc_file_name) {
|
||||
ss |= s;
|
||||
emit DBCFileChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if there is already a file for this sourceset, then replace it
|
||||
if (ss == s) {
|
||||
try {
|
||||
dbc_files[i] = {s, new DBCFile(dbc_file_name, this)};
|
||||
delete dbc_file;
|
||||
|
||||
emit DBCFileChanged();
|
||||
return true;
|
||||
} catch (std::exception &e) {
|
||||
if (error) *error = e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
parseExtraInfo(content);
|
||||
name_ = name;
|
||||
emit DBCFileChanged();
|
||||
delete dbc;
|
||||
}
|
||||
|
||||
try {
|
||||
dbc_files.push_back({s, new DBCFile(dbc_file_name, this)});
|
||||
} catch (std::exception &e) {
|
||||
if (error) *error = e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
emit DBCFileChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBCManager::parseExtraInfo(const QString &content) {
|
||||
static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))");
|
||||
static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"(.*)\";)");
|
||||
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (.*);)");
|
||||
auto get_sig = [this](uint32_t address, const QString &name) -> cabana::Signal * {
|
||||
auto m = (cabana::Msg *)msg(address);
|
||||
return m ? (cabana::Signal *)m->sig(name) : nullptr;
|
||||
};
|
||||
|
||||
QTextStream stream((QString *)&content);
|
||||
uint32_t address = 0;
|
||||
while (!stream.atEnd()) {
|
||||
QString line = stream.readLine().trimmed();
|
||||
if (line.startsWith("BO_ ")) {
|
||||
if (auto match = bo_regexp.match(line); match.hasMatch()) {
|
||||
address = match.captured(1).toUInt();
|
||||
}
|
||||
} else if (line.startsWith("SG_ ")) {
|
||||
int offset = 0;
|
||||
auto match = sg_regexp.match(line);
|
||||
if (!match.hasMatch()) {
|
||||
match = sgm_regexp.match(line);
|
||||
offset = 1;
|
||||
}
|
||||
if (match.hasMatch()) {
|
||||
if (auto s = get_sig(address, match.captured(1))) {
|
||||
s->min = match.captured(8 + offset);
|
||||
s->max = match.captured(9 + offset);
|
||||
s->unit = match.captured(10 + offset);
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("VAL_ ")) {
|
||||
if (auto match = val_regexp.match(line); match.hasMatch()) {
|
||||
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
|
||||
QStringList desc_list = match.captured(3).trimmed().split('"');
|
||||
for (int i = 0; i < desc_list.size(); i += 2) {
|
||||
auto val = desc_list[i].trimmed();
|
||||
if (!val.isEmpty() && (i + 1) < desc_list.size()) {
|
||||
auto desc = desc_list[i+1].trimmed();
|
||||
s->val_desc.push_back({val, desc});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("CM_ SG_ ")) {
|
||||
if (auto match = sg_comment_regexp.match(line); match.hasMatch()) {
|
||||
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
|
||||
s->comment = match.captured(3).trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
bool DBCManager::open(SourceSet s, const QString &name, const QString &content, QString *error) {
|
||||
try {
|
||||
dbc_files.push_back({s, new DBCFile(name, content, this)});
|
||||
} catch (std::exception &e) {
|
||||
if (error) *error = e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
emit DBCFileChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DBCManager::generateDBC() {
|
||||
QString dbc_string, signal_comment, val_desc;
|
||||
for (auto &[address, m] : msgs) {
|
||||
dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size);
|
||||
for (auto sig : m.getSignals()) {
|
||||
dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [%8|%9] \"%10\" XXX\n")
|
||||
.arg(sig->name)
|
||||
.arg(sig->start_bit)
|
||||
.arg(sig->size)
|
||||
.arg(sig->is_little_endian ? '1' : '0')
|
||||
.arg(sig->is_signed ? '-' : '+')
|
||||
.arg(sig->factor, 0, 'g', std::numeric_limits<double>::digits10)
|
||||
.arg(sig->offset, 0, 'g', std::numeric_limits<double>::digits10)
|
||||
.arg(sig->min)
|
||||
.arg(sig->max)
|
||||
.arg(sig->unit);
|
||||
if (!sig->comment.isEmpty()) {
|
||||
signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment);
|
||||
}
|
||||
if (!sig->val_desc.isEmpty()) {
|
||||
QStringList text;
|
||||
for (auto &[val, desc] : sig->val_desc) {
|
||||
text << QString("%1 \"%2\"").arg(val, desc);
|
||||
}
|
||||
val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" "));
|
||||
}
|
||||
}
|
||||
dbc_string += "\n";
|
||||
void DBCManager::closeAll() {
|
||||
while (dbc_files.size()) {
|
||||
DBCFile *dbc_file = dbc_files.back().second;
|
||||
dbc_files.pop_back();
|
||||
delete dbc_file;
|
||||
}
|
||||
return dbc_string + signal_comment + val_desc;
|
||||
emit DBCFileChanged();
|
||||
}
|
||||
|
||||
void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) {
|
||||
auto &m = msgs[id.address];
|
||||
m.name = name;
|
||||
m.size = size;
|
||||
|
||||
// This DBC applies to all active sources, emit for every source
|
||||
for (uint8_t source : sources) {
|
||||
emit msgUpdated({.source = source, .address = id.address});
|
||||
}
|
||||
}
|
||||
|
||||
void DBCManager::removeMsg(const MessageId &id) {
|
||||
msgs.erase(id.address);
|
||||
|
||||
// This DBC applies to all active sources, emit for every source
|
||||
for (uint8_t source : sources) {
|
||||
emit msgRemoved({.source = source, .address = id.address});
|
||||
}
|
||||
}
|
||||
|
||||
void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) {
|
||||
if (auto m = const_cast<cabana::Msg *>(msg(id.address))) {
|
||||
m->sigs.push_back(sig);
|
||||
auto s = &m->sigs.last();
|
||||
auto sources_dbc_file = findDBCFile(id);
|
||||
assert(sources_dbc_file); // Create new DBC?
|
||||
auto [dbc_sources, dbc_file] = *sources_dbc_file;
|
||||
|
||||
// This DBC applies to all active sources, emit for every source
|
||||
for (uint8_t source : sources) {
|
||||
cabana::Signal *s = dbc_file->addSignal(id, sig);
|
||||
|
||||
if (s != nullptr) {
|
||||
for (uint8_t source : dbc_sources) {
|
||||
emit signalAdded({.source = source, .address = id.address}, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) {
|
||||
if (auto m = const_cast<cabana::Msg *>(msg(id))) {
|
||||
if (auto s = (cabana::Signal *)m->sig(sig_name)) {
|
||||
*s = sig;
|
||||
emit signalUpdated(s);
|
||||
}
|
||||
auto sources_dbc_file = findDBCFile(id);
|
||||
assert(sources_dbc_file); // This should be impossible
|
||||
auto [_, dbc_file] = *sources_dbc_file;
|
||||
|
||||
cabana::Signal *s = dbc_file->updateSignal(id, sig_name, sig);
|
||||
|
||||
if (s != nullptr) {
|
||||
emit signalUpdated(s);
|
||||
}
|
||||
}
|
||||
|
||||
void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) {
|
||||
if (auto m = const_cast<cabana::Msg *>(msg(id))) {
|
||||
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; });
|
||||
if (it != m->sigs.end()) {
|
||||
emit signalRemoved(&(*it));
|
||||
m->sigs.erase(it);
|
||||
}
|
||||
auto sources_dbc_file = findDBCFile(id);
|
||||
assert(sources_dbc_file); // This should be impossible
|
||||
auto [_, dbc_file] = *sources_dbc_file;
|
||||
|
||||
cabana::Signal *s = dbc_file->getSignal(id, sig_name);
|
||||
|
||||
if (s != nullptr) {
|
||||
emit signalRemoved(s);
|
||||
dbc_file->removeSignal(id, sig_name);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList DBCManager::signalNames() {
|
||||
// Used for autocompletion
|
||||
QStringList ret;
|
||||
for (auto const& [_, msg] : msgs) {
|
||||
for (auto sig: msg.getSignals()) {
|
||||
ret << sig->name;
|
||||
}
|
||||
void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) {
|
||||
auto sources_dbc_file = findDBCFile(id);
|
||||
assert(sources_dbc_file); // This should be impossible
|
||||
auto [dbc_sources, dbc_file] = *sources_dbc_file;
|
||||
|
||||
dbc_file->updateMsg(id, name, size);
|
||||
|
||||
for (uint8_t source : dbc_sources) {
|
||||
emit msgUpdated({.source = source, .address = id.address});
|
||||
}
|
||||
}
|
||||
|
||||
void DBCManager::removeMsg(const MessageId &id) {
|
||||
auto sources_dbc_file = findDBCFile(id);
|
||||
assert(sources_dbc_file); // This should be impossible
|
||||
auto [dbc_sources, dbc_file] = *sources_dbc_file;
|
||||
|
||||
dbc_file->removeMsg(id);
|
||||
|
||||
for (uint8_t source : dbc_sources) {
|
||||
emit msgRemoved({.source = source, .address = id.address});
|
||||
}
|
||||
}
|
||||
|
||||
std::map<MessageId, cabana::Msg> DBCManager::getMessages(uint8_t source) {
|
||||
std::map<MessageId, cabana::Msg> ret;
|
||||
|
||||
auto sources_dbc_file = findDBCFile({.source = source, .address = 0});
|
||||
if (!sources_dbc_file) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto [_, dbc_file] = *sources_dbc_file;
|
||||
|
||||
for (auto &[address, msg] : dbc_file->getMessages()) {
|
||||
MessageId id = {.source = source, .address = address};
|
||||
ret[id] = msg;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const cabana::Msg *DBCManager::msg(const MessageId &id) const {
|
||||
auto sources_dbc_file = findDBCFile(id);
|
||||
if (!sources_dbc_file) {
|
||||
return nullptr;
|
||||
}
|
||||
auto [_, dbc_file] = *sources_dbc_file;
|
||||
return dbc_file->msg(id);
|
||||
}
|
||||
|
||||
const cabana::Msg* DBCManager::msg(uint8_t source, const QString &name) {
|
||||
auto sources_dbc_file = findDBCFile({.source = source, .address = 0});
|
||||
if (!sources_dbc_file) {
|
||||
return nullptr;
|
||||
}
|
||||
auto [_, dbc_file] = *sources_dbc_file;
|
||||
return dbc_file->msg(name);
|
||||
}
|
||||
|
||||
QStringList DBCManager::signalNames() const {
|
||||
QStringList ret;
|
||||
|
||||
for (auto &[_, dbc_file] : dbc_files) {
|
||||
ret << dbc_file->signalNames();
|
||||
}
|
||||
|
||||
ret.sort();
|
||||
ret.removeDuplicates();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DBCManager::updateSources(const QSet<uint8_t> &s) {
|
||||
int DBCManager::msgCount() const {
|
||||
int ret = 0;
|
||||
|
||||
for (auto &[_, dbc_file] : dbc_files) {
|
||||
ret += dbc_file->msgCount();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int DBCManager::dbcCount() const {
|
||||
return dbc_files.size();
|
||||
}
|
||||
|
||||
void DBCManager::updateSources(const SourceSet &s) {
|
||||
sources = s;
|
||||
}
|
||||
|
||||
std::optional<std::pair<SourceSet, DBCFile*>> DBCManager::findDBCFile(const uint8_t source) const {
|
||||
// Find DBC file that matches id.source, fall back to SOURCE_ALL if no specific DBC is found
|
||||
|
||||
for (auto &[source_set, dbc_file] : dbc_files) {
|
||||
if (source_set.contains(source)) return {{source_set, dbc_file}};
|
||||
}
|
||||
for (auto &[source_set, dbc_file] : dbc_files) {
|
||||
if (source_set == SOURCE_ALL) return {{sources, dbc_file}};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::pair<SourceSet, DBCFile*>> DBCManager::findDBCFile(const MessageId &id) const {
|
||||
return findDBCFile(id.source);
|
||||
}
|
||||
|
||||
DBCManager *dbc() {
|
||||
static DBCManager dbc_manager(nullptr);
|
||||
return &dbc_manager;
|
||||
|
||||
+24
-35
@@ -9,6 +9,10 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include "tools/cabana/dbc.h"
|
||||
#include "tools/cabana/dbcfile.h"
|
||||
|
||||
typedef QSet<uint8_t> SourceSet;
|
||||
const SourceSet SOURCE_ALL = {};
|
||||
|
||||
class DBCManager : public QObject {
|
||||
Q_OBJECT
|
||||
@@ -16,39 +20,35 @@ class DBCManager : public QObject {
|
||||
public:
|
||||
DBCManager(QObject *parent) {}
|
||||
~DBCManager() {}
|
||||
bool open(const QString &dbc_file_name, QString *error = nullptr);
|
||||
bool open(const QString &name, const QString &content, QString *error = nullptr);
|
||||
QString generateDBC();
|
||||
bool open(SourceSet s, const QString &dbc_file_name, QString *error = nullptr);
|
||||
bool open(SourceSet s, const QString &name, const QString &content, QString *error = nullptr);
|
||||
void closeAll();
|
||||
|
||||
void addSignal(const MessageId &id, const cabana::Signal &sig);
|
||||
void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig);
|
||||
void removeSignal(const MessageId &id, const QString &sig_name);
|
||||
inline int msgCount() const { return msgs.size(); }
|
||||
|
||||
inline QString name() const { return name_; }
|
||||
void updateMsg(const MessageId &id, const QString &name, uint32_t size);
|
||||
void removeMsg(const MessageId &id);
|
||||
inline std::map<MessageId, cabana::Msg> getMessages(uint8_t source) {
|
||||
std::map<MessageId, cabana::Msg> ret;
|
||||
for (auto &[address, msg] : msgs) {
|
||||
MessageId id = {.source = source, .address = address};
|
||||
ret[id] = msg;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
inline const cabana::Msg *msg(const MessageId &id) const { return msg(id.address); }
|
||||
inline const cabana::Msg* msg(uint8_t source, const QString &name) {
|
||||
for (auto &[_, msg] : msgs) {
|
||||
if (msg.name == name) {
|
||||
return &msg;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
QStringList signalNames();
|
||||
std::map<MessageId, cabana::Msg> getMessages(uint8_t source);
|
||||
const cabana::Msg *msg(const MessageId &id) const;
|
||||
const cabana::Msg* msg(uint8_t source, const QString &name);
|
||||
|
||||
QStringList signalNames() const;
|
||||
int msgCount() const;
|
||||
int dbcCount() const;
|
||||
|
||||
std::optional<std::pair<SourceSet, DBCFile*>> findDBCFile(const uint8_t source) const;
|
||||
std::optional<std::pair<SourceSet, DBCFile*>> findDBCFile(const MessageId &id) const;
|
||||
|
||||
QList<std::pair<SourceSet, DBCFile*>> dbc_files;
|
||||
|
||||
private:
|
||||
SourceSet sources;
|
||||
|
||||
public slots:
|
||||
void updateSources(const QSet<uint8_t> &s);
|
||||
void updateSources(const SourceSet &s);
|
||||
|
||||
signals:
|
||||
void signalAdded(MessageId id, const cabana::Signal *sig);
|
||||
@@ -57,17 +57,6 @@ signals:
|
||||
void msgUpdated(MessageId id);
|
||||
void msgRemoved(MessageId id);
|
||||
void DBCFileChanged();
|
||||
|
||||
private:
|
||||
void parseExtraInfo(const QString &content);
|
||||
std::map<uint32_t, cabana::Msg> msgs;
|
||||
QString name_;
|
||||
QSet<uint8_t> sources;
|
||||
|
||||
inline const cabana::Msg *msg(uint32_t address) const {
|
||||
auto it = msgs.find(address);
|
||||
return it != msgs.end() ? &it->second : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
DBCManager *dbc();
|
||||
|
||||
+146
-54
@@ -42,7 +42,7 @@ MainWindow::MainWindow() : QMainWindow() {
|
||||
messages_widget->restoreHeaderState(settings.message_header_state);
|
||||
|
||||
qRegisterMetaType<uint64_t>("uint64_t");
|
||||
qRegisterMetaType<QSet<uint8_t>>("QSet<uint8_t>");
|
||||
qRegisterMetaType<SourceSet>("SourceSet");
|
||||
qRegisterMetaType<ReplyMsgType>("ReplyMsgType");
|
||||
installMessageHandler([this](ReplyMsgType type, const std::string msg) {
|
||||
// use queued connection to recv the log messages from replay.
|
||||
@@ -54,9 +54,13 @@ MainWindow::MainWindow() : QMainWindow() {
|
||||
|
||||
main_win = this;
|
||||
qInstallMessageHandler(qLogMessageHandler);
|
||||
QFile json_file("./car_fingerprint_to_dbc.json");
|
||||
if (json_file.open(QIODevice::ReadOnly)) {
|
||||
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
|
||||
|
||||
for (const QString &fn : {"./car_fingerprint_to_dbc.json", "./tools/cabana/car_fingerprint_to_dbc.json"}) {
|
||||
QFile json_file(fn);
|
||||
if (json_file.open(QIODevice::ReadOnly)) {
|
||||
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setStyleSheet(QString(R"(QMainWindow::separator {
|
||||
@@ -72,6 +76,7 @@ MainWindow::MainWindow() : QMainWindow() {
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::updateStatus);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged);
|
||||
QObject::connect(can, &AbstractStream::sourcesUpdated, dbc(), &DBCManager::updateSources);
|
||||
QObject::connect(can, &AbstractStream::sourcesUpdated, this, &MainWindow::updateSources);
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged);
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MainWindow::undoStackIndexChanged);
|
||||
QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus);
|
||||
@@ -87,6 +92,9 @@ void MainWindow::createActions() {
|
||||
file_menu->addAction(tr("New DBC File"), this, &MainWindow::newFile)->setShortcuts(QKeySequence::New);
|
||||
file_menu->addAction(tr("Open DBC File..."), this, &MainWindow::openFile)->setShortcuts(QKeySequence::Open);
|
||||
|
||||
open_dbc_for_source = file_menu->addMenu(tr("Open &DBC File for Bus"));
|
||||
open_dbc_for_source->setEnabled(false);
|
||||
|
||||
open_recent_menu = file_menu->addMenu(tr("Open &Recent"));
|
||||
for (int i = 0; i < MAX_RECENT_FILES; ++i) {
|
||||
recent_files_acts[i] = new QAction(this);
|
||||
@@ -108,9 +116,14 @@ void MainWindow::createActions() {
|
||||
file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard);
|
||||
|
||||
file_menu->addSeparator();
|
||||
file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save)->setShortcuts(QKeySequence::Save);
|
||||
file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs)->setShortcuts(QKeySequence::SaveAs);
|
||||
file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard);
|
||||
save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save);
|
||||
save_dbc->setShortcuts(QKeySequence::Save);
|
||||
|
||||
save_dbc_as = file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs);
|
||||
save_dbc_as->setShortcuts(QKeySequence::SaveAs);
|
||||
|
||||
copy_dbc_to_clipboard = file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard);
|
||||
|
||||
file_menu->addSeparator();
|
||||
file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption)->setShortcuts(QKeySequence::Preferences);
|
||||
|
||||
@@ -231,7 +244,7 @@ void MainWindow::undoStackCleanChanged(bool clean) {
|
||||
|
||||
void MainWindow::DBCFileChanged() {
|
||||
UndoStack::instance()->clear();
|
||||
setWindowFilePath(QString("%1").arg(dbc()->name()));
|
||||
updateLoadSaveMenus();
|
||||
}
|
||||
|
||||
void MainWindow::openRoute() {
|
||||
@@ -247,7 +260,9 @@ void MainWindow::openRoute() {
|
||||
|
||||
void MainWindow::newFile() {
|
||||
remindSaveChanges();
|
||||
dbc()->open("untitled.dbc", "");
|
||||
dbc()->closeAll();
|
||||
dbc()->open(SOURCE_ALL, "", "");
|
||||
updateLoadSaveMenus();
|
||||
}
|
||||
|
||||
void MainWindow::openFile() {
|
||||
@@ -258,7 +273,19 @@ void MainWindow::openFile() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::loadFile(const QString &fn) {
|
||||
void MainWindow::openFileForSource() {
|
||||
if (auto action = qobject_cast<QAction *>(sender())) {
|
||||
uint8_t source = action->data().value<uint8_t>();
|
||||
assert(source < 64);
|
||||
|
||||
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
|
||||
if (!fn.isEmpty()) {
|
||||
loadFile(fn, {source, uint8_t(source + 128), uint8_t(source + 192)}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::loadFile(const QString &fn, SourceSet s, bool close_all) {
|
||||
if (!fn.isEmpty()) {
|
||||
QString dbc_fn = fn;
|
||||
|
||||
@@ -271,20 +298,24 @@ void MainWindow::loadFile(const QString &fn) {
|
||||
}
|
||||
}
|
||||
|
||||
QFile file(dbc_fn);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
auto dbc_name = QFileInfo(fn).baseName();
|
||||
QString error;
|
||||
bool ret = dbc()->open(dbc_name, file.readAll(), &error);
|
||||
if (ret) {
|
||||
setCurrentFile(fn);
|
||||
statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000);
|
||||
} else {
|
||||
QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC file"), tr("Failed to parse DBC file %1").arg(fn));
|
||||
msg_box.setDetailedText(error);
|
||||
msg_box.exec();
|
||||
}
|
||||
auto dbc_name = QFileInfo(fn).baseName();
|
||||
QString error;
|
||||
|
||||
if (close_all) {
|
||||
dbc()->closeAll();
|
||||
}
|
||||
|
||||
bool ret = dbc()->open(s, dbc_fn, &error);
|
||||
if (ret) {
|
||||
updateRecentFiles(fn);
|
||||
statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000);
|
||||
} else {
|
||||
QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC file"), tr("Failed to parse DBC file %1").arg(fn));
|
||||
msg_box.setDetailedText(error);
|
||||
msg_box.exec();
|
||||
}
|
||||
|
||||
updateLoadSaveMenus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,17 +334,23 @@ void MainWindow::openRecentFile() {
|
||||
}
|
||||
|
||||
void MainWindow::loadDBCFromOpendbc(const QString &name) {
|
||||
if (name != dbc()->name()) {
|
||||
remindSaveChanges();
|
||||
dbc()->open(name);
|
||||
}
|
||||
remindSaveChanges();
|
||||
|
||||
QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, name);
|
||||
|
||||
dbc()->closeAll();
|
||||
dbc()->open(SOURCE_ALL, opendbc_file_path);
|
||||
|
||||
updateLoadSaveMenus();
|
||||
}
|
||||
|
||||
void MainWindow::loadDBCFromClipboard() {
|
||||
remindSaveChanges();
|
||||
QString dbc_str = QGuiApplication::clipboard()->text();
|
||||
QString error;
|
||||
bool ret = dbc()->open("clipboard", dbc_str, &error);
|
||||
|
||||
dbc()->closeAll();
|
||||
bool ret = dbc()->open(SOURCE_ALL, "", dbc_str, &error);
|
||||
if (ret && dbc()->msgCount() > 0) {
|
||||
QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!"));
|
||||
} else {
|
||||
@@ -327,7 +364,7 @@ void MainWindow::loadDBCFromClipboard() {
|
||||
|
||||
void MainWindow::loadDBCFromFingerprint() {
|
||||
// Don't overwrite already loaded DBC
|
||||
if (!dbc()->name().isEmpty()) {
|
||||
if (dbc()->msgCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,54 +386,110 @@ void MainWindow::loadDBCFromFingerprint() {
|
||||
}
|
||||
|
||||
void MainWindow::save() {
|
||||
if (current_file.isEmpty()) {
|
||||
saveAs();
|
||||
} else {
|
||||
saveFile(current_file);
|
||||
}
|
||||
saveFile();
|
||||
}
|
||||
|
||||
void MainWindow::autoSave() {
|
||||
if (!current_file.isEmpty() && !UndoStack::instance()->isClean()) {
|
||||
QFile file(current_file + AUTO_SAVE_EXTENSION);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dbc()->generateDBC().toUtf8());
|
||||
if (!UndoStack::instance()->isClean()) {
|
||||
for (auto &[_, dbc_file] : dbc()->dbc_files) {
|
||||
if (!dbc_file->filename.isEmpty()) {
|
||||
dbc_file->autoSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::cleanupAutoSaveFile() {
|
||||
if (!current_file.isEmpty()) {
|
||||
QFile::remove(current_file + AUTO_SAVE_EXTENSION);
|
||||
for (auto &[_, dbc_file] : dbc()->dbc_files) {
|
||||
dbc_file->cleanupAutoSaveFile();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::saveFile(const QString &fn) {
|
||||
cleanupAutoSaveFile();
|
||||
QFile file(fn);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dbc()->generateDBC().toUtf8());
|
||||
UndoStack::instance()->setClean();
|
||||
setCurrentFile(fn);
|
||||
statusBar()->showMessage(tr("File saved"), 2000);
|
||||
void MainWindow::saveFile() {
|
||||
// Save all open DBC files
|
||||
for (auto &[s, dbc_file] : dbc()->dbc_files) {
|
||||
if (!dbc_file->filename.isEmpty()) {
|
||||
dbc_file->save();
|
||||
updateRecentFiles(dbc_file->filename);
|
||||
} else {
|
||||
QString fn = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)"));
|
||||
if (!fn.isEmpty()) {
|
||||
dbc_file->saveAs(fn);
|
||||
updateRecentFiles(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UndoStack::instance()->setClean();
|
||||
statusBar()->showMessage(tr("File saved"), 2000);
|
||||
}
|
||||
|
||||
void MainWindow::saveAs() {
|
||||
// Assume only one file is open
|
||||
assert(dbc()->dbcCount() > 0);
|
||||
auto &[_, dbc_file] = dbc()->dbc_files.first();
|
||||
|
||||
QString fn = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)"));
|
||||
if (!fn.isEmpty()) {
|
||||
saveFile(fn);
|
||||
dbc_file->saveAs(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::saveDBCToClipboard() {
|
||||
QGuiApplication::clipboard()->setText(dbc()->generateDBC());
|
||||
// Assume only one file is open
|
||||
assert(dbc()->dbcCount() > 0);
|
||||
|
||||
auto &[_, dbc_file] = dbc()->dbc_files.first();
|
||||
QGuiApplication::clipboard()->setText(dbc_file->generateDBC());
|
||||
QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!"));
|
||||
}
|
||||
|
||||
void MainWindow::setCurrentFile(const QString &fn) {
|
||||
current_file = fn;
|
||||
setWindowFilePath(QString("%1").arg(fn));
|
||||
void MainWindow::updateSources(const SourceSet &s) {
|
||||
sources = s;
|
||||
updateLoadSaveMenus();
|
||||
}
|
||||
|
||||
void MainWindow::updateLoadSaveMenus() {
|
||||
if (dbc()->dbcCount() > 1) {
|
||||
save_dbc->setText(tr("Save %1 DBCs...").arg(dbc()->dbcCount()));
|
||||
} else {
|
||||
save_dbc->setText(tr("Save DBC..."));
|
||||
}
|
||||
|
||||
// TODO: Support save as for multiple files
|
||||
save_dbc_as->setEnabled(dbc()->dbcCount() == 1);
|
||||
|
||||
// TODO: Support clipboard for multiple files
|
||||
copy_dbc_to_clipboard->setEnabled(dbc()->dbcCount() == 1);
|
||||
|
||||
|
||||
QList<uint8_t> sources_sorted = sources.toList();
|
||||
std::sort(sources_sorted.begin(), sources_sorted.end());
|
||||
|
||||
open_dbc_for_source->setEnabled(sources.size() > 0);
|
||||
open_dbc_for_source->clear();
|
||||
|
||||
for (uint8_t source : sources_sorted) {
|
||||
if (source >= 64) continue; // Sent and blocked buses are handled implicitly
|
||||
QAction *action = new QAction(this);
|
||||
|
||||
auto d = dbc()->findDBCFile(source);
|
||||
QString name = tr("no DBC");
|
||||
if (d && !d->second->name().isEmpty()) {
|
||||
name = tr("%1").arg(d->second->name());
|
||||
} else if (d) {
|
||||
name = "untitled";
|
||||
}
|
||||
|
||||
action->setText(tr("Bus %1 (current: %2)").arg(source).arg(name));
|
||||
action->setData(source);
|
||||
|
||||
QObject::connect(action, &QAction::triggered, this, &MainWindow::openFileForSource);
|
||||
open_dbc_for_source->addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateRecentFiles(const QString &fn) {
|
||||
settings.recent_files.removeAll(fn);
|
||||
settings.recent_files.prepend(fn);
|
||||
while (settings.recent_files.size() > MAX_RECENT_FILES) {
|
||||
@@ -434,7 +527,6 @@ void MainWindow::remindSaveChanges() {
|
||||
}
|
||||
}
|
||||
UndoStack::instance()->clear();
|
||||
current_file = "";
|
||||
}
|
||||
|
||||
void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) {
|
||||
|
||||
+12
-6
@@ -8,13 +8,12 @@
|
||||
#include <QStatusBar>
|
||||
|
||||
#include "tools/cabana/chartswidget.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
#include "tools/cabana/detailwidget.h"
|
||||
#include "tools/cabana/messageswidget.h"
|
||||
#include "tools/cabana/videowidget.h"
|
||||
#include "tools/cabana/tools/findsimilarbits.h"
|
||||
|
||||
const QString AUTO_SAVE_EXTENSION = ".tmp";
|
||||
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -22,12 +21,13 @@ public:
|
||||
MainWindow();
|
||||
void dockCharts(bool dock);
|
||||
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
|
||||
void loadFile(const QString &fn);
|
||||
void loadFile(const QString &fn, SourceSet s = SOURCE_ALL, bool close_all = true);
|
||||
|
||||
public slots:
|
||||
void openRoute();
|
||||
void newFile();
|
||||
void openFile();
|
||||
void openFileForSource();
|
||||
void openRecentFile();
|
||||
void openOpendbcFile();
|
||||
void loadDBCFromOpendbc(const QString &name);
|
||||
@@ -36,6 +36,7 @@ public slots:
|
||||
void save();
|
||||
void saveAs();
|
||||
void saveDBCToClipboard();
|
||||
void updateSources(const SourceSet &s);
|
||||
|
||||
signals:
|
||||
void showMessage(const QString &msg, int timeout);
|
||||
@@ -43,10 +44,10 @@ signals:
|
||||
|
||||
protected:
|
||||
void remindSaveChanges();
|
||||
void saveFile(const QString &fn);
|
||||
void saveFile();
|
||||
void autoSave();
|
||||
void cleanupAutoSaveFile();
|
||||
void setCurrentFile(const QString &fn);
|
||||
void updateRecentFiles(const QString &fn);
|
||||
void updateRecentFileActions();
|
||||
void createActions();
|
||||
void createDockWindows();
|
||||
@@ -62,6 +63,7 @@ protected:
|
||||
void onlineHelp();
|
||||
void toggleFullScreen();
|
||||
void updateStatus();
|
||||
void updateLoadSaveMenus();
|
||||
|
||||
VideoWidget *video_widget = nullptr;
|
||||
QDockWidget *video_dock;
|
||||
@@ -74,12 +76,16 @@ protected:
|
||||
QLabel *status_label;
|
||||
QJsonDocument fingerprint_to_dbc;
|
||||
QSplitter *video_splitter;;
|
||||
QString current_file = "";
|
||||
enum { MAX_RECENT_FILES = 15 };
|
||||
QAction *recent_files_acts[MAX_RECENT_FILES] = {};
|
||||
QMenu *open_recent_menu = nullptr;
|
||||
QMenu *open_dbc_for_source = nullptr;
|
||||
QAction *save_dbc = nullptr;
|
||||
QAction *save_dbc_as = nullptr;
|
||||
QAction *copy_dbc_to_clipboard = nullptr;
|
||||
int prev_undostack_index = 0;
|
||||
int prev_undostack_count = 0;
|
||||
SourceSet sources;
|
||||
friend class OnlineHelp;
|
||||
};
|
||||
|
||||
|
||||
@@ -64,11 +64,11 @@ signals:
|
||||
void updated();
|
||||
void msgsReceived(const QHash<MessageId, CanData> *);
|
||||
void received(QHash<MessageId, CanData> *);
|
||||
void sourcesUpdated(const QSet<uint8_t> &s);
|
||||
void sourcesUpdated(const SourceSet &s);
|
||||
|
||||
public:
|
||||
QHash<MessageId, CanData> last_msgs;
|
||||
QSet<uint8_t> sources;
|
||||
SourceSet sources;
|
||||
|
||||
protected:
|
||||
virtual void process(QHash<MessageId, CanData> *);
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
// demo route, first segment
|
||||
const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2";
|
||||
|
||||
TEST_CASE("DBCManager::generateDBC") {
|
||||
DBCManager dbc_origin(nullptr);
|
||||
dbc_origin.open("toyota_new_mc_pt_generated");
|
||||
DBCManager dbc_from_generated(nullptr);
|
||||
dbc_from_generated.open("", dbc_origin.generateDBC());
|
||||
TEST_CASE("DBCFile::generateDBC") {
|
||||
QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "toyota_new_mc_pt_generated");
|
||||
DBCFile dbc_origin(fn);
|
||||
DBCFile dbc_from_generated("", dbc_origin.generateDBC());
|
||||
|
||||
REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount());
|
||||
auto msgs = dbc_origin.getMessages(0);
|
||||
auto new_msgs = dbc_from_generated.getMessages(0);
|
||||
auto msgs = dbc_origin.getMessages();
|
||||
auto new_msgs = dbc_from_generated.getMessages();
|
||||
for (auto &[id, m] : msgs) {
|
||||
auto &new_m = new_msgs.at(id);
|
||||
REQUIRE(m.name == new_m.name);
|
||||
@@ -33,7 +32,7 @@ TEST_CASE("DBCManager::generateDBC") {
|
||||
|
||||
TEST_CASE("Parse can messages") {
|
||||
DBCManager dbc(nullptr);
|
||||
dbc.open("toyota_new_mc_pt_generated");
|
||||
dbc.open({0}, "toyota_new_mc_pt_generated");
|
||||
CANParser can_parser(0, "toyota_new_mc_pt_generated", {}, {});
|
||||
|
||||
LogReader log;
|
||||
|
||||
@@ -20,7 +20,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi
|
||||
QHBoxLayout *src_layout = new QHBoxLayout();
|
||||
src_bus_combo = new QComboBox(this);
|
||||
find_bus_combo = new QComboBox(this);
|
||||
QSet<uint8_t> bus_set;
|
||||
SourceSet bus_set;
|
||||
for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) {
|
||||
bus_set << it.key().source;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user