diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index 8d6b0f656f..0534ce39ff 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -222,11 +222,11 @@ int DBCFile::msgCount() const { } QString DBCFile::name() const { - return name_; + return name_.isEmpty() ? "untitled" : name_; } bool DBCFile::isEmpty() const { - return (signalCount() == 0) && name().isEmpty(); + return (signalCount() == 0) && name_.isEmpty(); } void DBCFile::parseExtraInfo(const QString &content) { diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index b17d535131..390ded8512 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -19,20 +19,6 @@ bool DBCManager::open(SourceSet s, const QString &dbc_file_name, QString *error) 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; - } - } } try { @@ -58,6 +44,38 @@ bool DBCManager::open(SourceSet s, const QString &name, const QString &content, return true; } +void DBCManager::close(SourceSet s) { + // Build new list of dbc files, removing the ones that match the sourceset + QList> new_dbc_files; + for (auto entry : dbc_files) { + if (entry.first == s) { + delete entry.second; + } else { + new_dbc_files.push_back(entry); + } + } + + dbc_files = new_dbc_files; + emit DBCFileChanged(); +} + +void DBCManager::close(DBCFile *dbc_file) { + assert(dbc_file != nullptr); + + // Build new list of dbc files, removing the one that matches dbc_file* + QList> new_dbc_files; + for (auto entry : dbc_files) { + if (entry.second == dbc_file) { + delete entry.second; + } else { + new_dbc_files.push_back(entry); + } + } + + dbc_files = new_dbc_files; + emit DBCFileChanged(); +} + void DBCManager::closeAll() { if (dbc_files.isEmpty()) return; @@ -69,6 +87,29 @@ void DBCManager::closeAll() { emit DBCFileChanged(); } +void DBCManager::removeSourcesFromFile(DBCFile *dbc_file, SourceSet s) { + assert(dbc_file != nullptr); + + // Build new list of dbc files, for the given dbc_file* remove s from the current sources + QList> new_dbc_files; + for (auto entry : dbc_files) { + if (entry.second == dbc_file) { + SourceSet ss = (entry.first == SOURCE_ALL) ? sources : entry.first; + ss -= s; + if (ss.empty()) { // Close file if no more sources remain + delete dbc_file; + } else { + new_dbc_files.push_back({ss, dbc_file}); + } + } else { + new_dbc_files.push_back(entry); + } + } + + dbc_files = new_dbc_files; + emit DBCFileChanged(); +} + void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { auto sources_dbc_file = findDBCFile(id); diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index a1b9e2ae3a..96d9380dbd 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -24,7 +24,10 @@ public: ~DBCManager() {} 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 close(SourceSet s); + void close(DBCFile *dbc_file); void closeAll(); + void removeSourcesFromFile(DBCFile *dbc_file, SourceSet s); void addSignal(const MessageId &id, const cabana::Signal &sig); void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); @@ -73,3 +76,17 @@ inline QString msgName(const MessageId &id) { auto msg = dbc()->msg(id); return msg ? msg->name : UNTITLED; } + +inline QString toString(SourceSet ss) { + if (ss == SOURCE_ALL) { + return "all"; + } else { + QStringList ret; + QList source_list = ss.values(); + std::sort(source_list.begin(), source_list.end()); + for (auto s : source_list) { + ret << QString::number(s); + } + return ret.join(", "); + } +} diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 05545a17fc..dad809afe2 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -92,8 +92,7 @@ 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); + manage_dbcs_menu = file_menu->addMenu(tr("Manage &DBC Files")); open_recent_menu = file_menu->addMenu(tr("Open &Recent")); for (int i = 0; i < MAX_RECENT_FILES; ++i) { @@ -113,7 +112,7 @@ void MainWindow::createActions() { load_opendbc_menu->addAction(QString::fromStdString(name), this, &MainWindow::openOpendbcFile); } - file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard); + file_menu->addAction(tr("Load DBC From Clipboard"), [=]() { loadFromClipboard(); }); file_menu->addSeparator(); save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save); @@ -122,7 +121,7 @@ void MainWindow::createActions() { 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); + copy_dbc_to_clipboard = file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveToClipboard); file_menu->addSeparator(); file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption)->setShortcuts(QKeySequence::Preferences); @@ -275,18 +274,20 @@ void MainWindow::openFile() { } } -void MainWindow::openFileForSource() { - if (auto action = qobject_cast(sender())) { - uint8_t source = action->data().value(); - 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::openFileForSource(SourceSet s) { + QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)"); + if (!fn.isEmpty()) { + loadFile(fn, s, false); } } +void MainWindow::newFileForSource(SourceSet s) { + remindSaveChanges(); + + dbc()->close(s); + dbc()->open(s, "", ""); +} + void MainWindow::loadFile(const QString &fn, SourceSet s, bool close_all) { if (!fn.isEmpty()) { QString dbc_fn = fn; @@ -307,6 +308,7 @@ void MainWindow::loadFile(const QString &fn, SourceSet s, bool close_all) { dbc()->closeAll(); } + dbc()->close(s); bool ret = dbc()->open(s, dbc_fn, &error); if (ret) { updateRecentFiles(fn); @@ -346,13 +348,17 @@ void MainWindow::loadDBCFromOpendbc(const QString &name) { updateLoadSaveMenus(); } -void MainWindow::loadDBCFromClipboard() { +void MainWindow::loadFromClipboard(SourceSet s, bool close_all) { remindSaveChanges(); QString dbc_str = QGuiApplication::clipboard()->text(); QString error; - dbc()->closeAll(); - bool ret = dbc()->open(SOURCE_ALL, "", dbc_str, &error); + if (close_all) { + dbc()->closeAll(); + } + + dbc()->close(s); + bool ret = dbc()->open(s, "", dbc_str, &error); if (ret && dbc()->msgCount() > 0) { QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); } else { @@ -388,9 +394,22 @@ void MainWindow::loadDBCFromFingerprint() { } void MainWindow::save() { - saveFile(); + // Save all open DBC files + for (auto &[s, dbc_file] : dbc()->dbc_files) { + if (dbc_file->isEmpty()) continue; + saveFile(dbc_file); + } +} + +void MainWindow::saveAs() { + // Save as all open DBC files. Should not be called with more than 1 file open + for (auto &[s, dbc_file] : dbc()->dbc_files) { + if (dbc_file->isEmpty()) continue; + saveFileAs(dbc_file); + } } + void MainWindow::autoSave() { if (!UndoStack::instance()->isClean()) { for (auto &[_, dbc_file] : dbc()->dbc_files) { @@ -407,46 +426,86 @@ void MainWindow::cleanupAutoSaveFile() { } } -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 if (!dbc_file->isEmpty()) { - 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); - } +void MainWindow::closeFile(DBCFile *dbc_file) { + assert(dbc_file != nullptr); + remindSaveChanges(); + + dbc()->close(dbc_file); + + // Ensure we always have at least one file open + if (dbc()->dbcCount() == 0) { + newFile(); + } +} + +void MainWindow::saveFile(DBCFile *dbc_file) { + assert(dbc_file != nullptr); + + SourceSet s; + for (auto &[s_, dbc_file_] : dbc()->dbc_files) { + if (dbc_file_ == dbc_file) { + s = s_; + break; + } + } + + if (!dbc_file->filename.isEmpty()) { + dbc_file->save(); + updateRecentFiles(dbc_file->filename); + } else if (!dbc_file->isEmpty()) { + QString fn = QFileDialog::getSaveFileName(this, tr("Save File (bus: %1)").arg(toString(s)), 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); + updateLoadSaveMenus(); } -void MainWindow::saveAs() { - // Assume only one file is open - for (auto &[s, dbc_file] : dbc()->dbc_files) { - if (dbc_file->isEmpty()) continue; - 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); +void MainWindow::saveFileAs(DBCFile *dbc_file) { + assert(dbc_file != nullptr); + + SourceSet s; + for (auto &[s_, dbc_file_] : dbc()->dbc_files) { + if (dbc_file_ == dbc_file) { + s = s_; + break; } } + + QString fn = QFileDialog::getSaveFileName(this, tr("Save File (bus: %1)").arg(toString(s)), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); + if (!fn.isEmpty()) { + dbc_file->saveAs(fn); + updateRecentFiles(fn); + updateLoadSaveMenus(); + } } -void MainWindow::saveDBCToClipboard() { - // Assume only one file is open +void MainWindow::removeBusFromFile(DBCFile *dbc_file, uint8_t source) { + assert(dbc_file != nullptr); + SourceSet ss = {source, uint8_t(source + 128), uint8_t(source + 192)}; + dbc()->removeSourcesFromFile(dbc_file, ss); + updateLoadSaveMenus(); +} + + +void MainWindow::saveToClipboard() { + // Copy all open DBC files to clipboard. Should not be called with more than 1 file open for (auto &[s, dbc_file] : dbc()->dbc_files) { if (dbc_file->isEmpty()) continue; - QGuiApplication::clipboard()->setText(dbc_file->generateDBC()); - QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); + saveFileToClipboard(dbc_file); } } +void MainWindow::saveFileToClipboard(DBCFile *dbc_file) { + assert(dbc_file != nullptr); + QGuiApplication::clipboard()->setText(dbc_file->generateDBC()); + QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); +} + void MainWindow::updateSources(const SourceSet &s) { sources = s; updateLoadSaveMenus(); @@ -462,7 +521,6 @@ void MainWindow::updateLoadSaveMenus() { save_dbc->setText(tr("Save DBC...")); } - // TODO: Support save as for multiple files save_dbc_as->setEnabled(cnt == 1); // TODO: Support clipboard for multiple files @@ -472,35 +530,92 @@ void MainWindow::updateLoadSaveMenus() { QList 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(); + manage_dbcs_menu->clear(); - std::map dbc_files; 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) { - if (!d->second->name().isEmpty()) { - name = tr("%1").arg(d->second->name()); - } else { - name = "untitled"; + + SourceSet ss = {source, uint8_t(source + 128), uint8_t(source + 192)}; + + QMenu *bus_menu = new QMenu(this); + + // New + QAction *new_action = new QAction(this); + new_action->setText(tr("New DBC File...")); + QObject::connect(new_action, &QAction::triggered, [=]() { newFileForSource(ss); }); + bus_menu->addAction(new_action); + + // Open + QAction *open_action = new QAction(this); + open_action->setText(tr("Open DBC File...")); + QObject::connect(open_action, &QAction::triggered, [=]() { openFileForSource(ss); }); + bus_menu->addAction(open_action); + + // Open + QAction *load_clipboard_action = new QAction(this); + load_clipboard_action->setText(tr("Load DBC From Clipboard...")); + QObject::connect(load_clipboard_action, &QAction::triggered, [=]() { loadFromClipboard(ss, false); }); + bus_menu->addAction(load_clipboard_action); + + // Show sub-menu for each dbc for this source. + QStringList bus_menu_fns; + for (auto it : dbc()->dbc_files) { + auto &[src, dbc_file] = it; + if (!src.contains(source) && (src != SOURCE_ALL)) { + continue; } - dbc_files[d->second->filename].push_back(QString::number(source)); + + QString fn = dbc_file->filename.isEmpty() ? "untitled" : QFileInfo(dbc_file->filename).baseName(); + // QMenu *manage_menu = bus_menu; + + bus_menu->addSeparator(); + QAction *fn_action = new QAction(this); + fn_action->setText(fn + " (" + toString(src) + ")"); + fn_action->setEnabled(false); + bus_menu->addAction(fn_action); + + // Save + QAction *save_action = new QAction(this); + save_action->setText(tr("Save...")); + bus_menu->addAction(save_action); + QObject::connect(save_action, &QAction::triggered, [=](){ saveFile(it.second); }); + + // Save as + QAction *save_as_action = new QAction(this); + save_as_action->setText(tr("Save As...")); + bus_menu->addAction(save_as_action); + QObject::connect(save_as_action, &QAction::triggered, [=](){ saveFileAs(it.second); }); + + // Copy to clipboard + QAction *save_clipboard_action = new QAction(this); + save_clipboard_action->setText(tr("Copy to Clipboard...")); + bus_menu->addAction(save_clipboard_action); + QObject::connect(save_clipboard_action, &QAction::triggered, [=](){ saveFileToClipboard(it.second); }); + + // Remove from this bus + QAction *remove_action = new QAction(this); + remove_action->setText(tr("Remove from this bus...")); + bus_menu->addAction(remove_action); + QObject::connect(remove_action, &QAction::triggered, [=](){ removeBusFromFile(it.second, source); }); + + // Close/Remove from all buses + QAction *close_action = new QAction(this); + close_action->setText(tr("Remove from all buses...")); + bus_menu->addAction(close_action); + QObject::connect(close_action, &QAction::triggered, [=](){ closeFile(it.second); }); + + bus_menu_fns << fn; } - 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); + manage_dbcs_menu->addMenu(bus_menu); + QString bus_menu_title = bus_menu_fns.size() ? bus_menu_fns.join(", ") : "No DBCs loaded"; + bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(bus_menu_title)); } QStringList title; - for (auto &[filename, src] : dbc_files) { - QString bus = dbc_files.size() == 1 ? "all" : src.join(","); - title.push_back("[" + bus + "]" + QFileInfo(filename).baseName()); + for (auto &[src, dbc_file] : dbc()->dbc_files) { + QString fn = dbc_file->filename.isEmpty() ? "untitled" : QFileInfo(dbc_file->filename).baseName(); + title.push_back(tr("(%1) %2").arg(toString(src)).arg(fn)); } setWindowFilePath(title.join(" | ")); } diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 3445642d4d..5e76d86e5c 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -27,15 +27,13 @@ public slots: void openRoute(); void newFile(); void openFile(); - void openFileForSource(); void openRecentFile(); void openOpendbcFile(); void loadDBCFromOpendbc(const QString &name); void loadDBCFromFingerprint(); - void loadDBCFromClipboard(); void save(); void saveAs(); - void saveDBCToClipboard(); + void saveToClipboard(); void updateSources(const SourceSet &s); signals: @@ -44,7 +42,14 @@ signals: protected: void remindSaveChanges(); - void saveFile(); + void closeFile(DBCFile *dbc_file); + void saveFile(DBCFile *dbc_file); + void saveFileAs(DBCFile *dbc_file); + void saveFileToClipboard(DBCFile *dbc_file); + void removeBusFromFile(DBCFile *dbc_file, uint8_t source); + void loadFromClipboard(SourceSet s = SOURCE_ALL, bool close_all = true); + void openFileForSource(SourceSet s); + void newFileForSource(SourceSet s); void autoSave(); void cleanupAutoSaveFile(); void updateRecentFiles(const QString &fn); @@ -79,7 +84,7 @@ protected: enum { MAX_RECENT_FILES = 15 }; QAction *recent_files_acts[MAX_RECENT_FILES] = {}; QMenu *open_recent_menu = nullptr; - QMenu *open_dbc_for_source = nullptr; + QMenu *manage_dbcs_menu = nullptr; QAction *save_dbc = nullptr; QAction *save_dbc_as = nullptr; QAction *copy_dbc_to_clipboard = nullptr;