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: d5d1ca11d4
beeps
Dean Lee 2 years ago committed by GitHub
parent 14fdc8aca6
commit 6cc979a258
  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()) {
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();
}

@ -1,12 +1,5 @@
#include "tools/cabana/dbc/dbcmanager.h"
#include <QFile>
#include <QRegularExpression>
#include <QTextStream>
#include <QVector>
#include <limits>
#include <sstream>
#include <algorithm>
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) {

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

@ -6,7 +6,6 @@
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QResizeEvent>
@ -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<DummyStream *>(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<DummyStream *>(can) == nullptr);
for (uint8_t source : sources_sorted) {
if (source >= 64) continue; // Sent and blocked buses are handled implicitly

@ -3,6 +3,7 @@
#include <QDockWidget>
#include <QJsonDocument>
#include <QMainWindow>
#include <QMenu>
#include <QProgressBar>
#include <QSplitter>
#include <QStatusBar>
@ -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;

@ -5,6 +5,8 @@
#include <QScrollBar>
#include <QVBoxLayout>
#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<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})
bool ok1 = true, ok2 = true;
unsigned int parsed1 = std::numeric_limits<unsigned int>::min();
unsigned int parsed2 = std::numeric_limits<unsigned int>::max();
unsigned int min = std::numeric_limits<unsigned int>::min();
unsigned int max = std::numeric_limits<unsigned int>::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<int, QString> &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<MessageId> 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)) {

@ -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:

@ -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);

Loading…
Cancel
Save