cabana: replace custom non-view Qt signals w/ plain observer (#38713)

This commit is contained in:
Trey Moen
2026-08-27 18:54:06 -07:00
committed by GitHub
parent 318257fa3b
commit cbf750de20
48 changed files with 403 additions and 339 deletions
+1 -1
View File
@@ -99,7 +99,7 @@ cabana_env.Command(assets, "assets/assets.qrc", f"rcc $SOURCES -o $TARGET")
cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, "assets/assets.o"]))
cabana_srcs = ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'dbc/dbcqt.cc',
'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc',
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc',
+2 -3
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/binaryview.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
@@ -36,8 +35,8 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
setMouseTracking(true);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &BinaryView::refresh);
QObject::connect(undoNotifier(), &QtUndoNotifier::indexChanged, this, &BinaryView::refresh);
connections_.push_back(dbc()->fileChanged.connect([this]() { refresh(); }));
connections_.push_back(UndoStack::instance()->indexChanged.connect([this]() { refresh(); }));
addShortcuts();
setWhatsThis(R"(
+1
View File
@@ -100,5 +100,6 @@ private:
bool is_message_active = false;
const cabana::Signal *resize_sig = nullptr;
const cabana::Signal *hovered_sig = nullptr;
Connections connections_;
friend class BinaryItemDelegate;
};
+5 -5
View File
@@ -146,19 +146,19 @@ int main(int argc, char *argv[]) {
AbstractStream *stream = nullptr;
if (args.msgq) {
stream = new DeviceStream(&app);
stream = new DeviceStream();
} else if (!args.zmq.empty()) {
stream = new DeviceStream(&app, QString::fromStdString(args.zmq));
stream = new DeviceStream(QString::fromStdString(args.zmq));
} else if (args.panda || !args.panda_serial.empty()) {
try {
stream = new PandaStream(&app, {.serial = args.panda_serial});
stream = new PandaStream({.serial = args.panda_serial});
} catch (std::exception &e) {
fprintf(stderr, "%s\n", e.what());
return 0;
}
#ifdef __linux__
} else if (SocketCanStream::available() && !args.socketcan.empty()) {
stream = new SocketCanStream(&app, {.device = args.socketcan});
stream = new SocketCanStream({.device = args.socketcan});
#endif
} else {
uint32_t replay_flags = REPLAY_FLAG_NONE;
@@ -174,7 +174,7 @@ int main(int argc, char *argv[]) {
route = DEMO_ROUTE;
}
if (!route.isEmpty()) {
auto replay_stream = std::make_unique<ReplayStream>(&app);
auto replay_stream = std::make_unique<ReplayStream>();
if (!replay_stream->loadRoute(route.toStdString(), args.data_dir, replay_flags, args.auto_source)) {
return 0;
}
+9 -13
View File
@@ -9,13 +9,11 @@
#include <QPainter>
#include "common/yuv.h"
#include "tools/cabana/utils/util.h"
CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, QWidget* parent) :
stream_name(stream_name), active_stream_type(type), requested_stream_type(type), QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
qRegisterMetaType<std::set<VisionStreamType>>("availableStreams");
QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection);
QObject::connect(this, &CameraWidget::vipcAvailableStreamsUpdated, this, &CameraWidget::availableStreamsUpdated, Qt::QueuedConnection);
QObject::connect(QApplication::instance(), &QCoreApplication::aboutToQuit, this, &CameraWidget::stopVipcThread);
}
@@ -38,10 +36,6 @@ void CameraWidget::stopVipcThread() {
}
}
void CameraWidget::availableStreamsUpdated(std::set<VisionStreamType> streams) {
available_streams = streams;
}
void CameraWidget::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.fillRect(rect(), bg);
@@ -67,10 +61,6 @@ void CameraWidget::paintEvent(QPaintEvent *event) {
p.drawImage(video_rect, rgb_frame);
}
void CameraWidget::vipcFrameReceived() {
update();
}
void CameraWidget::vipcThread() {
VisionStreamType cur_stream = requested_stream_type;
std::unique_ptr<VisionIpcClient> vipc_client;
@@ -93,7 +83,11 @@ void CameraWidget::vipcThread() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
emit vipcAvailableStreamsUpdated(streams);
utils::runOnMainThread([this, alive = std::weak_ptr<bool>(alive_), streams]() {
if (alive.expired()) return;
available_streams = streams;
availableStreamsUpdated(streams);
});
if (!vipc_client->connect(false)) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -112,7 +106,9 @@ void CameraWidget::vipcThread() {
std::lock_guard lk(frame_lock);
rgb_frame.swap(rgb_back);
}
emit vipcThreadFrameReceived();
utils::runOnMainThread([this, alive = std::weak_ptr<bool>(alive_)]() {
if (!alive.expired()) update();
});
}
}
}
+6 -13
View File
@@ -1,6 +1,7 @@
#pragma once
#include <atomic>
#include <memory>
#include <mutex>
#include <set>
#include <string>
@@ -11,11 +12,10 @@
#include <QWidget>
#include "openpilot/cereal/visionstream.h"
#include "tools/cabana/core/observable.h"
#include "msgq/visionipc/visionipc_client.h"
class CameraWidget : public QWidget {
Q_OBJECT
public:
explicit CameraWidget(std::string stream_name, VisionStreamType stream_type, QWidget* parent = nullptr);
~CameraWidget();
@@ -23,16 +23,14 @@ public:
VisionStreamType getStreamType() { return active_stream_type; }
void stopVipcThread();
signals:
void clicked();
void vipcThreadFrameReceived();
void vipcAvailableStreamsUpdated(std::set<VisionStreamType>);
Observable<> clicked;
Observable<std::set<VisionStreamType>> availableStreamsUpdated; // invoked on the main thread
protected:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override { stopVipcThread(); }
void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); }
void mouseReleaseEvent(QMouseEvent *event) override { clicked(); }
void vipcThread();
void clearFrames();
@@ -47,10 +45,5 @@ protected:
std::thread vipc_thread;
std::atomic<bool> vipc_exit = false;
std::mutex frame_lock;
protected slots:
void vipcFrameReceived();
void availableStreamsUpdated(std::set<VisionStreamType> streams);
std::shared_ptr<bool> alive_ = std::make_shared<bool>(true);
};
Q_DECLARE_METATYPE(std::set<VisionStreamType>);
+4 -5
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/chart/chart.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <limits>
@@ -37,10 +36,10 @@ ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *par
createToolButtons();
signal_value_font.setPointSize(9);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalRemoved, this, &ChartView::signalRemoved);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalUpdated, this, &ChartView::signalUpdated);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgRemoved, this, &ChartView::msgRemoved);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgUpdated, this, &ChartView::msgUpdated);
connections_.push_back(dbc()->signalRemoved.connect([this](const cabana::Signal *sig) { signalRemoved(sig); }));
connections_.push_back(dbc()->signalUpdated.connect([this](const cabana::Signal *sig) { signalUpdated(sig); }));
connections_.push_back(dbc()->msgRemoved.connect([this](MessageId id) { msgRemoved(id); }));
connections_.push_back(dbc()->msgUpdated.connect([this](MessageId id) { msgUpdated(id); }));
}
void ChartView::createToolButtons() {
+1
View File
@@ -126,5 +126,6 @@ private:
double tooltip_x = -1;
QFont signal_value_font;
ChartsWidget *charts_widget;
Connections connections_;
friend class ChartsWidget;
};
+8 -9
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/chart/chartswidget.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <future>
@@ -76,10 +75,10 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) {
redo_zoom_action = toolbar->addAction(utils::icon("arrow-clockwise"), tr("Redo Zoom"), [this]() { zoom_undo_stack.redo(); });
undo_zoom_action->setEnabled(false);
redo_zoom_action->setEnabled(false);
zoom_undo_stack.setCallbacks({.index_changed = [this]() {
connections_.push_back(zoom_undo_stack.indexChanged.connect([this]() {
undo_zoom_action->setEnabled(zoom_undo_stack.canUndo());
redo_zoom_action->setEnabled(zoom_undo_stack.canRedo());
}});
}));
reset_zoom_action = toolbar->addWidget(reset_zoom_btn = new ToolButton("zoom-out", tr("Reset Zoom")));
reset_zoom_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
@@ -122,16 +121,16 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) {
align_timer->setSingleShot(true);
QObject::connect(align_timer, &QTimer::timeout, this, &ChartsWidget::alignCharts);
QObject::connect(auto_scroll_timer, &QTimer::timeout, this, &ChartsWidget::doAutoScroll);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &ChartsWidget::removeAll);
QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged);
QObject::connect(can, &AbstractStream::msgsReceived, this, &ChartsWidget::updateState);
QObject::connect(can, &AbstractStream::seeking, this, &ChartsWidget::updateState);
QObject::connect(can, &AbstractStream::timeRangeChanged, this, &ChartsWidget::timeRangeChanged);
connections_.push_back(dbc()->fileChanged.connect([this]() { removeAll(); }));
connections_.push_back(can->eventsMerged.connect([this](const MessageEventsMap &events) { eventsMerged(events); }));
connections_.push_back(can->msgsReceived.connect([this](const std::set<MessageId> *, bool) { updateState(); }));
connections_.push_back(can->seeking.connect([this](double) { updateState(); }));
connections_.push_back(can->timeRangeChanged.connect([this](const auto &range) { timeRangeChanged(range); }));
QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange);
QObject::connect(new_plot_btn, &QToolButton::clicked, this, &ChartsWidget::newChart);
QObject::connect(remove_all_btn, &QToolButton::clicked, this, &ChartsWidget::removeAll);
QObject::connect(reset_zoom_btn, &QToolButton::clicked, this, &ChartsWidget::zoomReset);
QObject::connect(&settings, &Settings::changed, this, &ChartsWidget::settingChanged);
connections_.push_back(settings.changed.connect([this]() { settingChanged(); }));
QObject::connect(new_tab_btn, &QToolButton::clicked, this, &ChartsWidget::newTab);
QObject::connect(this, &ChartsWidget::seriesChanged, this, &ChartsWidget::updateTabBar);
QObject::connect(tabbar, &QTabBar::tabCloseRequested, this, &ChartsWidget::removeTab);
@@ -123,6 +123,7 @@ private:
QTimer *align_timer;
int current_theme = 0;
bool value_tip_visible_ = false;
Connections connections_;
friend class ChartView;
friend class ChartsContainer;
};
@@ -1,5 +1,4 @@
#include "tools/cabana/chart/signalselector.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <QDialogButtonBox>
#include <QGridLayout>
+5 -17
View File
@@ -28,22 +28,22 @@ void UndoStack::clear() {
bool was_clean = isClean();
commands_.clear();
index_ = clean_index_ = 0;
if (callbacks_.index_changed) callbacks_.index_changed();
if (!was_clean && callbacks_.clean_changed) callbacks_.clean_changed(true);
indexChanged();
if (!was_clean) cleanChanged(true);
}
void UndoStack::setClean() {
if (!isClean()) {
clean_index_ = index_;
if (callbacks_.clean_changed) callbacks_.clean_changed(true);
cleanChanged(true);
}
}
void UndoStack::setIndex(int index) {
bool was_clean = isClean();
index_ = index;
if (callbacks_.index_changed) callbacks_.index_changed();
if (isClean() != was_clean && callbacks_.clean_changed) callbacks_.clean_changed(isClean());
indexChanged();
if (isClean() != was_clean) cleanChanged(isClean());
}
UndoStack *UndoStack::instance() {
@@ -51,18 +51,6 @@ UndoStack *UndoStack::instance() {
return &undo_stack;
}
QtUndoNotifier::QtUndoNotifier(QObject *parent) : QObject(parent) {
UndoStack::instance()->setCallbacks({
.index_changed = [this]() { emit indexChanged(); },
.clean_changed = [this](bool clean) { emit cleanChanged(clean); },
});
}
QtUndoNotifier *undoNotifier() {
static QtUndoNotifier notifier;
return &notifier;
}
// EditMsgCommand
EditMsgCommand::EditMsgCommand(const MessageId &id, const std::string &name, int size,
+4 -24
View File
@@ -1,13 +1,11 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <QObject>
#include "tools/cabana/core/observable.h"
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
@@ -21,11 +19,6 @@ public:
class UndoStack {
public:
struct Callbacks {
std::function<void()> index_changed;
std::function<void(bool)> clean_changed;
};
void push(UndoCommand *cmd); // takes ownership and calls redo()
void undo();
void redo();
@@ -36,31 +29,18 @@ public:
bool canRedo() const { return index_ < (int)commands_.size(); }
std::string undoText() const { return canUndo() ? commands_[index_ - 1]->text : ""; }
std::string redoText() const { return canRedo() ? commands_[index_]->text : ""; }
void setCallbacks(Callbacks callbacks) { callbacks_ = std::move(callbacks); }
static UndoStack *instance();
Observable<> indexChanged;
Observable<bool> cleanChanged;
private:
void setIndex(int index);
std::vector<std::unique_ptr<UndoCommand>> commands_;
int index_ = 0;
int clean_index_ = 0;
Callbacks callbacks_;
};
// emits Qt signals for the global undo stack
class QtUndoNotifier : public QObject {
Q_OBJECT
public:
explicit QtUndoNotifier(QObject *parent = nullptr);
signals:
void indexChanged();
void cleanChanged(bool clean);
};
QtUndoNotifier *undoNotifier();
class EditMsgCommand : public UndoCommand {
public:
EditMsgCommand(const MessageId &id, const std::string &name, int size, const std::string &node,
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <functional>
#include <map>
#include <memory>
#include <utility>
#include <vector>
namespace observable_detail {
struct HandlerTable {
virtual ~HandlerTable() = default;
virtual void erase(int id) = 0;
};
} // namespace observable_detail
// disconnects on destruction; safe to outlive the Observable
class Connection {
public:
Connection() = default;
Connection(std::weak_ptr<observable_detail::HandlerTable> table, int id) : table_(std::move(table)), id_(id) {}
Connection(Connection &&other) noexcept { *this = std::move(other); }
Connection &operator=(Connection &&other) noexcept {
if (this != &other) {
disconnect();
table_ = std::move(other.table_);
id_ = std::exchange(other.id_, -1);
}
return *this;
}
Connection(const Connection &) = delete;
Connection &operator=(const Connection &) = delete;
~Connection() { disconnect(); }
void disconnect() {
if (auto table = table_.lock()) table->erase(id_);
table_.reset();
id_ = -1;
}
private:
std::weak_ptr<observable_detail::HandlerTable> table_;
int id_ = -1;
};
using Connections = std::vector<Connection>;
// main thread only. handlers may disconnect (or destroy the Observable) while being invoked.
template <typename... Args>
class Observable {
public:
using Handler = std::function<void(Args...)>;
Observable() = default;
Observable(const Observable &) = delete;
Observable &operator=(const Observable &) = delete;
[[nodiscard]] Connection connect(Handler handler) {
int id = table_->next_id++;
table_->handlers.emplace(id, std::make_shared<Handler>(std::move(handler)));
return Connection(table_, id);
}
void operator()(Args... args) const {
auto table = table_;
std::vector<int> ids;
ids.reserve(table->handlers.size());
for (const auto &[id, _] : table->handlers) ids.push_back(id);
for (int id : ids) {
auto it = table->handlers.find(id);
if (it == table->handlers.end()) continue;
auto handler = it->second;
(*handler)(args...);
}
}
private:
struct Table : observable_detail::HandlerTable {
std::map<int, std::shared_ptr<Handler>> handlers;
int next_id = 0;
void erase(int id) override { handlers.erase(id); }
};
std::shared_ptr<Table> table_ = std::make_shared<Table>();
};
+14 -14
View File
@@ -17,7 +17,7 @@ bool DBCManager::open(const SourceSet &sources, const std::string &dbc_file_name
return false;
}
if (callbacks_.file_changed) callbacks_.file_changed();
fileChanged();
return true;
}
@@ -32,7 +32,7 @@ bool DBCManager::open(const SourceSet &sources, const std::string &name, const s
return false;
}
if (callbacks_.file_changed) callbacks_.file_changed();
fileChanged();
return true;
}
@@ -40,26 +40,26 @@ void DBCManager::close(const SourceSet &sources) {
for (auto s : sources) {
dbc_files[s] = nullptr;
}
if (callbacks_.file_changed) callbacks_.file_changed();
fileChanged();
}
void DBCManager::close(DBCFile *dbc_file) {
for (auto &[_, f] : dbc_files) {
if (f.get() == dbc_file) f = nullptr;
}
if (callbacks_.file_changed) callbacks_.file_changed();
fileChanged();
}
void DBCManager::closeAll() {
dbc_files.clear();
if (callbacks_.file_changed) callbacks_.file_changed();
fileChanged();
}
void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) {
if (auto m = msg(id)) {
if (auto s = m->addSignal(sig)) {
if (callbacks_.signal_added) callbacks_.signal_added(id, s);
if (callbacks_.mask_updated) callbacks_.mask_updated();
signalAdded(id, s);
maskUpdated();
}
}
}
@@ -67,8 +67,8 @@ void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) {
void DBCManager::updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig) {
if (auto m = msg(id)) {
if (auto s = m->updateSignal(sig_name, sig)) {
if (callbacks_.signal_updated) callbacks_.signal_updated(s);
if (callbacks_.mask_updated) callbacks_.mask_updated();
signalUpdated(s);
maskUpdated();
}
}
}
@@ -76,9 +76,9 @@ void DBCManager::updateSignal(const MessageId &id, const std::string &sig_name,
void DBCManager::removeSignal(const MessageId &id, const std::string &sig_name) {
if (auto m = msg(id)) {
if (auto s = m->sig(sig_name)) {
if (callbacks_.signal_removed) callbacks_.signal_removed(s);
signalRemoved(s);
m->removeSignal(sig_name);
if (callbacks_.mask_updated) callbacks_.mask_updated();
maskUpdated();
}
}
}
@@ -87,15 +87,15 @@ void DBCManager::updateMsg(const MessageId &id, const std::string &name, uint32_
auto dbc_file = findDBCFile(id);
assert(dbc_file); // This should be impossible
dbc_file->updateMsg(id, name, size, node, comment);
if (callbacks_.msg_updated) callbacks_.msg_updated(id);
msgUpdated(id);
}
void DBCManager::removeMsg(const MessageId &id) {
auto dbc_file = findDBCFile(id);
assert(dbc_file); // This should be impossible
dbc_file->removeMsg(id);
if (callbacks_.msg_removed) callbacks_.msg_removed(id);
if (callbacks_.mask_updated) callbacks_.mask_updated();
msgRemoved(id);
maskUpdated();
}
std::string DBCManager::newMsgName(const MessageId &id) {
+9 -13
View File
@@ -1,12 +1,12 @@
#pragma once
#include <functional>
#include <memory>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "tools/cabana/core/observable.h"
#include "tools/cabana/dbc/dbcfile.h"
typedef std::set<int> SourceSet;
@@ -15,16 +15,6 @@ inline bool operator<(const std::shared_ptr<DBCFile> &l, const std::shared_ptr<D
class DBCManager {
public:
struct Callbacks {
std::function<void(MessageId, const cabana::Signal *)> signal_added;
std::function<void(const cabana::Signal *)> signal_removed;
std::function<void(const cabana::Signal *)> signal_updated;
std::function<void(MessageId)> msg_updated;
std::function<void(MessageId)> msg_removed;
std::function<void()> file_changed;
std::function<void()> mask_updated;
};
DBCManager() = default;
bool open(const SourceSet &sources, const std::string &dbc_file_name, std::string *error = nullptr);
bool open(const SourceSet &sources, const std::string &name, const std::string &content, std::string *error = nullptr);
@@ -54,11 +44,17 @@ public:
DBCFile *findDBCFile(const uint8_t source);
inline DBCFile *findDBCFile(const MessageId &id) { return findDBCFile(id.source); }
std::set<DBCFile *> allDBCFiles();
void setCallbacks(Callbacks callbacks) { callbacks_ = std::move(callbacks); }
Observable<MessageId, const cabana::Signal *> signalAdded;
Observable<const cabana::Signal *> signalRemoved;
Observable<const cabana::Signal *> signalUpdated;
Observable<MessageId> msgUpdated;
Observable<MessageId> msgRemoved;
Observable<> fileChanged;
Observable<> maskUpdated;
private:
std::map<int, std::shared_ptr<DBCFile>> dbc_files;
Callbacks callbacks_;
};
DBCManager *dbc();
-18
View File
@@ -1,18 +0,0 @@
#include "tools/cabana/dbc/dbcqt.h"
QtDBCNotifier::QtDBCNotifier(QObject *parent) : QObject(parent) {
dbc()->setCallbacks({
.signal_added = [this](MessageId id, const cabana::Signal *sig) { emit signalAdded(id, sig); },
.signal_removed = [this](const cabana::Signal *sig) { emit signalRemoved(sig); },
.signal_updated = [this](const cabana::Signal *sig) { emit signalUpdated(sig); },
.msg_updated = [this](MessageId id) { emit msgUpdated(id); },
.msg_removed = [this](MessageId id) { emit msgRemoved(id); },
.file_changed = [this]() { emit DBCFileChanged(); },
.mask_updated = [this]() { emit maskUpdated(); },
});
}
QtDBCNotifier *dbcNotifier() {
static QtDBCNotifier notifier;
return &notifier;
}
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include <QMetaType>
#include <QObject>
#include "tools/cabana/dbc/dbcmanager.h"
Q_DECLARE_METATYPE(MessageId)
Q_DECLARE_METATYPE(ValueDescription)
class QtDBCNotifier : public QObject {
Q_OBJECT
public:
explicit QtDBCNotifier(QObject *parent = nullptr);
signals:
void signalAdded(MessageId id, const cabana::Signal *sig);
void signalRemoved(const cabana::Signal *sig);
void signalUpdated(const cabana::Signal *sig);
void msgUpdated(MessageId id);
void msgRemoved(MessageId id);
void DBCFileChanged();
void maskUpdated();
};
QtDBCNotifier *dbcNotifier();
+5 -6
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/detailwidget.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <QFormLayout>
#include <QMenu>
@@ -56,9 +55,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
QObject::connect(signal_view, &SignalView::showChart, charts, &ChartsWidget::showChart);
QObject::connect(signal_view, &SignalView::highlight, binary_view, &BinaryView::highlight);
QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); });
QObject::connect(can, &AbstractStream::msgsReceived, this, &DetailWidget::updateState);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &DetailWidget::refresh);
QObject::connect(undoNotifier(), &QtUndoNotifier::indexChanged, this, &DetailWidget::refresh);
connections_.push_back(can->msgsReceived.connect([this](const std::set<MessageId> *msgs, bool) { updateState(msgs); }));
connections_.push_back(dbc()->fileChanged.connect([this]() { refresh(); }));
connections_.push_back(UndoStack::instance()->indexChanged.connect([this]() { refresh(); }));
QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu);
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) {
if (index != -1) {
@@ -97,11 +96,11 @@ void DetailWidget::createToolBar() {
layout()->addWidget(toolbar);
connect(heatmap_live, &QAbstractButton::toggled, this, [this](bool on) { binary_view->setHeatmapLiveMode(on); });
connect(can, &AbstractStream::timeRangeChanged, this, [=](const std::optional<std::pair<double, double>> &range) {
connections_.push_back(can->timeRangeChanged.connect([=](const std::optional<std::pair<double, double>> &range) {
auto text = range ? QString("%1 - %2").arg(range->first, 0, 'f', 3).arg(range->second, 0, 'f', 3) : "All";
heatmap_all->setText(text);
(range ? heatmap_all : heatmap_live)->setChecked(true);
});
}));
}
void DetailWidget::showTabBarContextMenu(const QPoint &pt) {
+1
View File
@@ -57,6 +57,7 @@ private:
SignalView *signal_view;
ChartsWidget *charts;
QSplitter *splitter;
Connections connections_;
};
class CenterWidget : public QWidget {
+6 -4
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/historylog.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <functional>
@@ -10,6 +9,12 @@
#include "tools/cabana/commands.h"
#include "tools/cabana/utils/export.h"
HistoryLogModel::HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {
connections_.push_back(can->seekedTo.connect([this](double) { reset(); }));
connections_.push_back(dbc()->fileChanged.connect([this]() { reset(); }));
connections_.push_back(UndoStack::instance()->indexChanged.connect([this]() { reset(); }));
}
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
const auto &m = messages[index.row()];
const int col = index.column();
@@ -207,9 +212,6 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) {
QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(filterChanged()));
QObject::connect(value_edit, &QLineEdit::textEdited, this, &LogsWidget::filterChanged);
QObject::connect(export_btn, &QToolButton::clicked, this, &LogsWidget::exportToCSV);
QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::reset);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, model, &HistoryLogModel::reset);
QObject::connect(undoNotifier(), &QtUndoNotifier::indexChanged, model, &HistoryLogModel::reset);
QObject::connect(model, &HistoryLogModel::modelReset, this, &LogsWidget::modelReset);
QObject::connect(model, &HistoryLogModel::rowsInserted, [this]() { export_btn->setEnabled(true); });
}
+2 -1
View File
@@ -22,7 +22,7 @@ class HistoryLogModel : public QAbstractTableModel {
Q_OBJECT
public:
HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {}
HistoryLogModel(QObject *parent);
void setMessage(const MessageId &message_id);
void updateState(bool clear = false);
void setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp);
@@ -54,6 +54,7 @@ public:
std::deque<Message> messages;
std::vector<cabana::Signal *> sigs;
bool hex_mode = false;
Connections connections_;
};
class LogsWidget : public QFrame {
+38 -31
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/mainwin.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <filesystem>
@@ -41,15 +40,13 @@ MainWindow::MainWindow(AbstractStream *stream, const QString &dbc_file) : QMainW
restoreGeometry(utils::qbytes(settings.geometry));
restoreState(utils::qbytes(settings.window_state));
// install handlers
// download handlers are called from download threads
static auto static_main_win = this;
qRegisterMetaType<uint64_t>("uint64_t");
qRegisterMetaType<SourceSet>("SourceSet");
installDownloadProgressHandler([](uint64_t cur, uint64_t total, bool success) {
emit static_main_win->updateProgressBar(cur, total, success);
utils::runOnMainThread([=]() { static_main_win->updateDownloadProgress(cur, total, success); });
});
installMessageHandler([](ReplyMsgType type, const std::string msg) {
emit static_main_win->showMessage(QString::fromStdString(msg), 2000);
utils::runOnMainThread([=]() { static_main_win->statusBar()->showMessage(QString::fromStdString(msg), 2000); });
});
setStyleSheet(QString(R"(QMainWindow::separator {
@@ -57,11 +54,14 @@ MainWindow::MainWindow(AbstractStream *stream, const QString &dbc_file) : QMainW
height: %1px; /* when horizontal */
})").arg(style()->pixelMetric(QStyle::PM_SplitterWidth)));
QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage);
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &MainWindow::DBCFileChanged);
QObject::connect(undoNotifier(), &QtUndoNotifier::cleanChanged, this, &MainWindow::undoStackCleanChanged);
QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus);
connections_.push_back(dbc()->fileChanged.connect([this]() { DBCFileChanged(); }));
connections_.push_back(UndoStack::instance()->cleanChanged.connect([this](bool clean) { undoStackCleanChanged(clean); }));
connections_.push_back(settings.changed.connect([this]() { updateStatus(); }));
// temporary pump for the non-Qt main thread queue until imgui owns the loop
auto *queue_timer = new QTimer(this);
QObject::connect(queue_timer, &QTimer::timeout, utils::drainMainThreadQueue);
queue_timer->start(10);
QTimer::singleShot(0, this, [=]() { stream ? openStream(stream, dbc_file) : selectAndOpenStream(); });
show();
@@ -136,7 +136,7 @@ void MainWindow::createActions() {
undo_act->setShortcuts(QKeySequence::Undo);
redo_act = edit_menu->addAction(tr("&Redo"), []() { UndoStack::instance()->redo(); });
redo_act->setShortcuts(QKeySequence::Redo);
QObject::connect(undoNotifier(), &QtUndoNotifier::indexChanged, this, &MainWindow::updateUndoRedoActions);
connections_.push_back(UndoStack::instance()->indexChanged.connect([this]() { updateUndoRedoActions(); }));
updateUndoRedoActions();
// View Menu
@@ -259,14 +259,14 @@ void MainWindow::selectAndOpenStream() {
if (dlg.exec()) {
openStream(dlg.stream(), dlg.dbcFile());
} else if (!can) {
openStream(new DummyStream(this));
openStream(new DummyStream());
}
}
void MainWindow::closeStream() {
openStream(new DummyStream(this));
openStream(new DummyStream());
if (dbc()->nonEmptyDBCCount() > 0) {
emit dbcNotifier()->DBCFileChanged();
dbc()->fileChanged();
}
statusBar()->showMessage(tr("stream closed"));
}
@@ -336,13 +336,19 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) {
}
}
// stream threads read the global `can` until its destructor joins them
MainWindow::~MainWindow() {
delete can;
can = nullptr;
}
void MainWindow::openStream(AbstractStream *stream, const QString &dbc_file) {
if (can) {
QObject::connect(can, &QObject::destroyed, this, [=]() { startStream(stream, dbc_file); });
can->deleteLater();
} else {
startStream(stream, dbc_file);
}
stream_connections_.clear();
if (wait_dlg_) wait_dlg_->deleteLater();
wait_dlg_ = nullptr;
delete can;
can = nullptr;
startStream(stream, dbc_file);
}
void MainWindow::startStream(AbstractStream *stream, QString dbc_file) {
@@ -350,8 +356,7 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) {
delete messages_widget;
delete video_splitter;
can = stream;
can->setParent(this); // take ownership
can = stream; // take ownership
can->start();
loadFile(dbc_file);
@@ -373,18 +378,19 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) {
newFile();
}
QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::eventsMerged);
stream_connections_.push_back(can->eventsMerged.connect([this](const MessageEventsMap &) { eventsMerged(); }));
if (has_stream) {
auto wait_dlg = new QProgressDialog(
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));
wait_dlg_->setWindowModality(Qt::WindowModal);
wait_dlg_->setFixedSize(400, wait_dlg_->sizeHint().height());
QObject::connect(wait_dlg_, &QProgressDialog::canceled, this, &MainWindow::close);
wait_dlg_connection_ = can->eventsMerged.connect([this](const MessageEventsMap &) {
wait_dlg_->deleteLater();
wait_dlg_ = nullptr;
wait_dlg_connection_.disconnect();
});
}
}
@@ -544,6 +550,7 @@ void MainWindow::remindSaveChanges() {
}
void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) {
if (wait_dlg_) wait_dlg_->setValue((int)((cur / (double)total) * 100));
if (success && cur < total) {
progress_bar->setValue((cur / (double)total) * 100);
progress_bar->setFormat(tr("Downloading %p% (%1)").arg(formattedDataSize(total).c_str()));
+7 -4
View File
@@ -19,11 +19,14 @@
#include "tools/cabana/videowidget.h"
#include "tools/cabana/tools/findsimilarbits.h"
class QProgressDialog;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(AbstractStream *stream, const QString &dbc_file);
~MainWindow();
void toggleChartsDocking();
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
void loadFile(const QString &fn, SourceSet s = SOURCE_ALL);
@@ -42,10 +45,6 @@ public slots:
void saveAs();
void saveToClipboard();
signals:
void showMessage(const QString &msg, int timeout);
void updateProgressBar(uint64_t cur, uint64_t total, bool success);
protected:
void startStream(AbstractStream *stream, QString dbc_file);
bool eventFilter(QObject *obj, QEvent *event) override;
@@ -104,6 +103,10 @@ protected:
QAction *redo_act = nullptr;
QString car_fingerprint;
std::vector<uint8_t> default_state;
Connections connections_;
Connections stream_connections_;
Connection wait_dlg_connection_;
QProgressDialog *wait_dlg_ = nullptr;
};
class HelpOverlay : public QWidget {
+7 -5
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/messageswidget.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <limits>
#include <utility>
@@ -43,9 +42,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget
QObject::connect(menu, &QMenu::aboutToShow, this, &MessagesWidget::menuAboutToShow);
QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent);
QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions);
QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, model, &MessageListModel::dbcModified);
QObject::connect(undoNotifier(), &QtUndoNotifier::indexChanged, model, &MessageListModel::dbcModified);
QObject::connect(model, &MessageListModel::modelReset, [this]() {
if (current_msg_id) {
selectMessage(*current_msg_id);
@@ -96,7 +92,7 @@ QWidget *MessagesWidget::createToolBar() {
QObject::connect(suppress_add, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted);
QObject::connect(suppress_clear, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted);
QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, can, &AbstractStream::suppressDefinedSignals);
QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, this, [](int state) { can->suppressDefinedSignals(state); });
suppressHighlighted();
return toolbar;
@@ -161,6 +157,12 @@ void MessagesWidget::setMultiLineBytes(bool multi) {
// MessageListModel
MessageListModel::MessageListModel(QObject *parent) : QAbstractTableModel(parent) {
connections_.push_back(can->msgsReceived.connect([this](const std::set<MessageId> *msgs, bool has_new_ids) { msgsReceived(msgs, has_new_ids); }));
connections_.push_back(dbc()->fileChanged.connect([this]() { dbcModified(); }));
connections_.push_back(UndoStack::instance()->indexChanged.connect([this]() { dbcModified(); }));
}
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
+2 -1
View File
@@ -31,7 +31,7 @@ public:
DATA,
};
MessageListModel(QObject *parent) : QAbstractTableModel(parent) {}
MessageListModel(QObject *parent);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return Column::DATA + 1; }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
@@ -63,6 +63,7 @@ private:
int sort_column = 0;
Qt::SortOrder sort_order = Qt::AscendingOrder;
int sort_threshold_ = 0;
Connections connections_;
};
class MessageView : public QTreeView {
+1 -1
View File
@@ -613,6 +613,6 @@ void SettingsDlg::save() {
settings.log_livestream = log_livestream->isChecked();
settings.log_path = log_path->text().toStdString();
settings.drag_direction = (Settings::DragDirection)drag_direction->currentIndex();
emit settings.changed();
settings.changed();
QDialog::accept();
}
+3 -5
View File
@@ -9,11 +9,10 @@
#include <QLineEdit>
#include <QSpinBox>
#include "tools/cabana/core/observable.h"
#include "tools/cabana/core/settings.h"
class Settings : public QObject, public CabanaSettingsState {
Q_OBJECT
class Settings : public CabanaSettingsState {
public:
Settings();
void save();
@@ -24,8 +23,7 @@ public:
std::vector<uint8_t> window_state;
std::vector<uint8_t> message_header_state;
signals:
void changed();
Observable<> changed;
};
class SettingsDlg : public QDialog {
+9 -10
View File
@@ -1,5 +1,4 @@
#include "tools/cabana/signalview.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <future>
@@ -26,12 +25,12 @@ static QString signalTypeToString(cabana::Signal::Type type) {
}
SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(parent) {
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &SignalModel::refresh);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgUpdated, this, &SignalModel::handleMsgChanged);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgRemoved, this, &SignalModel::handleMsgChanged);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalAdded, this, &SignalModel::handleSignalAdded);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalUpdated, this, &SignalModel::handleSignalUpdated);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalRemoved, this, &SignalModel::handleSignalRemoved);
connections_.push_back(dbc()->fileChanged.connect([this]() { refresh(); }));
connections_.push_back(dbc()->msgUpdated.connect([this](MessageId id) { handleMsgChanged(id); }));
connections_.push_back(dbc()->msgRemoved.connect([this](MessageId id) { handleMsgChanged(id); }));
connections_.push_back(dbc()->signalAdded.connect([this](MessageId id, const cabana::Signal *sig) { handleSignalAdded(id, sig); }));
connections_.push_back(dbc()->signalUpdated.connect([this](const cabana::Signal *sig) { handleSignalUpdated(sig); }));
connections_.push_back(dbc()->signalRemoved.connect([this](const cabana::Signal *sig) { handleSignalRemoved(sig); }));
}
void SignalModel::insertItem(SignalModel::Item *root_item, int pos, const cabana::Signal *sig) {
@@ -472,11 +471,11 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
QObject::connect(tree, &QTreeView::entered, [this](const QModelIndex &index) { emit highlight(model->getItem(index)->sig); });
QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalAdded, this, &SignalView::handleSignalAdded);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalUpdated, this, &SignalView::handleSignalUpdated);
connections_.push_back(dbc()->signalAdded.connect([this](MessageId id, const cabana::Signal *sig) { handleSignalAdded(id, sig); }));
connections_.push_back(dbc()->signalUpdated.connect([this](const cabana::Signal *sig) { handleSignalUpdated(sig); }));
QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); });
QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); });
QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalView::updateState);
connections_.push_back(can->msgsReceived.connect([this](const std::set<MessageId> *msgs, bool) { updateState(msgs); }));
QObject::connect(tree->header(), &QHeaderView::sectionResized, [this](int logicalIndex, int oldSize, int newSize) {
if (logicalIndex == 1) {
value_column_width = newSize;
+2
View File
@@ -62,6 +62,7 @@ private:
MessageId msg_id;
QString filter_str;
std::unique_ptr<Item> root;
Connections connections_;
friend class SignalView;
friend class SignalItemDelegate;
};
@@ -150,4 +151,5 @@ private:
ChartsWidget *charts;
QLabel *signal_count_lb;
SignalItemDelegate *delegate;
Connections connections_;
};
@@ -1,10 +1,8 @@
#include "tools/cabana/streams/abstractstream.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <limits>
#include <utility>
#include <QApplication>
#include "common/timing.h"
#include "tools/cabana/settings.h"
@@ -12,15 +10,41 @@ static const int EVENT_NEXT_BUFFER_SIZE = 6 * 1024 * 1024; // 6MB
AbstractStream *can = nullptr;
AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
assert(parent != nullptr);
AbstractStream::AbstractStream() {
event_buffer_ = std::make_unique<MonotonicBuffer>(EVENT_NEXT_BUFFER_SIZE);
QObject::connect(this, &AbstractStream::privateUpdateLastMsgsSignal, this, &AbstractStream::updateLastMessages, Qt::QueuedConnection);
QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo);
QObject::connect(this, &AbstractStream::seeking, this, [this](double sec) { current_sec_ = sec; });
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &AbstractStream::updateMasks);
QObject::connect(dbcNotifier(), &QtDBCNotifier::maskUpdated, this, &AbstractStream::updateMasks);
// connected first so the stream state is updated before any widget handlers run
connections_.push_back(seekedTo.connect([this](double sec) { updateLastMsgsTo(sec); }));
connections_.push_back(seeking.connect([this](double sec) { current_sec_ = sec; }));
connections_.push_back(dbc()->fileChanged.connect([this]() { updateMasks(); }));
connections_.push_back(dbc()->maskUpdated.connect([this]() { updateMasks(); }));
}
void AbstractStream::postToMainThread(std::function<void()> fn) {
utils::runOnMainThread([alive = std::weak_ptr<bool>(alive_), fn = std::move(fn)]() {
if (!alive.expired()) fn();
});
}
void AbstractStream::postToMainThreadAndWait(std::function<void()> fn) {
assert(!utils::isMainThread());
std::unique_lock lock(mutex_);
if (exiting_) return;
auto done = std::make_shared<bool>(false);
postToMainThread([this, alive = std::weak_ptr<bool>(alive_), done, fn = std::move(fn)]() {
fn();
if (alive.expired()) return; // fn deleted the stream, the waiter was released by cancelWaits()
std::lock_guard lk(mutex_);
*done = true;
wait_cv_.notify_all();
});
wait_cv_.wait(lock, [&]() { return *done || exiting_; });
}
void AbstractStream::cancelWaits() {
std::lock_guard lk(mutex_);
exiting_ = true;
wait_cv_.notify_all();
}
void AbstractStream::updateMasks() {
@@ -97,9 +121,8 @@ void AbstractStream::updateLastMessages() {
if (sources.size() != prev_src_size) {
updateMasks();
emit sourcesUpdated(sources);
}
emit msgsReceived(&msgs, prev_msg_size != last_msgs.size());
msgsReceived(&msgs, prev_msg_size != last_msgs.size());
}
void AbstractStream::setTimeRange(const std::optional<std::pair<double, double>> &range) {
@@ -107,7 +130,7 @@ void AbstractStream::setTimeRange(const std::optional<std::pair<double, double>>
if (time_range_ && (current_sec_ < time_range_->first || current_sec_ >= time_range_->second)) {
seekTo(time_range_->first);
}
emit timeRangeChanged(time_range_);
timeRangeChanged(time_range_);
}
void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size) {
@@ -175,16 +198,16 @@ void AbstractStream::updateLastMsgsTo(double sec) {
std::any_of(messages_.cbegin(), messages_.cend(),
[this](const auto &m) { return !last_msgs.count(m.first); });
last_msgs = messages_;
emit msgsReceived(nullptr, id_changed);
msgsReceived(nullptr, id_changed);
std::lock_guard lk(mutex_);
seek_finished_ = true;
seek_finished_cv_.notify_one();
wait_cv_.notify_all();
}
void AbstractStream::waitForSeekFinshed() {
std::unique_lock lock(mutex_);
seek_finished_cv_.wait(lock, [this]() { return seek_finished_; });
wait_cv_.wait(lock, [this]() { return seek_finished_ || exiting_; });
seek_finished_ = false;
}
@@ -218,16 +241,16 @@ void AbstractStream::mergeEvents(const std::vector<const CanEvent *> &events) {
}
auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), events.front()->mono_time, CompareCanEvent());
all_events_.insert(pos, events.cbegin(), events.cend());
emit eventsMerged(msg_events);
eventsMerged(msg_events);
}
}
std::pair<CanEventIter, CanEventIter> AbstractStream::eventsInRange(const MessageId &id, std::optional<std::pair<double, double>> time_range) const {
const auto &events = can->events(id);
const auto &events = this->events(id);
if (!time_range) return {events.begin(), events.end()};
auto first = std::lower_bound(events.begin(), events.end(), can->toMonoTime(time_range->first), CompareCanEvent());
auto last = std::upper_bound(first, events.end(), can->toMonoTime(time_range->second), CompareCanEvent());
auto first = std::lower_bound(events.begin(), events.end(), toMonoTime(time_range->first), CompareCanEvent());
auto last = std::upper_bound(first, events.end(), toMonoTime(time_range->second), CompareCanEvent());
return {first, last};
}
+21 -19
View File
@@ -4,6 +4,7 @@
#include <array>
#include <condition_variable>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
@@ -14,16 +15,15 @@
#include "openpilot/cereal/messaging/messaging.h"
#include "tools/cabana/core/can_data.h"
#include "tools/cabana/core/observable.h"
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/utils/util.h"
#include "tools/replay/util.h"
class AbstractStream : public QObject {
Q_OBJECT
class AbstractStream {
public:
AbstractStream(QObject *parent);
virtual ~AbstractStream() {}
AbstractStream();
virtual ~AbstractStream() = default;
virtual void start() = 0;
virtual bool liveStreaming() const { return true; }
virtual void seekTo(double ts) {}
@@ -56,21 +56,22 @@ public:
void clearSuppressed();
void suppressDefinedSignals(bool suppress);
signals:
void paused();
void resume();
void seeking(double sec);
void seekedTo(double sec);
void timeRangeChanged(const std::optional<std::pair<double, double>> &range);
void eventsMerged(const MessageEventsMap &events_map);
void msgsReceived(const std::set<MessageId> *new_msgs, bool has_new_ids);
void sourcesUpdated(const SourceSet &s);
void privateUpdateLastMsgsSignal();
// invoked on the main thread
Observable<> paused;
Observable<> resume;
Observable<double> seeking;
Observable<double> seekedTo;
Observable<const std::optional<std::pair<double, double>> &> timeRangeChanged;
Observable<const MessageEventsMap &> eventsMerged;
Observable<const std::set<MessageId> *, bool> msgsReceived;
public:
SourceSet sources;
protected:
void postToMainThread(std::function<void()> fn); // dropped if the stream is destroyed first
void postToMainThreadAndWait(std::function<void()> fn);
void cancelWaits(); // call before joining threads, the main thread isn't pumping events during destruction
void requestUpdateLastMessages() { postToMainThread([this]() { updateLastMessages(); }); }
void mergeEvents(const std::vector<const CanEvent *> &events);
const CanEvent *newEvent(uint64_t mono_time, const cereal::CanData::Reader &c);
void updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size);
@@ -87,11 +88,14 @@ private:
MessageEventsMap events_;
std::unordered_map<MessageId, CanData> last_msgs;
std::unique_ptr<MonotonicBuffer> event_buffer_;
std::shared_ptr<bool> alive_ = std::make_shared<bool>(true);
Connections connections_;
// Members accessed in multiple threads. (mutex protected)
std::mutex mutex_;
std::condition_variable seek_finished_cv_;
std::condition_variable wait_cv_;
bool seek_finished_ = false;
bool exiting_ = false;
std::set<MessageId> new_msgs_;
std::unordered_map<MessageId, CanData> messages_;
std::unordered_map<MessageId, std::vector<uint8_t>> masks_;
@@ -108,9 +112,7 @@ signals:
};
class DummyStream : public AbstractStream {
Q_OBJECT
public:
DummyStream(QObject *parent) : AbstractStream(parent) {}
std::string routeName() const override { return "No Stream"; }
void start() override {}
};
@@ -23,7 +23,7 @@
// DeviceStream
DeviceStream::DeviceStream(QObject *parent, QString address) : zmq_address(address), LiveStream(parent) {
DeviceStream::DeviceStream(QString address) : zmq_address(address) {
}
DeviceStream::~DeviceStream() {
@@ -61,8 +61,8 @@ void DeviceStream::start() {
// fails, the child writes errno and the parent aborts stream start.
int err_pipe[2] = {-1, -1};
if (::pipe(err_pipe) != 0) {
QMessageBox::warning(nullptr, tr("Error"),
tr("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(errno))));
QMessageBox::warning(nullptr, "Error",
QString("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(errno))));
return;
}
@@ -79,8 +79,8 @@ void DeviceStream::start() {
::close(err_pipe[1]);
if (pid < 0) {
::close(err_pipe[0]);
QMessageBox::warning(nullptr, tr("Error"),
tr("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(errno))));
QMessageBox::warning(nullptr, "Error",
QString("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(errno))));
return;
}
@@ -91,8 +91,8 @@ void DeviceStream::start() {
// Child failed to exec; reap and surface the error.
int status = 0;
::waitpid(pid, &status, 0);
QMessageBox::warning(nullptr, tr("Error"),
tr("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(exec_errno))));
QMessageBox::warning(nullptr, "Error",
QString("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(exec_errno))));
return;
}
@@ -144,5 +144,5 @@ OpenDeviceWidget::OpenDeviceWidget(QWidget *parent) : AbstractOpenStreamWidget(p
AbstractStream *OpenDeviceWidget::open() {
QString ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text();
bool msgq = group->checkedId() == 0;
return new DeviceStream(qApp, msgq ? "" : ip);
return new DeviceStream(msgq ? "" : ip);
}
@@ -5,9 +5,8 @@
#include <sys/types.h>
class DeviceStream : public LiveStream {
Q_OBJECT
public:
DeviceStream(QObject *parent, QString address = {});
DeviceStream(QString address = {});
~DeviceStream();
inline std::string routeName() const override {
return "Live Streaming From " + (zmq_address.isEmpty() ? std::string("127.0.0.1") : zmq_address.toStdString());
+6 -6
View File
@@ -38,7 +38,7 @@ struct LiveStream::Logger {
uint64_t start_ts;
};
LiveStream::LiveStream(QObject *parent) : AbstractStream(parent) {
LiveStream::LiveStream() {
if (settings.log_livestream) {
logger = std::make_unique<Logger>();
}
@@ -65,9 +65,9 @@ void LiveStream::stop() {
void LiveStream::updateThread() {
while (!exit_) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / fps_));
// coalesce: skip the emit if the main thread hasn't processed the previous one yet.
// coalesce: skip the request if the main thread hasn't processed the previous one yet.
if (!update_pending_.exchange(true)) {
emit privateUpdateLastMsgsSignal();
requestUpdateLastMessages();
}
}
}
@@ -89,7 +89,7 @@ void LiveStream::handleEvent(kj::ArrayPtr<capnp::word> data) {
}
}
// called on the main thread by the queued privateUpdateLastMsgsSignal connection
// called on the main thread via requestUpdateLastMessages()
void LiveStream::updateLastMessages() {
update_pending_ = false;
fps_ = settings.fps;
@@ -142,10 +142,10 @@ void LiveStream::seekTo(double sec) {
first_update_ts = nanos_since_boot();
current_event_ts = first_event_ts = std::min<uint64_t>(sec * 1e9 + begin_event_ts, lastest_event_ts);
post_last_event = (first_event_ts == lastest_event_ts);
emit seekedTo((current_event_ts - begin_event_ts) / 1e9);
seekedTo((current_event_ts - begin_event_ts) / 1e9);
}
void LiveStream::pause(bool pause) {
paused_ = pause;
emit(pause ? paused() : resume());
pause ? paused() : resume();
}
+1 -3
View File
@@ -9,10 +9,8 @@
#include "tools/cabana/streams/abstractstream.h"
class LiveStream : public AbstractStream {
Q_OBJECT
public:
LiveStream(QObject *parent);
LiveStream();
virtual ~LiveStream();
void start() override;
void stop();
@@ -10,7 +10,7 @@
#include <QPushButton>
#include <QTimer>
PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(config_), LiveStream(parent) {
PandaStream::PandaStream(PandaStreamConfig config_) : config(config_) {
if (!connect()) {
throw std::runtime_error("Failed to connect to panda");
}
@@ -181,7 +181,7 @@ void OpenPandaWidget::buildConfigForm() {
AbstractStream *OpenPandaWidget::open() {
try {
return new PandaStream(qApp, config);
return new PandaStream(config);
} catch (std::exception &e) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
return nullptr;
+1 -2
View File
@@ -24,9 +24,8 @@ struct PandaStreamConfig {
};
class PandaStream : public LiveStream {
Q_OBJECT
public:
PandaStream(QObject *parent, PandaStreamConfig config_ = {});
PandaStream(PandaStreamConfig config_ = {});
~PandaStream() { stop(); }
inline std::string routeName() const override {
return "Panda: " + config.serial;
+22 -18
View File
@@ -12,17 +12,21 @@
#include "common/util.h"
#include "tools/cabana/streams/routes.h"
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
ReplayStream::ReplayStream() {
unsetenv("ZMQ");
setenv("COMMA_CACHE", "/tmp/comma_download_cache", 1);
op_prefix = std::make_unique<OpenpilotPrefix>();
QObject::connect(&settings, &Settings::changed, this, [this]() {
settings_connection_ = settings.changed.connect([this]() {
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);
});
}
ReplayStream::~ReplayStream() {
cancelWaits();
}
void ReplayStream::mergeSegments() {
auto event_data = replay->getEventData();
for (const auto &[n, seg] : event_data->segments) {
@@ -51,14 +55,14 @@ bool ReplayStream::loadRoute(const std::string &route, const std::string &data_d
replay->setSegmentCacheLimit(settings.max_cached_minutes);
replay->installEventFilter([this](const Event *event) { return eventFilter(event); });
// Forward replay callbacks to corresponding Qt signals.
replay->onSeeking = [this](double sec) { emit seeking(sec); };
// replay callbacks arrive on replay threads
replay->onSeeking = [this](double sec) { postToMainThread([this, sec]() { seeking(sec); }); };
replay->onSeekedTo = [this](double sec) {
emit seekedTo(sec);
postToMainThread([this, sec]() { seekedTo(sec); });
waitForSeekFinshed();
};
replay->onQLogLoaded = [this](std::shared_ptr<LogReader> qlog) { emit qLogLoaded(qlog); };
replay->onSegmentsMerged = [this]() { QMetaObject::invokeMethod(this, &ReplayStream::mergeSegments, Qt::BlockingQueuedConnection); };
replay->onQLogLoaded = [this](std::shared_ptr<LogReader> qlog) { postToMainThread([this, qlog]() { qLogLoaded(qlog); }); };
replay->onSegmentsMerged = [this]() { postToMainThreadAndWait([this]() { mergeSegments(); }); };
bool success = replay->load();
if (!success) {
@@ -70,18 +74,18 @@ bool ReplayStream::loadRoute(const std::string &route, const std::string &data_d
"python3 openpilot/tools/lib/auth.py\n\n"
"This will grant access to routes from your comma account.";
} else {
message = tr("Access Denied. You do not have permission to access route:\n\n%1\n\n"
"This is likely a private route.").arg(QString::fromStdString(route));
message = QString("Access Denied. You do not have permission to access route:\n\n%1\n\n"
"This is likely a private route.").arg(QString::fromStdString(route));
}
QMessageBox::warning(nullptr, tr("Access Denied"), message);
QMessageBox::warning(nullptr, "Access Denied", message);
} else if (replay->lastRouteError() == RouteLoadError::NetworkError) {
QMessageBox::warning(nullptr, tr("Network Error"),
tr("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(QString::fromStdString(route)));
QMessageBox::warning(nullptr, "Network Error",
QString("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(QString::fromStdString(route)));
} else if (replay->lastRouteError() == RouteLoadError::FileNotFound) {
QMessageBox::warning(nullptr, tr("Route Not Found"),
tr("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(QString::fromStdString(route)));
QMessageBox::warning(nullptr, "Route Not Found",
QString("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(QString::fromStdString(route)));
} else {
QMessageBox::warning(nullptr, tr("Route Load Failed"), tr("Failed to load route: '%1'").arg(QString::fromStdString(route)));
QMessageBox::warning(nullptr, "Route Load Failed", QString("Failed to load route: '%1'").arg(QString::fromStdString(route)));
}
}
return success;
@@ -102,7 +106,7 @@ bool ReplayStream::eventFilter(const Event *event) {
double ts = millis_since_boot();
if ((ts - prev_update_ts) > (1000.0 / settings.fps)) {
emit privateUpdateLastMsgsSignal();
requestUpdateLastMessages();
prev_update_ts = ts;
}
return true;
@@ -110,7 +114,7 @@ bool ReplayStream::eventFilter(const Event *event) {
void ReplayStream::pause(bool pause) {
replay->pause(pause);
emit(pause ? paused() : resume());
pause ? paused() : resume();
}
@@ -161,7 +165,7 @@ AbstractStream *OpenReplayWidget::open() {
if (!is_valid_format) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
} else {
auto replay_stream = std::make_unique<ReplayStream>(qApp);
auto replay_stream = std::make_unique<ReplayStream>();
uint32_t flags = REPLAY_FLAG_NONE;
if (cameras[1]->isChecked()) flags |= REPLAY_FLAG_CABIN_CAMERA;
if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_WIDE_ROAD;
@@ -10,13 +10,10 @@
#include "tools/cabana/streams/abstractstream.h"
#include "tools/replay/replay.h"
Q_DECLARE_METATYPE(std::shared_ptr<LogReader>);
class ReplayStream : public AbstractStream {
Q_OBJECT
public:
ReplayStream(QObject *parent);
ReplayStream();
~ReplayStream();
void start() override { replay->start(); }
bool loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false);
bool eventFilter(const Event *event);
@@ -36,12 +33,13 @@ public:
inline bool isPaused() const override { return replay->isPaused(); }
void pause(bool pause) override;
signals:
void qLogLoaded(std::shared_ptr<LogReader> qlog);
// invoked on the main thread
Observable<std::shared_ptr<LogReader>> qLogLoaded;
private:
void mergeSegments();
std::unique_ptr<Replay> replay = nullptr;
Connection settings_connection_;
std::set<int> processed_segments;
std::unique_ptr<OpenpilotPrefix> op_prefix;
};
@@ -16,7 +16,7 @@
#include <QMessageBox>
#include <QPushButton>
SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) {
SocketCanStream::SocketCanStream(SocketCanStreamConfig config_) : config(config_) {
if (!available()) {
throw std::runtime_error("SocketCAN not available");
}
@@ -140,7 +140,7 @@ void OpenSocketCanWidget::refreshDevices() {
AbstractStream *OpenSocketCanWidget::open() {
try {
return new SocketCanStream(qApp, config);
return new SocketCanStream(config);
} catch (std::exception &e) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to SocketCAN device: '%1'").arg(e.what()));
return nullptr;
@@ -9,9 +9,8 @@ struct SocketCanStreamConfig {
};
class SocketCanStream : public LiveStream {
Q_OBJECT
public:
SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {});
SocketCanStream(SocketCanStreamConfig config_ = {});
~SocketCanStream();
static bool available();
+4 -5
View File
@@ -164,11 +164,10 @@ void test_dbc_manager() {
int files_changed = 0;
int signals_added = 0;
int masks_updated = 0;
manager.setCallbacks({
.signal_added = [&](MessageId, const cabana::Signal *) { ++signals_added; },
.file_changed = [&]() { ++files_changed; },
.mask_updated = [&]() { ++masks_updated; },
});
Connections connections;
connections.push_back(manager.signalAdded.connect([&](MessageId, const cabana::Signal *) { ++signals_added; }));
connections.push_back(manager.fileChanged.connect([&]() { ++files_changed; }));
connections.push_back(manager.maskUpdated.connect([&]() { ++masks_updated; }));
std::string error;
REQUIRE(manager.open(SOURCE_ALL, "test", "BO_ 160 message: 8 XXX\n", &error));
+1 -1
View File
@@ -6,7 +6,7 @@
#include "tools/cabana/streams/replaystream.h"
RouteInfoDlg::RouteInfoDlg(QWidget *parent) : QDialog(parent) {
auto *replay = qobject_cast<ReplayStream *>(can)->getReplay();
auto *replay = dynamic_cast<ReplayStream *>(can)->getReplay();
setWindowTitle(tr("Route: %1").arg(QString::fromStdString(replay->route().name())));
auto *table = new QTableWidget(replay->route().segments().size(), 7, this);
+26
View File
@@ -11,7 +11,9 @@
#include <filesystem>
#include <limits>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -23,6 +25,30 @@
#include <unordered_map>
#include "common/util.h"
static const std::thread::id main_thread_id = std::this_thread::get_id();
static std::mutex main_thread_queue_mutex;
static std::vector<std::function<void()>> main_thread_queue;
bool utils::isMainThread() { return std::this_thread::get_id() == main_thread_id; }
void utils::runOnMainThread(std::function<void()> fn) {
if (isMainThread()) {
fn();
} else {
std::lock_guard lk(main_thread_queue_mutex);
main_thread_queue.push_back(std::move(fn));
}
}
void utils::drainMainThreadQueue() {
std::vector<std::function<void()>> fns;
{
std::lock_guard lk(main_thread_queue_mutex);
fns.swap(main_thread_queue);
}
for (auto &fn : fns) fn();
}
// SegmentTree
void SegmentTree::build(const std::vector<QPointF> &arr) {
+12 -1
View File
@@ -4,6 +4,7 @@
#include <atomic>
#include <cmath>
#include <filesystem>
#include <functional>
#include <string>
#include <thread>
#include <vector>
@@ -20,9 +21,14 @@
#include <QToolButton>
#include <QValidator>
#include "tools/cabana/core/observable.h"
#include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/settings.h"
// needed by QVariant::fromValue() in the Qt views; goes away with QVariant
Q_DECLARE_METATYPE(MessageId)
Q_DECLARE_METATYPE(ValueDescription)
inline QColor toQColor(const CabanaColor &color) {
return QColor(color.r, color.g, color.b, color.a);
}
@@ -132,6 +138,10 @@ public:
namespace utils {
bool isMainThread();
// inline on the main thread, queued until drainMainThreadQueue() otherwise
void runOnMainThread(std::function<void()> fn);
void drainMainThreadQueue();
QPixmap icon(const QString &id);
std::string homePath();
std::filesystem::path configPath();
@@ -175,7 +185,7 @@ public:
const int metric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
setIconSize({metric, metric});
theme = settings.theme;
connect(&settings, &Settings::changed, this, &ToolButton::updateIcon);
settings_connection_ = settings.changed.connect([this]() { updateIcon(); });
}
void setIcon(const QString &icon) {
icon_str = icon;
@@ -184,6 +194,7 @@ public:
private:
void updateIcon() { if (std::exchange(theme, settings.theme) != theme) setIcon(icon_str); }
Connection settings_connection_;
QString icon_str;
int theme;
};
+11 -11
View File
@@ -27,7 +27,7 @@ static const QColor timeline_colors[] = {
};
static Replay *getReplay() {
auto stream = qobject_cast<ReplayStream *>(can);
auto stream = dynamic_cast<ReplayStream *>(can);
return stream ? stream->getReplay() : nullptr;
}
@@ -42,11 +42,11 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
createPlaybackController();
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::msgsReceived, this, &VideoWidget::updateState);
QObject::connect(can, &AbstractStream::seeking, this, &VideoWidget::updateState);
QObject::connect(can, &AbstractStream::timeRangeChanged, this, &VideoWidget::timeRangeChanged);
connections_.push_back(can->paused.connect([this]() { updatePlayBtnState(); }));
connections_.push_back(can->resume.connect([this]() { updatePlayBtnState(); }));
connections_.push_back(can->msgsReceived.connect([this](const std::set<MessageId> *, bool) { updateState(); }));
connections_.push_back(can->seeking.connect([this](double) { updateState(); }));
connections_.push_back(can->timeRangeChanged.connect([this](const auto &) { timeRangeChanged(); }));
updatePlayBtnState();
setWhatsThis(tr(R"(
@@ -157,14 +157,14 @@ QWidget *VideoWidget::createCameraWidget() {
slider->setTimeRange(can->minSeconds(), can->maxSeconds());
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); });
QObject::connect(can, &AbstractStream::paused, cam_widget, qOverload<>(&StreamCameraView::update));
QObject::connect(can, &AbstractStream::eventsMerged, this, [this]() { slider->update(); });
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(cam_widget, &CameraWidget::vipcAvailableStreamsUpdated, this, &VideoWidget::vipcAvailableStreamsUpdated);
connections_.push_back(can->paused.connect([this]() { cam_widget->update(); }));
connections_.push_back(can->eventsMerged.connect([this](const MessageEventsMap &) { slider->update(); }));
connections_.push_back(cam_widget->clicked.connect([]() { can->pause(!can->isPaused()); }));
connections_.push_back(cam_widget->availableStreamsUpdated.connect([this](std::set<VisionStreamType> streams) { vipcAvailableStreamsUpdated(streams); }));
QObject::connect(camera_tab, &QTabBar::currentChanged, [this](int index) {
if (index != -1) cam_widget->setStreamType((VisionStreamType)camera_tab->tabData(index).toInt());
});
QObject::connect(static_cast<ReplayStream*>(can), &ReplayStream::qLogLoaded, cam_widget, &StreamCameraView::parseQLog, Qt::QueuedConnection);
connections_.push_back(static_cast<ReplayStream *>(can)->qLogLoaded.connect([this](std::shared_ptr<LogReader> qlog) { cam_widget->parseQLog(qlog); }));
slider->installEventFilter(this);
return w;
}
+1
View File
@@ -64,6 +64,7 @@ protected:
void timeRangeChanged();
void updateState();
void updatePlayBtnState();
Connections connections_;
QWidget *createCameraWidget();
void createPlaybackController();
void createSpeedDropdown(QToolBar *toolbar);