From 6cc979a25829663df1a2b06917d22b2a3ca42808 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 26 May 2023 01:51:28 +0800 Subject: [PATCH] cabana: support editing dbc files without a stream (#28294) * no stream mode * disable tools menu if no stream * disable splitter * cleanup * refactor matchMessage old-commit-hash: d5d1ca11d4a5ba5ffcb067fd842540d5334e9360 --- tools/cabana/cabana.cc | 10 +-- tools/cabana/dbc/dbcmanager.cc | 17 +--- tools/cabana/dbc/dbcmanager.h | 1 - tools/cabana/mainwin.cc | 19 ++++- tools/cabana/mainwin.h | 4 + tools/cabana/messageswidget.cc | 116 +++++++------------------- tools/cabana/streams/abstractstream.h | 9 ++ tools/cabana/tools/findsimilarbits.cc | 6 +- 8 files changed, 70 insertions(+), 112 deletions(-) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 0215ec3c4e..0a4b9eb112 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -63,9 +63,7 @@ int main(int argc, char *argv[]) { if (route.isEmpty()) { StreamSelector dlg(&stream); - if (!dlg.exec()) { - return 0; - } + dlg.exec(); dbc_file = dlg.dbcFile(); } else { auto replay_stream = new ReplayStream(&app); @@ -77,11 +75,13 @@ int main(int argc, char *argv[]) { } MainWindow w; + if (!stream) { + stream = new DummyStream(&app); + } + stream->start(); if (!dbc_file.isEmpty()) { w.loadFile(dbc_file); } - assert(stream != nullptr); - stream->start(); w.show(); return app.exec(); } diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 9667dd8f2a..000035bd2c 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -1,12 +1,5 @@ #include "tools/cabana/dbc/dbcmanager.h" - -#include -#include -#include -#include -#include -#include - +#include bool DBCManager::open(SourceSet s, const QString &dbc_file_name, QString *error) { for (int i = 0; i < dbc_files.size(); i++) { @@ -280,13 +273,7 @@ int DBCManager::dbcCount() const { } int DBCManager::nonEmptyDBCCount() const { - int cnt = 0; - for (auto &[_, dbc_file] : dbc_files) { - if (!dbc_file->isEmpty()) { - cnt++; - } - } - return cnt; + return std::count_if(dbc_files.cbegin(), dbc_files.cend(), [](auto &f) { return !f.second->isEmpty(); }); } void DBCManager::updateSources(const SourceSet &s) { diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 699c280b4c..0d7055588b 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -8,7 +8,6 @@ #include #include #include -#include #include "tools/cabana/dbc/dbc.h" #include "tools/cabana/dbc/dbcfile.h" diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 1f5a13aebb..d3da4a1500 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -77,6 +76,8 @@ MainWindow::MainWindow() : QMainWindow() { void MainWindow::createActions() { QMenu *file_menu = menuBar()->addMenu(tr("&File")); file_menu->addAction(tr("Open Stream..."), this, &MainWindow::openStream); + close_stream_act = file_menu->addAction(tr("Close stream"), this, &MainWindow::closeStream); + close_stream_act->setEnabled(false); file_menu->addSeparator(); file_menu->addAction(tr("New DBC File"), [this]() { newFile(); })->setShortcuts(QKeySequence::New); @@ -134,7 +135,7 @@ void MainWindow::createActions() { commands_act->setDefaultWidget(new QUndoView(UndoStack::instance())); commands_menu->addAction(commands_act); - QMenu *tools_menu = menuBar()->addMenu(tr("&Tools")); + tools_menu = menuBar()->addMenu(tr("&Tools")); tools_menu->addAction(tr("Find &Similar Bits"), this, &MainWindow::findSimilarBits); tools_menu->addAction(tr("&Find Signal"), this, &MainWindow::findSignal); @@ -177,6 +178,7 @@ void MainWindow::createDockWidgets() { video_splitter->addWidget(charts_container); video_splitter->setStretchFactor(1, 1); video_splitter->restoreState(settings.video_splitter_state); + video_splitter->handle(1)->setEnabled(!can->liveStreaming()); video_dock->setWidget(video_splitter); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); } @@ -246,6 +248,15 @@ void MainWindow::openStream() { } } +void MainWindow::closeStream() { + AbstractStream *stream = new DummyStream(this); + stream->start(); + if (dbc()->nonEmptyDBCCount() > 0) { + emit dbc()->DBCFileChanged(); + } + statusBar()->showMessage(tr("stream closed")); +} + void MainWindow::newFile(SourceSet s) { closeFile(s); dbc()->open(s, "", ""); @@ -318,6 +329,9 @@ void MainWindow::changingStream() { } void MainWindow::streamStarted() { + bool has_stream = dynamic_cast(can) == nullptr; + close_stream_act->setEnabled(has_stream); + tools_menu->setEnabled(has_stream); createDockWidgets(); video_dock->setWindowTitle(can->routeName()); @@ -462,6 +476,7 @@ void MainWindow::updateLoadSaveMenus() { std::sort(sources_sorted.begin(), sources_sorted.end()); manage_dbcs_menu->clear(); + manage_dbcs_menu->setEnabled(dynamic_cast(can) == nullptr); for (uint8_t source : sources_sorted) { if (source >= 64) continue; // Sent and blocked buses are handled implicitly diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index e227ce593f..f56d37a252 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ public: public slots: void openStream(); + void closeStream(); void changingStream(); void streamStarted(); @@ -88,6 +90,8 @@ protected: QAction *recent_files_acts[MAX_RECENT_FILES] = {}; QMenu *open_recent_menu = nullptr; QMenu *manage_dbcs_menu = nullptr; + QMenu *tools_menu = nullptr; + QAction *close_stream_act = nullptr; QAction *save_dbc = nullptr; QAction *save_dbc_as = nullptr; QAction *copy_dbc_to_clipboard = nullptr; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 0a44cb931c..1d8499fa41 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -5,6 +5,8 @@ #include #include +#include "tools/cabana/commands.h" + MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0 ,0, 0, 0); @@ -82,11 +84,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { }); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified); - QObject::connect(dbc(), &DBCManager::msgUpdated, this, &MessagesWidget::dbcModified); - QObject::connect(dbc(), &DBCManager::msgRemoved, this, &MessagesWidget::dbcModified); - QObject::connect(dbc(), &DBCManager::signalAdded, this, &MessagesWidget::dbcModified); - QObject::connect(dbc(), &DBCManager::signalRemoved, this, &MessagesWidget::dbcModified); - QObject::connect(dbc(), &DBCManager::signalUpdated, this, &MessagesWidget::dbcModified); + QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MessagesWidget::dbcModified); QObject::connect(model, &MessageListModel::modelReset, [this]() { if (current_msg_id) { selectMessage(*current_msg_id); @@ -245,104 +243,55 @@ void MessageListModel::sortMessages(std::vector &new_msgs) { } } -static std::pair parseRange(const QString &filter, bool *ok = nullptr, int base = 10) { +static bool parseRange(const QString &filter, uint32_t value, int base = 10) { // Parse out filter string into a range (e.g. "1" -> {1, 1}, "1-3" -> {1, 3}, "1-" -> {1, inf}) - bool ok1 = true, ok2 = true; - unsigned int parsed1 = std::numeric_limits::min(); - unsigned int parsed2 = std::numeric_limits::max(); - + unsigned int min = std::numeric_limits::min(); + unsigned int max = std::numeric_limits::max(); auto s = filter.split('-'); - if (s.size() == 1) { - parsed1 = s[0].toUInt(ok, base); - return {parsed1, parsed1}; - } else if (s.size() == 2) { - if (!s[0].isEmpty()) parsed1 = s[0].toUInt(&ok1, base); - if (!s[1].isEmpty()) parsed2 = s[1].toUInt(&ok2, base); - - *ok = ok1 & ok2; - return {parsed1, parsed2}; - } else { - *ok = false; - return {0, 0}; - } + bool ok = s.size() <= 2; + if (ok && !s[0].isEmpty()) min = s[0].toUInt(&ok, base); + if (ok && s.size() == 2 && !s[1].isEmpty()) max = s[1].toUInt(&ok, base); + return ok && value >= min && value <= max; } bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, const QMap &filters) { - auto cs = Qt::CaseInsensitive; bool match = true; - bool convert_ok; - - for (int column = Column::NAME; column <= Column::DATA; column++) { - if (!filters.contains(column)) continue; - const QString &txt = filters[column]; - + for (auto it = filters.cbegin(); it != filters.cend() && match; ++it) { + const QString &txt = it.value(); QRegularExpression re(txt, QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption); - - switch (column) { - case Column::NAME: - { - bool name_match = re.match(msgName(id)).hasMatch(); - - // Message signals - if (const auto msg = dbc()->msg(id)) { - for (auto s : msg->getSignals()) { - if (re.match(s->name).hasMatch()) { - name_match = true; - break; - } - } - } - if (!name_match) match = false; - } + switch (it.key()) { + case Column::NAME: { + const auto msg = dbc()->msg(id); + match = re.match(msg ? msg->name : UNTITLED).hasMatch(); + match |= msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), [&re](const auto &s) { return re.match(s.name).hasMatch(); }); break; + } case Column::SOURCE: - { - auto source = parseRange(txt, &convert_ok); - bool source_match = convert_ok && (id.source >= source.first && id.source <= source.second); - if (!source_match) match = false; - } + match = parseRange(txt, id.source); break; - case Column::ADDRESS: - { - bool address_re_match = re.match(QString::number(id.address, 16)).hasMatch(); - - auto address = parseRange(txt, &convert_ok, 16); - bool address_match = convert_ok && (id.address >= address.first && id.address <= address.second); - - if (!address_re_match && !address_match) match = false; - } + case Column::ADDRESS: { + match = re.match(QString::number(id.address, 16)).hasMatch(); + match |= parseRange(txt, id.address, 16); break; + } case Column::FREQ: - { - // TODO: Hide stale messages? - auto freq = parseRange(txt, &convert_ok); - bool freq_match = convert_ok && (data.freq >= freq.first && data.freq <= freq.second); - if (!freq_match) match = false; - } + // TODO: Hide stale messages? + match = parseRange(txt, data.freq); break; case Column::COUNT: - { - auto count = parseRange(txt, &convert_ok); - bool count_match = convert_ok && (data.count >= count.first && data.count <= count.second); - if (!count_match) match = false; - } + match = parseRange(txt, data.count); break; - case Column::DATA: - { - bool data_match = false; - data_match |= QString(data.dat.toHex()).contains(txt, cs); - data_match |= re.match(QString(data.dat.toHex())).hasMatch(); - data_match |= re.match(QString(data.dat.toHex(' '))).hasMatch(); - - if (!data_match) match = false; - } + case Column::DATA: { + match = QString(data.dat.toHex()).contains(txt, Qt::CaseInsensitive); + match |= re.match(QString(data.dat.toHex())).hasMatch(); + match |= re.match(QString(data.dat.toHex(' '))).hasMatch(); break; + } } } return match; } - void MessageListModel::fetchData() { std::vector new_msgs; new_msgs.reserve(can->last_msgs.size() + dbc_address.size()); @@ -459,11 +408,10 @@ void MessageView::headerContextMenuEvent(const QPoint &pos) { QMenu *menu = new QMenu(this); int cur_index = header()->logicalIndexAt(pos); - QString column_name; QAction *action; for (int visual_index = 0; visual_index < header()->count(); visual_index++) { int logical_index = header()->logicalIndex(visual_index); - column_name = model()->headerData(logical_index, Qt::Horizontal).toString(); + QString column_name = model()->headerData(logical_index, Qt::Horizontal).toString(); // Hide show action if (header()->isSectionHidden(logical_index)) { diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 57e6c3cf1a..cdb939b948 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -100,6 +100,15 @@ protected: AbstractStream **stream = nullptr; }; +class DummyStream : public AbstractStream { + Q_OBJECT +public: + DummyStream(QObject *parent) : AbstractStream(parent) {} + QString routeName() const override { return tr("No Stream"); } + void start() override { emit streamStarted(); } + double currentSec() const override { return 0; } +}; + class StreamNotifier : public QObject { Q_OBJECT public: diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index 9d81ebd5d0..add4ee7928 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -20,12 +20,8 @@ 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); - SourceSet bus_set; - for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) { - bus_set << it.key().source; - } for (auto cb : {src_bus_combo, find_bus_combo}) { - for (uint8_t bus : bus_set) { + for (uint8_t bus : can->sources) { cb->addItem(QString::number(bus), bus); } cb->model()->sort(0);