cabana: support editing dbc files without a stream (#28294)

* no stream mode

* disable tools menu if no stream

* disable splitter

* cleanup

* refactor matchMessage
pull/28299/head
Dean Lee 2 years ago committed by GitHub
parent 53190e926c
commit d5d1ca11d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      tools/cabana/cabana.cc
  2. 17
      tools/cabana/dbc/dbcmanager.cc
  3. 1
      tools/cabana/dbc/dbcmanager.h
  4. 19
      tools/cabana/mainwin.cc
  5. 4
      tools/cabana/mainwin.h
  6. 116
      tools/cabana/messageswidget.cc
  7. 9
      tools/cabana/streams/abstractstream.h
  8. 6
      tools/cabana/tools/findsimilarbits.cc

@ -63,9 +63,7 @@ int main(int argc, char *argv[]) {
if (route.isEmpty()) { if (route.isEmpty()) {
StreamSelector dlg(&stream); StreamSelector dlg(&stream);
if (!dlg.exec()) { dlg.exec();
return 0;
}
dbc_file = dlg.dbcFile(); dbc_file = dlg.dbcFile();
} else { } else {
auto replay_stream = new ReplayStream(&app); auto replay_stream = new ReplayStream(&app);
@ -77,11 +75,13 @@ int main(int argc, char *argv[]) {
} }
MainWindow w; MainWindow w;
if (!stream) {
stream = new DummyStream(&app);
}
stream->start();
if (!dbc_file.isEmpty()) { if (!dbc_file.isEmpty()) {
w.loadFile(dbc_file); w.loadFile(dbc_file);
} }
assert(stream != nullptr);
stream->start();
w.show(); w.show();
return app.exec(); return app.exec();
} }

@ -1,12 +1,5 @@
#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/dbc/dbcmanager.h"
#include <algorithm>
#include <QFile>
#include <QRegularExpression>
#include <QTextStream>
#include <QVector>
#include <limits>
#include <sstream>
bool DBCManager::open(SourceSet s, const QString &dbc_file_name, QString *error) { bool DBCManager::open(SourceSet s, const QString &dbc_file_name, QString *error) {
for (int i = 0; i < dbc_files.size(); i++) { for (int i = 0; i < dbc_files.size(); i++) {
@ -280,13 +273,7 @@ int DBCManager::dbcCount() const {
} }
int DBCManager::nonEmptyDBCCount() const { int DBCManager::nonEmptyDBCCount() const {
int cnt = 0; return std::count_if(dbc_files.cbegin(), dbc_files.cend(), [](auto &f) { return !f.second->isEmpty(); });
for (auto &[_, dbc_file] : dbc_files) {
if (!dbc_file->isEmpty()) {
cnt++;
}
}
return cnt;
} }
void DBCManager::updateSources(const SourceSet &s) { void DBCManager::updateSources(const SourceSet &s) {

@ -8,7 +8,6 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QSet> #include <QSet>
#include <QDebug>
#include "tools/cabana/dbc/dbc.h" #include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/dbc/dbcfile.h" #include "tools/cabana/dbc/dbcfile.h"

@ -6,7 +6,6 @@
#include <QFile> #include <QFile>
#include <QFileDialog> #include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QMenu>
#include <QMenuBar> #include <QMenuBar>
#include <QMessageBox> #include <QMessageBox>
#include <QResizeEvent> #include <QResizeEvent>
@ -77,6 +76,8 @@ MainWindow::MainWindow() : QMainWindow() {
void MainWindow::createActions() { void MainWindow::createActions() {
QMenu *file_menu = menuBar()->addMenu(tr("&File")); QMenu *file_menu = menuBar()->addMenu(tr("&File"));
file_menu->addAction(tr("Open Stream..."), this, &MainWindow::openStream); 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->addSeparator();
file_menu->addAction(tr("New DBC File"), [this]() { newFile(); })->setShortcuts(QKeySequence::New); 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_act->setDefaultWidget(new QUndoView(UndoStack::instance()));
commands_menu->addAction(commands_act); 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 &Similar Bits"), this, &MainWindow::findSimilarBits);
tools_menu->addAction(tr("&Find Signal"), this, &MainWindow::findSignal); tools_menu->addAction(tr("&Find Signal"), this, &MainWindow::findSignal);
@ -177,6 +178,7 @@ void MainWindow::createDockWidgets() {
video_splitter->addWidget(charts_container); video_splitter->addWidget(charts_container);
video_splitter->setStretchFactor(1, 1); video_splitter->setStretchFactor(1, 1);
video_splitter->restoreState(settings.video_splitter_state); video_splitter->restoreState(settings.video_splitter_state);
video_splitter->handle(1)->setEnabled(!can->liveStreaming());
video_dock->setWidget(video_splitter); video_dock->setWidget(video_splitter);
QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); 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) { void MainWindow::newFile(SourceSet s) {
closeFile(s); closeFile(s);
dbc()->open(s, "", ""); dbc()->open(s, "", "");
@ -318,6 +329,9 @@ void MainWindow::changingStream() {
} }
void MainWindow::streamStarted() { void MainWindow::streamStarted() {
bool has_stream = dynamic_cast<DummyStream *>(can) == nullptr;
close_stream_act->setEnabled(has_stream);
tools_menu->setEnabled(has_stream);
createDockWidgets(); createDockWidgets();
video_dock->setWindowTitle(can->routeName()); video_dock->setWindowTitle(can->routeName());
@ -462,6 +476,7 @@ void MainWindow::updateLoadSaveMenus() {
std::sort(sources_sorted.begin(), sources_sorted.end()); std::sort(sources_sorted.begin(), sources_sorted.end());
manage_dbcs_menu->clear(); manage_dbcs_menu->clear();
manage_dbcs_menu->setEnabled(dynamic_cast<DummyStream *>(can) == nullptr);
for (uint8_t source : sources_sorted) { for (uint8_t source : sources_sorted) {
if (source >= 64) continue; // Sent and blocked buses are handled implicitly if (source >= 64) continue; // Sent and blocked buses are handled implicitly

@ -3,6 +3,7 @@
#include <QDockWidget> #include <QDockWidget>
#include <QJsonDocument> #include <QJsonDocument>
#include <QMainWindow> #include <QMainWindow>
#include <QMenu>
#include <QProgressBar> #include <QProgressBar>
#include <QSplitter> #include <QSplitter>
#include <QStatusBar> #include <QStatusBar>
@ -26,6 +27,7 @@ public:
public slots: public slots:
void openStream(); void openStream();
void closeStream();
void changingStream(); void changingStream();
void streamStarted(); void streamStarted();
@ -88,6 +90,8 @@ protected:
QAction *recent_files_acts[MAX_RECENT_FILES] = {}; QAction *recent_files_acts[MAX_RECENT_FILES] = {};
QMenu *open_recent_menu = nullptr; QMenu *open_recent_menu = nullptr;
QMenu *manage_dbcs_menu = nullptr; QMenu *manage_dbcs_menu = nullptr;
QMenu *tools_menu = nullptr;
QAction *close_stream_act = nullptr;
QAction *save_dbc = nullptr; QAction *save_dbc = nullptr;
QAction *save_dbc_as = nullptr; QAction *save_dbc_as = nullptr;
QAction *copy_dbc_to_clipboard = nullptr; QAction *copy_dbc_to_clipboard = nullptr;

@ -5,6 +5,8 @@
#include <QScrollBar> #include <QScrollBar>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "tools/cabana/commands.h"
MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0 ,0, 0, 0); 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(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified);
QObject::connect(dbc(), &DBCManager::msgUpdated, this, &MessagesWidget::dbcModified); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, 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(model, &MessageListModel::modelReset, [this]() { QObject::connect(model, &MessageListModel::modelReset, [this]() {
if (current_msg_id) { if (current_msg_id) {
selectMessage(*current_msg_id); selectMessage(*current_msg_id);
@ -245,104 +243,55 @@ void MessageListModel::sortMessages(std::vector<MessageId> &new_msgs) {
} }
} }
static std::pair<unsigned int, unsigned int> 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}) // 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 min = std::numeric_limits<unsigned int>::min();
unsigned int parsed1 = std::numeric_limits<unsigned int>::min(); unsigned int max = std::numeric_limits<unsigned int>::max();
unsigned int parsed2 = std::numeric_limits<unsigned int>::max();
auto s = filter.split('-'); auto s = filter.split('-');
if (s.size() == 1) { bool ok = s.size() <= 2;
parsed1 = s[0].toUInt(ok, base); if (ok && !s[0].isEmpty()) min = s[0].toUInt(&ok, base);
return {parsed1, parsed1}; if (ok && s.size() == 2 && !s[1].isEmpty()) max = s[1].toUInt(&ok, base);
} else if (s.size() == 2) { return ok && value >= min && value <= max;
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 MessageListModel::matchMessage(const MessageId &id, const CanData &data, const QMap<int, QString> &filters) { bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, const QMap<int, QString> &filters) {
auto cs = Qt::CaseInsensitive;
bool match = true; bool match = true;
bool convert_ok; for (auto it = filters.cbegin(); it != filters.cend() && match; ++it) {
const QString &txt = it.value();
for (int column = Column::NAME; column <= Column::DATA; column++) {
if (!filters.contains(column)) continue;
const QString &txt = filters[column];
QRegularExpression re(txt, QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption); QRegularExpression re(txt, QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption);
switch (it.key()) {
switch (column) { case Column::NAME: {
case Column::NAME: const auto msg = dbc()->msg(id);
{ match = re.match(msg ? msg->name : UNTITLED).hasMatch();
bool name_match = re.match(msgName(id)).hasMatch(); match |= msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), [&re](const auto &s) { return re.match(s.name).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;
}
break; break;
}
case Column::SOURCE: case Column::SOURCE:
{ match = parseRange(txt, id.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;
}
break; break;
case Column::ADDRESS: case Column::ADDRESS: {
{ match = re.match(QString::number(id.address, 16)).hasMatch();
bool address_re_match = re.match(QString::number(id.address, 16)).hasMatch(); match |= parseRange(txt, id.address, 16);
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;
}
break; break;
}
case Column::FREQ: case Column::FREQ:
{ // TODO: Hide stale messages?
// TODO: Hide stale messages? match = parseRange(txt, data.freq);
auto freq = parseRange(txt, &convert_ok);
bool freq_match = convert_ok && (data.freq >= freq.first && data.freq <= freq.second);
if (!freq_match) match = false;
}
break; break;
case Column::COUNT: case Column::COUNT:
{ match = parseRange(txt, data.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;
}
break; break;
case Column::DATA: case Column::DATA: {
{ match = QString(data.dat.toHex()).contains(txt, Qt::CaseInsensitive);
bool data_match = false; match |= re.match(QString(data.dat.toHex())).hasMatch();
data_match |= QString(data.dat.toHex()).contains(txt, cs); match |= re.match(QString(data.dat.toHex(' '))).hasMatch();
data_match |= re.match(QString(data.dat.toHex())).hasMatch();
data_match |= re.match(QString(data.dat.toHex(' '))).hasMatch();
if (!data_match) match = false;
}
break; break;
}
} }
} }
return match; return match;
} }
void MessageListModel::fetchData() { void MessageListModel::fetchData() {
std::vector<MessageId> new_msgs; std::vector<MessageId> new_msgs;
new_msgs.reserve(can->last_msgs.size() + dbc_address.size()); 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); QMenu *menu = new QMenu(this);
int cur_index = header()->logicalIndexAt(pos); int cur_index = header()->logicalIndexAt(pos);
QString column_name;
QAction *action; QAction *action;
for (int visual_index = 0; visual_index < header()->count(); visual_index++) { for (int visual_index = 0; visual_index < header()->count(); visual_index++) {
int logical_index = header()->logicalIndex(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 // Hide show action
if (header()->isSectionHidden(logical_index)) { if (header()->isSectionHidden(logical_index)) {

@ -100,6 +100,15 @@ protected:
AbstractStream **stream = nullptr; 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 { class StreamNotifier : public QObject {
Q_OBJECT Q_OBJECT
public: public:

@ -20,12 +20,8 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi
QHBoxLayout *src_layout = new QHBoxLayout(); QHBoxLayout *src_layout = new QHBoxLayout();
src_bus_combo = new QComboBox(this); src_bus_combo = new QComboBox(this);
find_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 (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->addItem(QString::number(bus), bus);
} }
cb->model()->sort(0); cb->model()->sort(0);

Loading…
Cancel
Save