cabana: simplify stream management and remove problematic autosave feature (#32945)

simplify code and remove problematic code
old-commit-hash: c17c34187b
fix-exp-path
Dean Lee 10 months ago committed by GitHub
parent 1b4583a63e
commit a18df70a3f
  1. 29
      tools/cabana/cabana.cc
  2. 9
      tools/cabana/chart/chart.cc
  3. 1
      tools/cabana/chart/chartswidget.h
  4. 3
      tools/cabana/chart/signalselector.cc
  5. 9
      tools/cabana/chart/sparkline.cc
  6. 1
      tools/cabana/chart/sparkline.h
  7. 23
      tools/cabana/dbc/dbcfile.cc
  8. 6
      tools/cabana/dbc/dbcfile.h
  9. 207
      tools/cabana/mainwin.cc
  10. 17
      tools/cabana/mainwin.h
  11. 2
      tools/cabana/signalview.cc
  12. 1
      tools/cabana/signalview.h
  13. 11
      tools/cabana/streams/abstractstream.cc
  14. 21
      tools/cabana/streams/abstractstream.h
  15. 11
      tools/cabana/streams/devicestream.cc
  16. 6
      tools/cabana/streams/devicestream.h
  17. 1
      tools/cabana/streams/livestream.cc
  18. 13
      tools/cabana/streams/pandastream.cc
  19. 6
      tools/cabana/streams/pandastream.h
  20. 17
      tools/cabana/streams/replaystream.cc
  21. 8
      tools/cabana/streams/replaystream.h
  22. 13
      tools/cabana/streams/socketcanstream.cc
  23. 6
      tools/cabana/streams/socketcanstream.h
  24. 16
      tools/cabana/streamselector.cc
  25. 6
      tools/cabana/streamselector.h

@ -38,26 +38,19 @@ int main(int argc, char *argv[]) {
cmd_parser.addOption({"dbc", "dbc file to open", "dbc"});
cmd_parser.process(app);
QString dbc_file = cmd_parser.isSet("dbc") ? cmd_parser.value("dbc") : "";
AbstractStream *stream = nullptr;
if (cmd_parser.isSet("stream")) {
stream = new DeviceStream(&app, cmd_parser.value("zmq"));
} else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) {
PandaStreamConfig config = {};
if (cmd_parser.isSet("panda-serial")) {
config.serial = cmd_parser.value("panda-serial");
}
try {
stream = new PandaStream(&app, config);
stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial")});
} catch (std::exception &e) {
qWarning() << e.what();
return 0;
}
} else if (cmd_parser.isSet("socketcan")) {
SocketCanStreamConfig config = {};
config.device = cmd_parser.value("socketcan");
stream = new SocketCanStream(&app, config);
stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan")});
} else {
uint32_t replay_flags = REPLAY_FLAG_NONE;
if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM;
@ -73,24 +66,14 @@ int main(int argc, char *argv[]) {
route = DEMO_ROUTE;
}
if (!route.isEmpty()) {
auto replay_stream = new ReplayStream(&app);
auto replay_stream = std::make_unique<ReplayStream>(&app);
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
return 0;
}
stream = replay_stream;
stream = replay_stream.release();
}
}
MainWindow w;
if (stream) {
stream->start();
if (!dbc_file.isEmpty()) {
w.loadFile(dbc_file);
}
} else {
w.openStream();
}
w.show();
MainWindow w(stream, cmd_parser.value("dbc"));
return app.exec();
}

@ -155,11 +155,10 @@ void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
}
void ChartView::signalUpdated(const cabana::Signal *sig) {
if (std::any_of(sigs.cbegin(), sigs.cend(), [=](auto &s) { return s.sig == sig; })) {
for (const auto &s : sigs) {
if (s.sig == sig && s.series->color() != sig->color) {
setSeriesColor(s.series, sig->color);
}
auto it = std::find_if(sigs.begin(), sigs.end(), [sig](auto &s) { return s.sig == sig; });
if (it != sigs.end()) {
if (it->series->color() != sig->color) {
setSeriesColor(it->series, sig->color);
}
updateTitle();
updateSeries(sig);

@ -110,7 +110,6 @@ private:
QTimer *align_timer;
int current_theme = 0;
bool value_tip_visible_ = false;
friend class ZoomCommand;
friend class ChartView;
friend class ChartsContainer;
};

@ -83,7 +83,8 @@ void SignalSelector::updateAvailableList(int index) {
MessageId msg_id = msgs_combo->itemData(index).value<MessageId>();
auto selected_items = seletedItems();
for (auto s : dbc()->msg(msg_id)->getSignals()) {
bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig = s](auto it) { return it->msg_id == msg_id && it->sig == sig; });
bool is_selected = std::any_of(selected_items.begin(), selected_items.end(),
[sig = s, &msg_id](auto it) { return it->msg_id == msg_id && it->sig == sig; });
if (!is_selected) {
addItemToList(available_list, msg_id, s);
}

@ -4,8 +4,6 @@
#include <limits>
#include <QPainter>
#include "tools/cabana/streams/abstractstream.h"
void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size) {
const auto &msgs = can->events(msg_id);
@ -14,11 +12,6 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl
auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), range_start, CompareCanEvent());
auto last = std::upper_bound(first, msgs.cend(), range_end, CompareCanEvent());
if (first == last || size.isEmpty()) {
pixmap = QPixmap();
return;
}
points.clear();
double value = 0;
for (auto it = first; it != last; ++it) {
@ -27,7 +20,7 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl
}
}
if (points.empty()) {
if (points.empty() || size.isEmpty()) {
pixmap = QPixmap();
return;
}

@ -5,6 +5,7 @@
#include <vector>
#include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/streams/abstractstream.h"
class Sparkline {
public:

@ -9,10 +9,6 @@ DBCFile::DBCFile(const QString &dbc_file_name) {
if (file.open(QIODevice::ReadOnly)) {
name_ = QFileInfo(dbc_file_name).baseName();
filename = dbc_file_name;
// Remove auto save file extension
if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) {
filename.chop(AUTO_SAVE_EXTENSION.length());
}
parse(file.readAll());
} else {
throw std::runtime_error("Failed to open file.");
@ -20,17 +16,12 @@ DBCFile::DBCFile(const QString &dbc_file_name) {
}
DBCFile::DBCFile(const QString &name, const QString &content) : name_(name), filename("") {
// Open from clipboard
parse(content);
}
bool DBCFile::save() {
assert(!filename.isEmpty());
if (writeContents(filename)) {
cleanupAutoSaveFile();
return true;
}
return false;
return writeContents(filename);
}
bool DBCFile::saveAs(const QString &new_filename) {
@ -38,16 +29,6 @@ bool DBCFile::saveAs(const QString &new_filename) {
return save();
}
bool DBCFile::autoSave() {
return !filename.isEmpty() && writeContents(filename + AUTO_SAVE_EXTENSION);
}
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)) {
@ -75,7 +56,7 @@ cabana::Msg *DBCFile::msg(const QString &name) {
return it != msgs.end() ? &(it->second) : nullptr;
}
cabana::Signal *DBCFile::signal(uint32_t address, const QString name) {
cabana::Signal *DBCFile::signal(uint32_t address, const QString &name) {
auto m = msg(address);
return m ? (cabana::Signal *)m->sig(name) : nullptr;
}

@ -5,8 +5,6 @@
#include "tools/cabana/dbc/dbc.h"
const QString AUTO_SAVE_EXTENSION = ".tmp";
class DBCFile {
public:
DBCFile(const QString &dbc_file_name);
@ -15,9 +13,7 @@ public:
bool save();
bool saveAs(const QString &new_filename);
bool autoSave();
bool writeContents(const QString &fn);
void cleanupAutoSaveFile();
QString generateDBC();
void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment);
@ -27,7 +23,7 @@ public:
cabana::Msg *msg(uint32_t address);
cabana::Msg *msg(const QString &name);
inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); }
cabana::Signal *signal(uint32_t address, const QString name);
cabana::Signal *signal(uint32_t address, const QString &name);
inline QString name() const { return name_.isEmpty() ? "untitled" : name_; }
inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); }

@ -12,6 +12,7 @@
#include <QJsonObject>
#include <QMenuBar>
#include <QMessageBox>
#include <QProgressDialog>
#include <QResizeEvent>
#include <QShortcut>
#include <QTextDocument>
@ -23,9 +24,8 @@
#include "tools/cabana/streamselector.h"
#include "tools/cabana/tools/findsignal.h"
#include "tools/cabana/utils/export.h"
#include "tools/replay/replay.h"
MainWindow::MainWindow() : QMainWindow() {
MainWindow::MainWindow(AbstractStream *stream, const QString &dbc_file) : QMainWindow() {
loadFingerprints();
createDockWindows();
setCentralWidget(center_widget = new CenterWidget(this));
@ -65,10 +65,10 @@ MainWindow::MainWindow() : QMainWindow() {
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged);
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);
QObject::connect(StreamNotifier::instance(), &StreamNotifier::changingStream, this, &MainWindow::changingStream);
QObject::connect(StreamNotifier::instance(), &StreamNotifier::streamStarted, this, &MainWindow::streamStarted);
QTimer::singleShot(0, this, [=]() { stream ? openStream(stream, dbc_file) : selectAndOpenStream(); });
show();
}
void MainWindow::loadFingerprints() {
@ -76,16 +76,12 @@ void MainWindow::loadFingerprints() {
if (json_file.open(QIODevice::ReadOnly)) {
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
}
// get opendbc names
for (auto fn : QDir(OPENDBC_FILE_PATH).entryList({"*.dbc"}, QDir::Files, QDir::Name)) {
opendbc_names << QFileInfo(fn).baseName();
}
}
void MainWindow::createActions() {
// File menu
QMenu *file_menu = menuBar()->addMenu(tr("&File"));
file_menu->addAction(tr("Open Stream..."), this, &MainWindow::openStream);
file_menu->addAction(tr("Open Stream..."), this, &MainWindow::selectAndOpenStream);
close_stream_act = file_menu->addAction(tr("Close stream"), this, &MainWindow::closeStream);
export_to_csv_act = file_menu->addAction(tr("Export to CSV..."), this, &MainWindow::exportToCSV);
close_stream_act->setEnabled(false);
@ -96,20 +92,15 @@ void MainWindow::createActions() {
file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); }, QKeySequence::Open);
manage_dbcs_menu = file_menu->addMenu(tr("Manage &DBC Files"));
QObject::connect(manage_dbcs_menu, &QMenu::aboutToShow, this, &MainWindow::updateLoadSaveMenus);
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);
recent_files_acts[i]->setVisible(false);
QObject::connect(recent_files_acts[i], &QAction::triggered, this, &MainWindow::openRecentFile);
open_recent_menu->addAction(recent_files_acts[i]);
}
updateRecentFileActions();
QObject::connect(open_recent_menu, &QMenu::aboutToShow, this, &MainWindow::updateRecentFileMenu);
file_menu->addSeparator();
QMenu *load_opendbc_menu = file_menu->addMenu(tr("Load DBC from commaai/opendbc"));
// load_opendbc_menu->setStyleSheet("QMenu { menu-scrollable: true; }");
for (const auto &dbc_name : opendbc_names) {
for (const auto &dbc_name : QDir(OPENDBC_FILE_PATH).entryList({"*.dbc"}, QDir::Files, QDir::Name)) {
load_opendbc_menu->addAction(dbc_name, [this, name = dbc_name]() { loadDBCFromOpendbc(name); });
}
@ -180,6 +171,7 @@ void MainWindow::createDockWidgets() {
messages_widget = new MessagesWidget(this);
messages_dock->setWidget(messages_widget);
QObject::connect(messages_widget, &MessagesWidget::titleChanged, messages_dock, &QDockWidget::setWindowTitle);
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage);
// right panel
charts_widget = new ChartsWidget(this);
@ -209,67 +201,52 @@ void MainWindow::createStatusBar() {
progress_bar->setVisible(false);
statusBar()->addWidget(new QLabel(tr("For Help, Press F1")));
statusBar()->addPermanentWidget(progress_bar);
statusBar()->addPermanentWidget(status_label = new QLabel(this));
updateStatus();
}
void MainWindow::createShortcuts() {
auto shortcut = new QShortcut(QKeySequence(Qt::Key_Space), this, nullptr, nullptr, Qt::ApplicationShortcut);
QObject::connect(shortcut, &QShortcut::activated, []() { can->pause(!can->isPaused()); });
QObject::connect(shortcut, &QShortcut::activated, this, []() {
if (can) can->pause(!can->isPaused());
});
// TODO: add more shortcuts here.
}
void MainWindow::undoStackIndexChanged(int index) {
int count = UndoStack::instance()->count();
if (count >= 0) {
QString command_text;
if (index == count) {
command_text = (count == prev_undostack_count ? "Redo " : "") + UndoStack::instance()->text(index - 1);
} else if (index < prev_undostack_index) {
command_text = tr("Undo %1").arg(UndoStack::instance()->text(index));
} else if (index > prev_undostack_index) {
command_text = tr("Redo %1").arg(UndoStack::instance()->text(index - 1));
}
statusBar()->showMessage(command_text, 2000);
}
prev_undostack_index = index;
prev_undostack_count = count;
autoSave();
updateLoadSaveMenus();
}
void MainWindow::undoStackCleanChanged(bool clean) {
if (clean) {
prev_undostack_index = 0;
prev_undostack_count = 0;
}
setWindowModified(!clean);
}
void MainWindow::DBCFileChanged() {
UndoStack::instance()->clear();
updateLoadSaveMenus();
// Update file menu
int cnt = dbc()->nonEmptyDBCCount();
save_dbc->setText(cnt > 1 ? tr("Save %1 DBCs...").arg(cnt) : tr("Save DBC..."));
save_dbc->setEnabled(cnt > 0);
save_dbc_as->setEnabled(cnt == 1);
// TODO: Support clipboard for multiple files
copy_dbc_to_clipboard->setEnabled(cnt == 1);
manage_dbcs_menu->setEnabled(dynamic_cast<DummyStream *>(can) == nullptr);
QStringList title;
for (auto f : dbc()->allDBCFiles()) {
title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name()));
}
setWindowFilePath(title.join(" | "));
}
void MainWindow::openStream() {
AbstractStream *stream = nullptr;
StreamSelector dlg(&stream, this);
void MainWindow::selectAndOpenStream() {
StreamSelector dlg(this);
if (dlg.exec()) {
if (!dlg.dbcFile().isEmpty()) {
loadFile(dlg.dbcFile());
}
stream->start();
statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000);
openStream(dlg.stream(), dlg.dbcFile());
} else if (!can) {
stream = new DummyStream(this);
stream->start();
openStream(new DummyStream(this));
}
}
void MainWindow::closeStream() {
AbstractStream *stream = new DummyStream(this);
stream->start();
openStream(new DummyStream(this));
if (dbc()->nonEmptyDBCCount() > 0) {
emit dbc()->DBCFileChanged();
}
@ -301,18 +278,8 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) {
if (!fn.isEmpty()) {
closeFile(s);
QString dbc_fn = fn;
// Prompt user to load auto saved file if it exists.
if (QFile::exists(fn + AUTO_SAVE_EXTENSION)) {
auto ret = QMessageBox::question(this, tr("Auto saved DBC found"), tr("Auto saved DBC file from previous session found. Do you want to load it instead?"));
if (ret == QMessageBox::Yes) {
dbc_fn += AUTO_SAVE_EXTENSION;
UndoStack::instance()->resetClean(); // Force user to save on close so the auto saved file is not lost
}
}
QString error;
if (dbc()->open(s, dbc_fn, &error)) {
if (dbc()->open(s, fn, &error)) {
updateRecentFiles(fn);
statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000);
} else {
@ -323,15 +290,8 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) {
}
}
void MainWindow::openRecentFile() {
if (auto action = qobject_cast<QAction *>(sender())) {
loadFile(action->data().toString());
}
}
void MainWindow::loadDBCFromOpendbc(const QString &name) {
QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, name);
loadFile(opendbc_file_path);
loadFile(QString("%1/%2").arg(OPENDBC_FILE_PATH, name));
}
void MainWindow::loadFromClipboard(SourceSet s, bool close_all) {
@ -349,13 +309,19 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) {
}
}
void MainWindow::changingStream() {
void MainWindow::openStream(AbstractStream *stream, const QString &dbc_file) {
center_widget->clear();
delete messages_widget;
delete video_splitter;
}
void MainWindow::streamStarted() {
delete can;
can = stream;
can->setParent(this); // take ownership
can->start();
loadFile(dbc_file);
statusBar()->showMessage(tr("Stream [%1] started").arg(can->routeName()), 2000);
bool has_stream = dynamic_cast<DummyStream *>(can) == nullptr;
close_stream_act->setEnabled(has_stream);
export_to_csv_act->setEnabled(has_stream);
@ -372,9 +338,20 @@ void MainWindow::streamStarted() {
newFile();
}
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage);
QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::eventsMerged);
QObject::connect(can, &AbstractStream::sourcesUpdated, this, &MainWindow::updateLoadSaveMenus);
if (has_stream) {
auto wait_dlg = new QProgressDialog(
can->liveStreaming() ? tr("Waiting for the live stream to start...") : tr("Loading segment data..."),
tr("&Abort"), 0, 100, this);
wait_dlg->setWindowModality(Qt::WindowModal);
wait_dlg->setFixedSize(400, wait_dlg->sizeHint().height());
QObject::connect(wait_dlg, &QProgressDialog::canceled, this, &MainWindow::close);
QObject::connect(can, &AbstractStream::eventsMerged, wait_dlg, &QProgressDialog::deleteLater);
QObject::connect(this, &MainWindow::updateProgressBar, wait_dlg, [=](uint64_t cur, uint64_t total, bool success) {
wait_dlg->setValue((int)((cur / (double)total) * 100));
});
}
}
void MainWindow::eventsMerged() {
@ -383,12 +360,8 @@ void MainWindow::eventsMerged() {
.arg(can->routeName())
.arg(car_fingerprint.isEmpty() ? tr("Unknown Car") : car_fingerprint));
// Don't overwrite already loaded DBC
if (!dbc()->nonEmptyDBCCount() && !car_fingerprint.isEmpty()) {
auto dbc_name = fingerprint_to_dbc[car_fingerprint];
if (dbc_name != QJsonValue::Undefined) {
// Prevent dialog that load autosaved file from blocking replay->start().
QTimer::singleShot(0, this, [dbc_name, this]() { loadDBCFromOpendbc(dbc_name.toString()); });
}
if (!dbc()->nonEmptyDBCCount() && fingerprint_to_dbc.object().contains(car_fingerprint)) {
QTimer::singleShot(0, this, [this]() { loadDBCFromOpendbc(fingerprint_to_dbc[car_fingerprint].toString() + ".dbc"); });
}
}
}
@ -409,22 +382,6 @@ void MainWindow::saveAs() {
}
}
void MainWindow::autoSave() {
if (!UndoStack::instance()->isClean()) {
for (auto dbc_file : dbc()->allDBCFiles()) {
if (!dbc_file->filename.isEmpty()) {
dbc_file->autoSave();
}
}
}
}
void MainWindow::cleanupAutoSaveFile() {
for (auto dbc_file : dbc()->allDBCFiles()) {
dbc_file->cleanupAutoSaveFile();
}
}
void MainWindow::closeFile(SourceSet s) {
remindSaveChanges();
if (s == SOURCE_ALL) {
@ -448,7 +405,6 @@ void MainWindow::saveFile(DBCFile *dbc_file) {
assert(dbc_file != nullptr);
if (!dbc_file->filename.isEmpty()) {
dbc_file->save();
updateLoadSaveMenus();
UndoStack::instance()->setClean();
statusBar()->showMessage(tr("File saved"), 2000);
} else if (!dbc_file->isEmpty()) {
@ -464,7 +420,6 @@ void MainWindow::saveFileAs(DBCFile *dbc_file) {
UndoStack::instance()->setClean();
statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000);
updateRecentFiles(fn);
updateLoadSaveMenus();
}
}
@ -483,16 +438,7 @@ void MainWindow::saveFileToClipboard(DBCFile *dbc_file) {
}
void MainWindow::updateLoadSaveMenus() {
int cnt = dbc()->nonEmptyDBCCount();
save_dbc->setText(cnt > 1 ? tr("Save %1 DBCs...").arg(cnt) : tr("Save DBC..."));
save_dbc->setEnabled(cnt > 0);
save_dbc_as->setEnabled(cnt == 1);
// TODO: Support clipboard for multiple files
copy_dbc_to_clipboard->setEnabled(cnt == 1);
manage_dbcs_menu->clear();
manage_dbcs_menu->setEnabled(dynamic_cast<DummyStream *>(can) == nullptr);
for (int source : can->sources) {
if (source >= 64) continue; // Sent and blocked buses are handled implicitly
@ -505,8 +451,8 @@ void MainWindow::updateLoadSaveMenus() {
bus_menu->addAction(tr("Load DBC From Clipboard..."), [=]() { loadFromClipboard(ss, false); });
// Show sub-menu for each dbc for this source.
QString file_name = "No DBCs loaded";
if (auto dbc_file = dbc()->findDBCFile(source)) {
auto dbc_file = dbc()->findDBCFile(source);
if (dbc_file) {
bus_menu->addSeparator();
bus_menu->addAction(dbc_file->name() + " (" + toString(dbc()->sources(dbc_file)) + ")")->setEnabled(false);
bus_menu->addAction(tr("Save..."), [=]() { saveFile(dbc_file); });
@ -514,19 +460,11 @@ void MainWindow::updateLoadSaveMenus() {
bus_menu->addAction(tr("Copy to Clipboard..."), [=]() { saveFileToClipboard(dbc_file); });
bus_menu->addAction(tr("Remove from this bus..."), [=]() { closeFile(ss); });
bus_menu->addAction(tr("Remove from all buses..."), [=]() { closeFile(dbc_file); });
file_name = dbc_file->name();
}
bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? dbc_file->name() : "No DBCs loaded"));
manage_dbcs_menu->addMenu(bus_menu);
bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(file_name));
}
QStringList title;
for (auto f : dbc()->allDBCFiles()) {
title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name()));
}
setWindowFilePath(title.join(" | "));
}
void MainWindow::updateRecentFiles(const QString &fn) {
@ -536,21 +474,21 @@ void MainWindow::updateRecentFiles(const QString &fn) {
settings.recent_files.removeLast();
}
settings.last_dir = QFileInfo(fn).absolutePath();
updateRecentFileActions();
}
void MainWindow::updateRecentFileActions() {
void MainWindow::updateRecentFileMenu() {
open_recent_menu->clear();
int num_recent_files = std::min<int>(settings.recent_files.size(), MAX_RECENT_FILES);
if (!num_recent_files) {
open_recent_menu->addAction(tr("No Recent Files"))->setEnabled(false);
return;
}
for (int i = 0; i < num_recent_files; ++i) {
QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(settings.recent_files[i]).fileName());
recent_files_acts[i]->setText(text);
recent_files_acts[i]->setData(settings.recent_files[i]);
recent_files_acts[i]->setVisible(true);
}
for (int i = num_recent_files; i < MAX_RECENT_FILES; ++i) {
recent_files_acts[i]->setVisible(false);
open_recent_menu->addAction(text, this, [this, i=i](){ loadFile(settings.recent_files[i]); });
}
open_recent_menu->setEnabled(num_recent_files > 0);
}
void MainWindow::remindSaveChanges() {
@ -606,7 +544,6 @@ void MainWindow::toggleChartsDocking() {
}
void MainWindow::closeEvent(QCloseEvent *event) {
cleanupAutoSaveFile();
remindSaveChanges();
installDownloadProgressHandler(nullptr);
@ -618,7 +555,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
// save states
settings.geometry = saveGeometry();
settings.window_state = saveState();
if (!can->liveStreaming()) {
if (can && !can->liveStreaming()) {
settings.video_splitter_state = video_splitter->saveState();
}
settings.message_header_state = messages_widget->saveHeaderState();

@ -20,22 +20,20 @@ class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow();
MainWindow(AbstractStream *stream, const QString &dbc_file);
void toggleChartsDocking();
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
void loadFile(const QString &fn, SourceSet s = SOURCE_ALL);
ChartsWidget *charts_widget = nullptr;
public slots:
void openStream();
void selectAndOpenStream();
void openStream(AbstractStream *stream, const QString &dbc_file = {});
void closeStream();
void exportToCSV();
void changingStream();
void streamStarted();
void newFile(SourceSet s = SOURCE_ALL);
void openFile(SourceSet s = SOURCE_ALL);
void openRecentFile();
void loadDBCFromOpendbc(const QString &name);
void save();
void saveAs();
@ -55,10 +53,8 @@ protected:
void saveFileToClipboard(DBCFile *dbc_file);
void loadFingerprints();
void loadFromClipboard(SourceSet s = SOURCE_ALL, bool close_all = true);
void autoSave();
void cleanupAutoSaveFile();
void updateRecentFiles(const QString &fn);
void updateRecentFileActions();
void updateRecentFileMenu();
void createActions();
void createDockWindows();
void createStatusBar();
@ -70,7 +66,6 @@ protected:
void findSimilarBits();
void findSignal();
void undoStackCleanChanged(bool clean);
void undoStackIndexChanged(int index);
void onlineHelp();
void toggleFullScreen();
void updateStatus();
@ -88,10 +83,8 @@ protected:
QProgressBar *progress_bar;
QLabel *status_label;
QJsonDocument fingerprint_to_dbc;
QStringList opendbc_names;
QSplitter *video_splitter = nullptr;
enum { MAX_RECENT_FILES = 15 };
QAction *recent_files_acts[MAX_RECENT_FILES] = {};
QMenu *open_recent_menu = nullptr;
QMenu *manage_dbcs_menu = nullptr;
QMenu *tools_menu = nullptr;
@ -101,8 +94,6 @@ protected:
QAction *save_dbc_as = nullptr;
QAction *copy_dbc_to_clipboard = nullptr;
QString car_fingerprint;
int prev_undostack_index = 0;
int prev_undostack_count = 0;
QByteArray default_state;
};

@ -494,6 +494,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
}
void SignalView::setMessage(const MessageId &id) {
max_value_width = 0;
filter_edit->clear();
model->setMessage(id);
}
@ -617,7 +618,6 @@ void SignalView::updateState(const std::set<MessageId> *msgs) {
const auto &last_msg = can->lastMessage(model->msg_id);
if (model->rowCount() == 0 || (msgs && !msgs->count(model->msg_id)) || last_msg.dat.size() == 0) return;
int max_value_width = 0;
for (auto item : model->root->children) {
double value = 0;
if (item->sig->getValue(last_msg.dat.data(), last_msg.dat.size(), &value)) {

@ -135,6 +135,7 @@ private:
QTreeView::leaveEvent(event);
}
};
int max_value_width = 0;
int value_column_width = 0;
TreeView *tree;
QLabel *sparkline_label;

@ -10,11 +10,6 @@ static const int EVENT_NEXT_BUFFER_SIZE = 6 * 1024 * 1024; // 6MB
AbstractStream *can = nullptr;
StreamNotifier *StreamNotifier::instance() {
static StreamNotifier notifier;
return &notifier;
}
AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
assert(parent != nullptr);
event_buffer_ = std::make_unique<MonotonicBuffer>(EVENT_NEXT_BUFFER_SIZE);
@ -24,12 +19,6 @@ AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
QObject::connect(this, &AbstractStream::seeking, this, [this](double sec) { current_sec_ = sec; });
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &AbstractStream::updateMasks);
QObject::connect(dbc(), &DBCManager::maskUpdated, this, &AbstractStream::updateMasks);
QObject::connect(this, &AbstractStream::streamStarted, [this]() {
emit StreamNotifier::instance()->changingStream();
delete can;
can = this;
emit StreamNotifier::instance()->streamStarted();
});
}
void AbstractStream::updateMasks() {

@ -96,7 +96,6 @@ signals:
void seeking(double sec);
void seekedTo(double sec);
void timeRangeChanged(const std::optional<std::pair<double, double>> &range);
void streamStarted();
void eventsMerged(const MessageEventsMap &events_map);
void msgsReceived(const std::set<MessageId> *new_msgs, bool has_new_ids);
void sourcesUpdated(const SourceSet &s);
@ -133,15 +132,11 @@ private:
class AbstractOpenStreamWidget : public QWidget {
Q_OBJECT
public:
AbstractOpenStreamWidget(AbstractStream **stream, QWidget *parent = nullptr) : stream(stream), QWidget(parent) {}
virtual bool open() = 0;
virtual QString title() = 0;
AbstractOpenStreamWidget(QWidget *parent = nullptr) : QWidget(parent) {}
virtual AbstractStream *open() = 0;
signals:
void enableOpenButton(bool);
protected:
AbstractStream **stream = nullptr;
};
class DummyStream : public AbstractStream {
@ -149,17 +144,7 @@ class DummyStream : public AbstractStream {
public:
DummyStream(QObject *parent) : AbstractStream(parent) {}
QString routeName() const override { return tr("No Stream"); }
void start() override { emit streamStarted(); }
};
class StreamNotifier : public QObject {
Q_OBJECT
public:
StreamNotifier(QObject *parent = nullptr) : QObject(parent) {}
static StreamNotifier* instance();
signals:
void streamStarted();
void changingStream();
void start() override {}
};
// A global pointer referring to the unique AbstractStream object

@ -33,13 +33,9 @@ void DeviceStream::streamThread() {
}
}
AbstractOpenStreamWidget *DeviceStream::widget(AbstractStream **stream) {
return new OpenDeviceWidget(stream);
}
// OpenDeviceWidget
OpenDeviceWidget::OpenDeviceWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
OpenDeviceWidget::OpenDeviceWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
QRadioButton *msgq = new QRadioButton(tr("MSGQ"));
QRadioButton *zmq = new QRadioButton(tr("ZMQ"));
ip_address = new QLineEdit(this);
@ -62,9 +58,8 @@ OpenDeviceWidget::OpenDeviceWidget(AbstractStream **stream) : AbstractOpenStream
zmq->setChecked(true);
}
bool OpenDeviceWidget::open() {
AbstractStream *OpenDeviceWidget::open() {
QString ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text();
bool msgq = group->checkedId() == 0;
*stream = new DeviceStream(qApp, msgq ? "" : ip);
return true;
return new DeviceStream(qApp, msgq ? "" : ip);
}

@ -6,7 +6,6 @@ class DeviceStream : public LiveStream {
Q_OBJECT
public:
DeviceStream(QObject *parent, QString address = {});
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
inline QString routeName() const override {
return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address);
}
@ -20,9 +19,8 @@ class OpenDeviceWidget : public AbstractOpenStreamWidget {
Q_OBJECT
public:
OpenDeviceWidget(AbstractStream **stream);
bool open() override;
QString title() override { return tr("&Device"); }
OpenDeviceWidget(QWidget *parent = nullptr);
AbstractStream *open() override;
private:
QLineEdit *ip_address;

@ -53,7 +53,6 @@ void LiveStream::startUpdateTimer() {
}
void LiveStream::start() {
emit streamStarted();
stream_thread->start();
startUpdateTimer();
begin_date_time = QDateTime::currentDateTime();

@ -77,13 +77,9 @@ void PandaStream::streamThread() {
}
}
AbstractOpenStreamWidget *PandaStream::widget(AbstractStream **stream) {
return new OpenPandaWidget(stream);
}
// OpenPandaWidget
OpenPandaWidget::OpenPandaWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
OpenPandaWidget::OpenPandaWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
form_layout = new QFormLayout(this);
if (can && dynamic_cast<PandaStream *>(can) != nullptr) {
form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(can->routeName())));
@ -182,12 +178,11 @@ void OpenPandaWidget::buildConfigForm() {
}
}
bool OpenPandaWidget::open() {
AbstractStream *OpenPandaWidget::open() {
try {
*stream = new PandaStream(qApp, config);
return true;
return new PandaStream(qApp, config);
} catch (std::exception &e) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
return false;
return nullptr;
}
}

@ -28,7 +28,6 @@ class PandaStream : public LiveStream {
public:
PandaStream(QObject *parent, PandaStreamConfig config_ = {});
~PandaStream() { stop(); }
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
inline QString routeName() const override {
return QString("Panda: %1").arg(config.serial);
}
@ -45,9 +44,8 @@ class OpenPandaWidget : public AbstractOpenStreamWidget {
Q_OBJECT
public:
OpenPandaWidget(AbstractStream **stream);
bool open() override;
QString title() override { return tr("&Panda"); }
OpenPandaWidget(QWidget *parent = nullptr);
AbstractStream *open() override;
private:
void refreshSerials();

@ -83,11 +83,6 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint
return success;
}
void ReplayStream::start() {
emit streamStarted();
replay->start();
}
bool ReplayStream::eventFilter(const Event *event) {
static double prev_update_ts = 0;
if (event->which == cereal::Event::Which::CAN) {
@ -115,13 +110,9 @@ void ReplayStream::pause(bool pause) {
}
AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) {
return new OpenReplayWidget(stream);
}
// OpenReplayWidget
OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
OpenReplayWidget::OpenReplayWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
QGridLayout *grid_layout = new QGridLayout(this);
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
@ -154,7 +145,7 @@ OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStream
});
}
bool OpenReplayWidget::open() {
AbstractStream *OpenReplayWidget::open() {
QString route = route_edit->text();
QString data_dir;
if (int idx = route.lastIndexOf('/'); idx != -1 && util::file_exists(route.toStdString())) {
@ -173,8 +164,8 @@ bool OpenReplayWidget::open() {
if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC;
if (replay_stream->loadRoute(route, data_dir, flags)) {
*stream = replay_stream.release();
return replay_stream.release();
}
}
return *stream != nullptr;
return nullptr;
}

@ -15,7 +15,7 @@ class ReplayStream : public AbstractStream {
public:
ReplayStream(QObject *parent);
void start() override;
void start() override { replay->start(); }
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
bool eventFilter(const Event *event);
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }
@ -31,7 +31,6 @@ public:
inline Replay *getReplay() const { return replay.get(); }
inline bool isPaused() const override { return replay->isPaused(); }
void pause(bool pause) override;
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
private:
void mergeSegments();
@ -44,9 +43,8 @@ class OpenReplayWidget : public AbstractOpenStreamWidget {
Q_OBJECT
public:
OpenReplayWidget(AbstractStream **stream);
bool open() override;
QString title() override { return tr("&Replay"); }
OpenReplayWidget(QWidget *parent = nullptr);
AbstractStream *open() override;
private:
QLineEdit *route_edit;

@ -66,11 +66,7 @@ void SocketCanStream::streamThread() {
}
}
AbstractOpenStreamWidget *SocketCanStream::widget(AbstractStream **stream) {
return new OpenSocketCanWidget(stream);
}
OpenSocketCanWidget::OpenSocketCanWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->addStretch(1);
@ -104,12 +100,11 @@ void OpenSocketCanWidget::refreshDevices() {
}
bool OpenSocketCanWidget::open() {
AbstractStream *OpenSocketCanWidget::open() {
try {
*stream = new SocketCanStream(qApp, config);
return new SocketCanStream(qApp, config);
} catch (std::exception &e) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to SocketCAN device: '%1'").arg(e.what()));
return false;
return nullptr;
}
return true;
}

@ -18,7 +18,6 @@ class SocketCanStream : public LiveStream {
public:
SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {});
~SocketCanStream() { stop(); }
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
static bool available();
inline QString routeName() const override {
@ -37,9 +36,8 @@ class OpenSocketCanWidget : public AbstractOpenStreamWidget {
Q_OBJECT
public:
OpenSocketCanWidget(AbstractStream **stream);
bool open() override;
QString title() override { return tr("&SocketCAN"); }
OpenSocketCanWidget(QWidget *parent = nullptr);
AbstractStream *open() override;
private:
void refreshDevices();

@ -10,7 +10,7 @@
#include "tools/cabana/streams/replaystream.h"
#include "tools/cabana/streams/socketcanstream.h"
StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) {
StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Open stream"));
QVBoxLayout *layout = new QVBoxLayout(this);
tab = new QTabWidget(this);
@ -33,17 +33,17 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
layout->addWidget(btn_box);
addStreamWidget(ReplayStream::widget(stream));
addStreamWidget(PandaStream::widget(stream));
addStreamWidget(new OpenReplayWidget, tr("&Replay"));
addStreamWidget(new OpenPandaWidget, tr("&Panda"));
if (SocketCanStream::available()) {
addStreamWidget(SocketCanStream::widget(stream));
addStreamWidget(new OpenSocketCanWidget, tr("&SocketCAN"));
}
addStreamWidget(DeviceStream::widget(stream));
addStreamWidget(new OpenDeviceWidget, tr("&Device"));
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() {
setEnabled(false);
if (((AbstractOpenStreamWidget *)tab->currentWidget())->open()) {
if (stream_ = ((AbstractOpenStreamWidget *)tab->currentWidget())->open(); stream_) {
accept();
}
setEnabled(true);
@ -57,8 +57,8 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
});
}
void StreamSelector::addStreamWidget(AbstractOpenStreamWidget *w) {
tab->addTab(w, w->title());
void StreamSelector::addStreamWidget(AbstractOpenStreamWidget *w, const QString &title) {
tab->addTab(w, title);
auto open_btn = btn_box->button(QDialogButtonBox::Open);
QObject::connect(w, &AbstractOpenStreamWidget::enableOpenButton, open_btn, &QPushButton::setEnabled);
}

@ -11,11 +11,13 @@ class StreamSelector : public QDialog {
Q_OBJECT
public:
StreamSelector(AbstractStream **stream, QWidget *parent = nullptr);
void addStreamWidget(AbstractOpenStreamWidget *w);
StreamSelector(QWidget *parent = nullptr);
void addStreamWidget(AbstractOpenStreamWidget *w, const QString &title);
QString dbcFile() const { return dbc_file->text(); }
AbstractStream *stream() const { return stream_; }
private:
AbstractStream *stream_ = nullptr;
QLineEdit *dbc_file;
QTabWidget *tab;
QDialogButtonBox *btn_box;

Loading…
Cancel
Save