cabana: supports switching streams on the fly (#28081)

* open stream

* use std::atomic::exchange

* emit streamStarted immediately
pull/28037/head
Dean Lee 2 years ago committed by GitHub
parent 75fc029130
commit a26e6d1633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      tools/cabana/cabana.cc
  2. 1
      tools/cabana/chart/chartswidget.cc
  3. 1
      tools/cabana/chart/chartswidget.h
  4. 5
      tools/cabana/detailwidget.cc
  5. 4
      tools/cabana/detailwidget.h
  6. 96
      tools/cabana/mainwin.cc
  7. 15
      tools/cabana/mainwin.h
  8. 17
      tools/cabana/messageswidget.cc
  9. 2
      tools/cabana/messageswidget.h
  10. 2
      tools/cabana/signalview.cc
  11. 19
      tools/cabana/streams/abstractstream.cc
  12. 12
      tools/cabana/streams/abstractstream.h
  13. 5
      tools/cabana/streams/devicestream.cc
  14. 11
      tools/cabana/streams/livestream.cc
  15. 2
      tools/cabana/streams/livestream.h
  16. 1
      tools/cabana/streams/pandastream.cc
  17. 42
      tools/cabana/streams/replaystream.cc
  18. 4
      tools/cabana/streams/replaystream.h
  19. 27
      tools/cabana/streamselector.cc
  20. 4
      tools/cabana/streamselector.h
  21. 43
      tools/cabana/videowidget.cc
  22. 5
      tools/cabana/videowidget.h

@ -1,7 +1,6 @@
#include <QApplication> #include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include "common/prefix.h"
#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/util.h"
#include "tools/cabana/mainwin.h" #include "tools/cabana/mainwin.h"
#include "tools/cabana/streamselector.h" #include "tools/cabana/streamselector.h"
@ -35,17 +34,15 @@ int main(int argc, char *argv[]) {
QString dbc_file = cmd_parser.isSet("dbc") ? cmd_parser.value("dbc") : ""; QString dbc_file = cmd_parser.isSet("dbc") ? cmd_parser.value("dbc") : "";
std::unique_ptr<OpenpilotPrefix> op_prefix; AbstractStream *stream = nullptr;
std::unique_ptr<AbstractStream> stream;
if (cmd_parser.isSet("stream")) { if (cmd_parser.isSet("stream")) {
stream.reset(new DeviceStream(&app, cmd_parser.value("zmq"))); stream = new DeviceStream(&app, cmd_parser.value("zmq"));
} else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) { } else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) {
PandaStreamConfig config = {}; PandaStreamConfig config = {};
if (cmd_parser.isSet("panda-serial")) { if (cmd_parser.isSet("panda-serial")) {
config.serial = cmd_parser.value("panda-serial"); config.serial = cmd_parser.value("panda-serial");
} }
stream.reset(new PandaStream(&app, config)); stream = new PandaStream(&app, config);
} else { } else {
uint32_t replay_flags = REPLAY_FLAG_NONE; uint32_t replay_flags = REPLAY_FLAG_NONE;
if (cmd_parser.isSet("ecam")) { if (cmd_parser.isSet("ecam")) {
@ -65,36 +62,26 @@ int main(int argc, char *argv[]) {
} }
if (route.isEmpty()) { if (route.isEmpty()) {
AbstractStream *out_stream = nullptr; StreamSelector dlg(&stream);
StreamSelector dlg;
dlg.addStreamWidget(ReplayStream::widget(&out_stream));
dlg.addStreamWidget(PandaStream::widget(&out_stream));
dlg.addStreamWidget(DeviceStream::widget(&out_stream));
if (!dlg.exec()) { if (!dlg.exec()) {
return 0; return 0;
} }
dbc_file = dlg.dbcFile(); dbc_file = dlg.dbcFile();
stream.reset(out_stream);
} else { } else {
// TODO: Remove when OpenpilotPrefix supports ZMQ
#ifndef __APPLE__
op_prefix.reset(new OpenpilotPrefix());
#endif
auto replay_stream = new ReplayStream(&app); auto replay_stream = new ReplayStream(&app);
stream.reset(replay_stream);
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) { if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
return 0; return 0;
} }
stream = replay_stream;
} }
} }
MainWindow w; MainWindow w;
// Load DBC
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();
} }

@ -398,6 +398,7 @@ void ChartsWidget::removeAll() {
tabbar->removeTab(1); tabbar->removeTab(1);
} }
tab_charts.clear(); tab_charts.clear();
zoomReset();
if (!charts.isEmpty()) { if (!charts.isEmpty()) {
for (auto c : charts) { for (auto c : charts) {

@ -124,4 +124,3 @@ public:
ChartsWidget *charts; ChartsWidget *charts;
std::pair<double, double> prev_range, range; std::pair<double, double> prev_range, range;
}; };

@ -5,6 +5,7 @@
#include <QMessageBox> #include <QMessageBox>
#include "tools/cabana/commands.h" #include "tools/cabana/commands.h"
#include "tools/cabana/mainwin.h"
// DetailWidget // DetailWidget
@ -218,7 +219,7 @@ void EditMessageDialog::validateName(const QString &text) {
// CenterWidget // CenterWidget
CenterWidget::CenterWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { CenterWidget::CenterWidget(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);
main_layout->addWidget(welcome_widget = createWelcomeWidget()); main_layout->addWidget(welcome_widget = createWelcomeWidget());
@ -228,7 +229,7 @@ void CenterWidget::setMessage(const MessageId &msg_id) {
if (!detail_widget) { if (!detail_widget) {
delete welcome_widget; delete welcome_widget;
welcome_widget = nullptr; welcome_widget = nullptr;
layout()->addWidget(detail_widget = new DetailWidget(charts, this)); layout()->addWidget(detail_widget = new DetailWidget(((MainWindow*)parentWidget())->charts_widget, this));
} }
detail_widget->setMessage(msg_id); detail_widget->setMessage(msg_id);
} }

@ -10,6 +10,7 @@
#include "tools/cabana/historylog.h" #include "tools/cabana/historylog.h"
#include "tools/cabana/signalview.h" #include "tools/cabana/signalview.h"
class MainWindow;
class EditMessageDialog : public QDialog { class EditMessageDialog : public QDialog {
public: public:
EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent); EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent);
@ -54,7 +55,7 @@ private:
class CenterWidget : public QWidget { class CenterWidget : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
CenterWidget(ChartsWidget* charts, QWidget *parent); CenterWidget(QWidget *parent);
void setMessage(const MessageId &msg_id); void setMessage(const MessageId &msg_id);
void clear(); void clear();
@ -62,5 +63,4 @@ private:
QWidget *createWelcomeWidget(); QWidget *createWelcomeWidget();
DetailWidget *detail_widget = nullptr; DetailWidget *detail_widget = nullptr;
QWidget *welcome_widget = nullptr; QWidget *welcome_widget = nullptr;
ChartsWidget *charts;
}; };

@ -18,7 +18,6 @@
#include "tools/cabana/commands.h" #include "tools/cabana/commands.h"
#include "tools/cabana/streamselector.h" #include "tools/cabana/streamselector.h"
#include "tools/cabana/streams/replaystream.h"
#include "tools/cabana/tools/findsignal.h" #include "tools/cabana/tools/findsignal.h"
static MainWindow *main_win = nullptr; static MainWindow *main_win = nullptr;
@ -29,8 +28,7 @@ void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const
MainWindow::MainWindow() : QMainWindow() { MainWindow::MainWindow() : QMainWindow() {
createDockWindows(); createDockWindows();
center_widget = new CenterWidget(charts_widget, this); setCentralWidget(center_widget = new CenterWidget(this));
setCentralWidget(center_widget);
createActions(); createActions();
createStatusBar(); createStatusBar();
createShortcuts(); createShortcuts();
@ -68,23 +66,18 @@ MainWindow::MainWindow() : QMainWindow() {
QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage); QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage);
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress); QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage);
QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts);
QObject::connect(can, &AbstractStream::streamStarted, this, &MainWindow::streamStarted);
QObject::connect(can, &AbstractStream::sourcesUpdated, dbc(), &DBCManager::updateSources);
QObject::connect(can, &AbstractStream::sourcesUpdated, this, &MainWindow::updateLoadSaveMenus);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged);
QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged); QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged);
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MainWindow::undoStackIndexChanged); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MainWindow::undoStackIndexChanged);
QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus); QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus);
QObject::connect(StreamNotifier::instance(), &StreamNotifier::changingStream, this, &MainWindow::changingStream);
QObject::connect(StreamNotifier::instance(), &StreamNotifier::streamStarted, this, &MainWindow::streamStarted);
} }
void MainWindow::createActions() { void MainWindow::createActions() {
QMenu *file_menu = menuBar()->addMenu(tr("&File")); QMenu *file_menu = menuBar()->addMenu(tr("&File"));
if (!can->liveStreaming()) { file_menu->addAction(tr("Open Stream..."), this, &MainWindow::openStream);
file_menu->addAction(tr("Open Route..."), this, &MainWindow::openRoute);
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);
file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); })->setShortcuts(QKeySequence::Open); file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); })->setShortcuts(QKeySequence::Open);
@ -151,14 +144,22 @@ void MainWindow::createActions() {
} }
void MainWindow::createDockWindows() { void MainWindow::createDockWindows() {
// left panel messages_dock = new QDockWidget(tr("MESSAGES"), this);
messages_dock->setObjectName("MessagesPanel");
messages_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
messages_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
addDockWidget(Qt::LeftDockWidgetArea, messages_dock);
video_dock = new QDockWidget("", this);
video_dock->setObjectName(tr("VideoPanel"));
video_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
video_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
addDockWidget(Qt::RightDockWidgetArea, video_dock);
}
void MainWindow::createDockWidgets() {
messages_widget = new MessagesWidget(this); messages_widget = new MessagesWidget(this);
QDockWidget *dock = new QDockWidget(tr("MESSAGES"), this); messages_dock->setWidget(messages_widget);
dock->setObjectName("MessagesPanel");
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
dock->setWidget(messages_widget);
addDockWidget(Qt::LeftDockWidgetArea, dock);
// right panel // right panel
charts_widget = new ChartsWidget(this); charts_widget = new ChartsWidget(this);
@ -176,17 +177,8 @@ void MainWindow::createDockWindows() {
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);
if (can->liveStreaming() || video_splitter->sizes()[0] == 0) {
// display video at minimum size.
video_splitter->setSizes({1, 1});
}
video_dock = new QDockWidget(can->routeName(), this);
video_dock->setObjectName(tr("VideoPanel"));
video_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
video_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
video_dock->setWidget(video_splitter); video_dock->setWidget(video_splitter);
addDockWidget(Qt::RightDockWidgetArea, video_dock); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts);
} }
void MainWindow::createStatusBar() { void MainWindow::createStatusBar() {
@ -242,15 +234,15 @@ void MainWindow::DBCFileChanged() {
updateLoadSaveMenus(); updateLoadSaveMenus();
} }
void MainWindow::openRoute() { void MainWindow::openStream() {
StreamSelector dlg(this); AbstractStream *stream = nullptr;
dlg.addStreamWidget(ReplayStream::widget(&can)); StreamSelector dlg(&stream, this);
if (dlg.exec()) { if (dlg.exec()) {
center_widget->clear(); if (!dlg.dbcFile().isEmpty()) {
charts_widget->removeAll(); loadFile(dlg.dbcFile());
}
stream->start();
statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000); statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000);
} else if (dlg.failed()) {
close();
} }
} }
@ -319,24 +311,42 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) {
} }
} }
void MainWindow::changingStream() {
center_widget->clear();
delete messages_widget;
delete video_splitter;
}
void MainWindow::streamStarted() { void MainWindow::streamStarted() {
auto fingerprint = can->carFingerprint(); createDockWidgets();
if (can->liveStreaming()) {
video_dock->setWindowTitle(can->routeName()); video_dock->setWindowTitle(can->routeName());
} else { if (can->liveStreaming() || video_splitter->sizes()[0] == 0) {
video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPRINT: %2").arg(can->routeName()).arg(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint)); // display video at minimum size.
video_splitter->setSizes({1, 1});
} }
// Don't overwrite already loaded DBC // Don't overwrite already loaded DBC
if (!dbc()->msgCount()) { if (!dbc()->msgCount()) {
if (!fingerprint.isEmpty()) { newFile();
}
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage);
QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::eventsMerged);
QObject::connect(can, &AbstractStream::sourcesUpdated, dbc(), &DBCManager::updateSources);
QObject::connect(can, &AbstractStream::sourcesUpdated, this, &MainWindow::updateLoadSaveMenus);
}
void MainWindow::eventsMerged() {
if (!can->liveStreaming()) {
auto fingerprint = can->carFingerprint();
video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPRINT: %2").arg(can->routeName()).arg(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint));
// Don't overwrite already loaded DBC
if (!dbc()->msgCount() && !fingerprint.isEmpty()) {
auto dbc_name = fingerprint_to_dbc[fingerprint]; auto dbc_name = fingerprint_to_dbc[fingerprint];
if (dbc_name != QJsonValue::Undefined) { if (dbc_name != QJsonValue::Undefined) {
loadDBCFromOpendbc(dbc_name.toString()); loadDBCFromOpendbc(dbc_name.toString());
return;
} }
} }
newFile();
} }
} }

@ -22,14 +22,17 @@ public:
void dockCharts(bool dock); void dockCharts(bool dock);
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); } void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
void loadFile(const QString &fn, SourceSet s = SOURCE_ALL); void loadFile(const QString &fn, SourceSet s = SOURCE_ALL);
ChartsWidget *charts_widget = nullptr;
public slots: public slots:
void openRoute(); void openStream();
void changingStream();
void streamStarted();
void newFile(SourceSet s = SOURCE_ALL); void newFile(SourceSet s = SOURCE_ALL);
void openFile(SourceSet s = SOURCE_ALL); void openFile(SourceSet s = SOURCE_ALL);
void openRecentFile(); void openRecentFile();
void loadDBCFromOpendbc(const QString &name); void loadDBCFromOpendbc(const QString &name);
void streamStarted();
void save(); void save();
void saveAs(); void saveAs();
void saveToClipboard(); void saveToClipboard();
@ -67,18 +70,20 @@ protected:
void toggleFullScreen(); void toggleFullScreen();
void updateStatus(); void updateStatus();
void updateLoadSaveMenus(); void updateLoadSaveMenus();
void createDockWidgets();
void eventsMerged();
VideoWidget *video_widget = nullptr; VideoWidget *video_widget = nullptr;
QDockWidget *video_dock; QDockWidget *video_dock;
MessagesWidget *messages_widget; QDockWidget *messages_dock;
MessagesWidget *messages_widget = nullptr;
CenterWidget *center_widget; CenterWidget *center_widget;
ChartsWidget *charts_widget;
QWidget *floating_window = nullptr; QWidget *floating_window = nullptr;
QVBoxLayout *charts_layout; QVBoxLayout *charts_layout;
QProgressBar *progress_bar; QProgressBar *progress_bar;
QLabel *status_label; QLabel *status_label;
QJsonDocument fingerprint_to_dbc; QJsonDocument fingerprint_to_dbc;
QSplitter *video_splitter;; QSplitter *video_splitter = nullptr;
enum { MAX_RECENT_FILES = 15 }; enum { MAX_RECENT_FILES = 15 };
QAction *recent_files_acts[MAX_RECENT_FILES] = {}; QAction *recent_files_acts[MAX_RECENT_FILES] = {};
QMenu *open_recent_menu = nullptr; QMenu *open_recent_menu = nullptr;

@ -81,7 +81,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
settings.suppress_defined_signals = (state == Qt::Checked); settings.suppress_defined_signals = (state == Qt::Checked);
}); });
QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(can, &AbstractStream::streamStarted, this, &MessagesWidget::reset);
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(dbc(), &DBCManager::msgUpdated, this, &MessagesWidget::dbcModified);
QObject::connect(dbc(), &DBCManager::msgRemoved, this, &MessagesWidget::dbcModified); QObject::connect(dbc(), &DBCManager::msgRemoved, this, &MessagesWidget::dbcModified);
@ -146,14 +145,6 @@ void MessagesWidget::updateSuppressedButtons() {
} }
} }
void MessagesWidget::reset() {
current_msg_id = std::nullopt;
view->selectionModel()->clear();
model->reset();
updateSuppressedButtons();
}
// MessageListModel // MessageListModel
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
@ -419,14 +410,6 @@ void MessageListModel::clearSuppress() {
suppressed_bytes.clear(); suppressed_bytes.clear();
} }
void MessageListModel::reset() {
beginResetModel();
filter_str.clear();
msgs.clear();
clearSuppress();
endResetModel();
}
void MessageListModel::forceResetModel() { void MessageListModel::forceResetModel() {
beginResetModel(); beginResetModel();
endResetModel(); endResetModel();

@ -38,7 +38,6 @@ public:
void fetchData(); void fetchData();
void suppress(); void suppress();
void clearSuppress(); void clearSuppress();
void reset();
void forceResetModel(); void forceResetModel();
void dbcModified(); void dbcModified();
std::vector<MessageId> msgs; std::vector<MessageId> msgs;
@ -100,7 +99,6 @@ public:
QByteArray saveHeaderState() const { return view->header()->saveState(); } QByteArray saveHeaderState() const { return view->header()->saveState(); }
bool restoreHeaderState(const QByteArray &state) const { return view->header()->restoreState(state); } bool restoreHeaderState(const QByteArray &state) const { return view->header()->restoreState(state); }
void updateSuppressedButtons(); void updateSuppressedButtons();
void reset();
public slots: public slots:
void dbcModified(); void dbcModified();

@ -481,10 +481,10 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged);
QObject::connect(dbc(), &DBCManager::signalAdded, [this](MessageId id, const cabana::Signal *sig) { selectSignal(sig); }); QObject::connect(dbc(), &DBCManager::signalAdded, [this](MessageId id, const cabana::Signal *sig) { selectSignal(sig); });
QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalView::updateState);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalView::handleSignalUpdated); QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalView::handleSignalUpdated);
QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); }); QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); });
QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); }); QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); });
QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalView::updateState);
setWhatsThis(tr(R"( setWhatsThis(tr(R"(
<b>Signal view</b><br /> <b>Signal view</b><br />

@ -4,10 +4,20 @@
AbstractStream *can = nullptr; AbstractStream *can = nullptr;
AbstractStream::AbstractStream(QObject *parent) : QObject(parent) { StreamNotifier *StreamNotifier::instance() {
can = this; static StreamNotifier notifier;
new_msgs = std::make_unique<QHash<MessageId, CanData>>(); return &notifier;
}
AbstractStream::AbstractStream(QObject *parent) : new_msgs(new QHash<MessageId, CanData>()), QObject(parent) {
assert(parent != nullptr);
QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo); QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo);
QObject::connect(this, &AbstractStream::streamStarted, [this]() {
emit StreamNotifier::instance()->changingStream();
delete can;
can = this;
emit StreamNotifier::instance()->streamStarted();
});
} }
void AbstractStream::updateMessages(QHash<MessageId, CanData> *messages) { void AbstractStream::updateMessages(QHash<MessageId, CanData> *messages) {
@ -37,8 +47,7 @@ void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t
bool AbstractStream::postEvents() { bool AbstractStream::postEvents() {
// delay posting CAN message if UI thread is busy // delay posting CAN message if UI thread is busy
if (!processing) { if (processing.exchange(true) == false) {
processing = true;
for (auto it = new_msgs->begin(); it != new_msgs->end(); ++it) { for (auto it = new_msgs->begin(); it != new_msgs->end(); ++it) {
it.value() = all_msgs[it.key()]; it.value() = all_msgs[it.key()];
} }

@ -40,6 +40,7 @@ class AbstractStream : public QObject {
public: public:
AbstractStream(QObject *parent); AbstractStream(QObject *parent);
virtual ~AbstractStream() {}; virtual ~AbstractStream() {};
virtual void start() = 0;
inline bool liveStreaming() const { return route() == nullptr; } inline bool liveStreaming() const { return route() == nullptr; }
virtual void seekTo(double ts) {} virtual void seekTo(double ts) {}
virtual QString routeName() const = 0; virtual QString routeName() const = 0;
@ -90,7 +91,6 @@ protected:
}; };
class AbstractOpenStreamWidget : public QWidget { class AbstractOpenStreamWidget : public QWidget {
Q_OBJECT
public: public:
AbstractOpenStreamWidget(AbstractStream **stream, QWidget *parent = nullptr) : stream(stream), QWidget(parent) {} AbstractOpenStreamWidget(AbstractStream **stream, QWidget *parent = nullptr) : stream(stream), QWidget(parent) {}
virtual bool open() = 0; virtual bool open() = 0;
@ -100,5 +100,15 @@ protected:
AbstractStream **stream = nullptr; AbstractStream **stream = nullptr;
}; };
class StreamNotifier : public QObject {
Q_OBJECT
public:
StreamNotifier(QObject *parent = nullptr) : QObject(parent) {}
static StreamNotifier* instance();
signals:
void streamStarted();
void changingStream();
};
// A global pointer referring to the unique AbstractStream object // A global pointer referring to the unique AbstractStream object
extern AbstractStream *can; extern AbstractStream *can;

@ -9,13 +9,10 @@
// DeviceStream // DeviceStream
DeviceStream::DeviceStream(QObject *parent, QString address) : zmq_address(address), LiveStream(parent) { DeviceStream::DeviceStream(QObject *parent, QString address) : zmq_address(address), LiveStream(parent) {
startStreamThread();
} }
void DeviceStream::streamThread() { void DeviceStream::streamThread() {
if (!zmq_address.isEmpty()) { zmq_address.isEmpty() ? unsetenv("ZMQ") : setenv("ZMQ", "1", 1);
setenv("ZMQ", "1", 1);
}
std::unique_ptr<Context> context(Context::create()); std::unique_ptr<Context> context(Context::create());
std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString();

@ -1,7 +1,5 @@
#include "tools/cabana/streams/livestream.h" #include "tools/cabana/streams/livestream.h"
#include <QTimer>
LiveStream::LiveStream(QObject *parent) : AbstractStream(parent) { LiveStream::LiveStream(QObject *parent) : AbstractStream(parent) {
if (settings.log_livestream) { if (settings.log_livestream) {
std::string path = (settings.log_path + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd--hh-mm-ss") + "--0").toStdString(); std::string path = (settings.log_path + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd--hh-mm-ss") + "--0").toStdString();
@ -21,11 +19,9 @@ void LiveStream::startUpdateTimer() {
timer_id = update_timer.timerId(); timer_id = update_timer.timerId();
} }
void LiveStream::startStreamThread() { void LiveStream::start() {
// delay the start of the thread to avoid calling startStreamThread emit streamStarted();
// in the constructor when other classes' slots have not been connected to stream_thread->start();
// the signals of the livestream.
QTimer::singleShot(0, [this]() { stream_thread->start(); });
startUpdateTimer(); startUpdateTimer();
} }
@ -71,7 +67,6 @@ void LiveStream::updateEvents() {
if (first_update_ts == 0) { if (first_update_ts == 0) {
first_update_ts = nanos_since_boot(); first_update_ts = nanos_since_boot();
first_event_ts = current_event_ts = all_events_.back()->mono_time; first_event_ts = current_event_ts = all_events_.back()->mono_time;
emit streamStarted();
} }
if (paused_ || prev_speed != speed_) { if (paused_ || prev_speed != speed_) {

@ -10,6 +10,7 @@ class LiveStream : public AbstractStream {
public: public:
LiveStream(QObject *parent); LiveStream(QObject *parent);
virtual ~LiveStream(); virtual ~LiveStream();
void start() override;
inline double routeStartTime() const override { return begin_event_ts / 1e9; } inline double routeStartTime() const override { return begin_event_ts / 1e9; }
inline double currentSec() const override { return (current_event_ts - begin_event_ts) / 1e9; } inline double currentSec() const override { return (current_event_ts - begin_event_ts) / 1e9; }
void setSpeed(float speed) override { speed_ = speed; } void setSpeed(float speed) override { speed_ = speed; }
@ -20,7 +21,6 @@ public:
protected: protected:
virtual void streamThread() = 0; virtual void streamThread() = 0;
void startStreamThread();
void handleEvent(const char *data, const size_t size); void handleEvent(const char *data, const size_t size);
private: private:

@ -20,7 +20,6 @@ PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(co
if (!connect()) { if (!connect()) {
throw std::runtime_error("Failed to connect to panda"); throw std::runtime_error("Failed to connect to panda");
} }
startStreamThread();
} }
bool PandaStream::connect() { bool PandaStream::connect() {

@ -6,18 +6,17 @@
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include "common/prefix.h"
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) { ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
unsetenv("ZMQ");
// TODO: Remove when OpenpilotPrefix supports ZMQ
#ifndef __APPLE__
op_prefix = std::make_unique<OpenpilotPrefix>();
#endif
QObject::connect(&settings, &Settings::changed, [this]() { QObject::connect(&settings, &Settings::changed, [this]() {
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes); if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);
}); });
} }
ReplayStream::~ReplayStream() {
if (replay) replay->stop();
}
static bool event_filter(const Event *e, void *opaque) { static bool event_filter(const Event *e, void *opaque) {
return ((ReplayStream *)opaque)->eventFilter(e); return ((ReplayStream *)opaque)->eventFilter(e);
} }
@ -37,13 +36,13 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint
replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->setSegmentCacheLimit(settings.max_cached_minutes);
replay->installEventFilter(event_filter, this); replay->installEventFilter(event_filter, this);
QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo); QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo);
QObject::connect(replay.get(), &Replay::streamStarted, this, &AbstractStream::streamStarted);
QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments); QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments);
if (replay->load()) { return replay->load();
replay->start();
return true;
} }
return false;
void ReplayStream::start() {
emit streamStarted();
replay->start();
} }
bool ReplayStream::eventFilter(const Event *event) { bool ReplayStream::eventFilter(const Event *event) {
@ -78,8 +77,6 @@ AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) {
// OpenReplayWidget // OpenReplayWidget
static std::unique_ptr<OpenpilotPrefix> op_prefix;
OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) { OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
// TODO: get route list from api.comma.ai // TODO: get route list from api.comma.ai
QGridLayout *grid_layout = new QGridLayout(); QGridLayout *grid_layout = new QGridLayout();
@ -118,26 +115,17 @@ bool OpenReplayWidget::open() {
route = route.mid(idx + 1); route = route.mid(idx + 1);
} }
bool ret = false;
bool is_valid_format = Route::parseRoute(route).str.size() > 0; bool is_valid_format = Route::parseRoute(route).str.size() > 0;
if (!is_valid_format) { if (!is_valid_format) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route)); QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
} else { } else {
// TODO: Remove when OpenpilotPrefix supports ZMQ
#ifndef __APPLE__
op_prefix.reset(new OpenpilotPrefix());
#endif
uint32_t flags[] = {REPLAY_FLAG_NO_VIPC, REPLAY_FLAG_NONE, REPLAY_FLAG_ECAM, REPLAY_FLAG_DCAM, REPLAY_FLAG_QCAMERA}; uint32_t flags[] = {REPLAY_FLAG_NO_VIPC, REPLAY_FLAG_NONE, REPLAY_FLAG_ECAM, REPLAY_FLAG_DCAM, REPLAY_FLAG_QCAMERA};
ReplayStream *replay_stream = *stream ? (ReplayStream *)*stream : new ReplayStream(qApp); auto replay_stream = std::make_unique<ReplayStream>(qApp);
ret = replay_stream->loadRoute(route, data_dir, flags[choose_video_cb->currentIndex()]); if (replay_stream->loadRoute(route, data_dir, flags[choose_video_cb->currentIndex()])) {
if (!ret) { *stream = replay_stream.release();
if (replay_stream != *stream) {
delete replay_stream;
}
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));
} else { } else {
*stream = replay_stream; QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));
} }
} }
return ret; return *stream != nullptr;
} }

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "common/prefix.h"
#include "tools/cabana/streams/abstractstream.h" #include "tools/cabana/streams/abstractstream.h"
class ReplayStream : public AbstractStream { class ReplayStream : public AbstractStream {
@ -7,7 +8,7 @@ class ReplayStream : public AbstractStream {
public: public:
ReplayStream(QObject *parent); ReplayStream(QObject *parent);
~ReplayStream(); void start() override;
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
bool eventFilter(const Event *event); bool eventFilter(const Event *event);
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }; void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); };
@ -29,6 +30,7 @@ private:
void mergeSegments(); void mergeSegments();
std::unique_ptr<Replay> replay = nullptr; std::unique_ptr<Replay> replay = nullptr;
std::set<int> processed_segments; std::set<int> processed_segments;
std::unique_ptr<OpenpilotPrefix> op_prefix;
}; };
class OpenReplayWidget : public AbstractOpenStreamWidget { class OpenReplayWidget : public AbstractOpenStreamWidget {

@ -6,13 +6,19 @@
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) { #include "tools/cabana/streams/devicestream.h"
#include "tools/cabana/streams/pandastream.h"
#include "tools/cabana/streams/replaystream.h"
StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Open stream")); setWindowTitle(tr("Open stream"));
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
QWidget *w = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(w);
tab = new QTabWidget(this); tab = new QTabWidget(this);
tab->setTabBarAutoHide(true); tab->setTabBarAutoHide(true);
main_layout->addWidget(tab); layout->addWidget(tab);
QHBoxLayout *dbc_layout = new QHBoxLayout(); QHBoxLayout *dbc_layout = new QHBoxLayout();
dbc_file = new QLineEdit(this); dbc_file = new QLineEdit(this);
@ -22,20 +28,29 @@ StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) {
dbc_layout->addWidget(new QLabel(tr("dbc File"))); dbc_layout->addWidget(new QLabel(tr("dbc File")));
dbc_layout->addWidget(dbc_file); dbc_layout->addWidget(dbc_file);
dbc_layout->addWidget(file_btn); dbc_layout->addWidget(file_btn);
main_layout->addLayout(dbc_layout); layout->addLayout(dbc_layout);
QFrame *line = new QFrame(this); QFrame *line = new QFrame(this);
line->setFrameStyle(QFrame::HLine | QFrame::Sunken); line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
main_layout->addWidget(line); layout->addWidget(line);
main_layout->addWidget(w);
auto btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel); auto btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
main_layout->addWidget(btn_box); main_layout->addWidget(btn_box);
addStreamWidget(ReplayStream::widget(stream));
addStreamWidget(PandaStream::widget(stream));
addStreamWidget(DeviceStream::widget(stream));
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() { QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() {
success = ((AbstractOpenStreamWidget *)tab->currentWidget())->open(); btn_box->button(QDialogButtonBox::Open)->setEnabled(false);
if (success) { w->setEnabled(false);
if (((AbstractOpenStreamWidget *)tab->currentWidget())->open()) {
accept(); accept();
} else {
btn_box->button(QDialogButtonBox::Open)->setEnabled(true);
w->setEnabled(true);
} }
}); });
QObject::connect(file_btn, &QPushButton::clicked, [this]() { QObject::connect(file_btn, &QPushButton::clicked, [this]() {

@ -10,13 +10,11 @@ class StreamSelector : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
StreamSelector(QWidget *parent = nullptr); StreamSelector(AbstractStream **stream, QWidget *parent = nullptr);
void addStreamWidget(AbstractOpenStreamWidget *w); void addStreamWidget(AbstractOpenStreamWidget *w);
QString dbcFile() const { return dbc_file->text(); } QString dbcFile() const { return dbc_file->text(); }
inline bool failed() const { return !success; }
private: private:
QLineEdit *dbc_file; QLineEdit *dbc_file;
QTabWidget *tab; QTabWidget *tab;
bool success = true;
}; };

@ -1,10 +1,12 @@
#include "tools/cabana/videowidget.h" #include "tools/cabana/videowidget.h"
#include <QButtonGroup> #include <QButtonGroup>
#include <QHBoxLayout>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
#include <QStackedLayout> #include <QStackedLayout>
#include <QStyleOptionSlider> #include <QStyleOptionSlider>
#include <QTimer>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QtConcurrent> #include <QtConcurrent>
@ -20,18 +22,6 @@ static const QColor timeline_colors[] = {
[(int)TimelineType::AlertCritical] = QColor(199, 0, 57), [(int)TimelineType::AlertCritical] = QColor(199, 0, 57),
}; };
bool sortTimelineBasedOnEventPriority(const std::tuple<int, int, TimelineType> &left, const std::tuple<int, int, TimelineType> &right){
const static std::map<TimelineType, int> timelinePriority = {
{ TimelineType::None, 0 },
{ TimelineType::Engaged, 10 },
{ TimelineType::AlertInfo, 20 },
{ TimelineType::AlertWarning, 30 },
{ TimelineType::AlertCritical, 40 },
{ TimelineType::UserFlag, 35 }
};
return timelinePriority.at(std::get<2>(left)) < timelinePriority.at(std::get<2>(right));
}
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain); setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
auto main_layout = new QVBoxLayout(this); auto main_layout = new QVBoxLayout(this);
@ -100,7 +90,7 @@ QWidget *VideoWidget::createCameraWidget() {
l->addLayout(stacked); l->addLayout(stacked);
// slider controls // slider controls
slider_layout = new QHBoxLayout(); auto slider_layout = new QHBoxLayout();
time_label = new QLabel("00:00"); time_label = new QLabel("00:00");
slider_layout->addWidget(time_label); slider_layout->addWidget(time_label);
@ -111,12 +101,13 @@ QWidget *VideoWidget::createCameraWidget() {
end_time_label = new QLabel(this); end_time_label = new QLabel(this);
slider_layout->addWidget(end_time_label); slider_layout->addWidget(end_time_label);
l->addLayout(slider_layout); l->addLayout(slider_layout);
setMaximumTime(can->totalSeconds());
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); });
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(value / 1000)); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(value / 1000)); });
QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime); QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime);
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
QObject::connect(can, &AbstractStream::streamStarted, [this]() { setMaximumTime(can->totalSeconds()); });
return w; return w;
} }
@ -157,14 +148,16 @@ void VideoWidget::updatePlayBtnState() {
} }
// Slider // Slider
Slider::Slider(QWidget *parent) : timer(this), thumbnail_label(parent), QSlider(Qt::Horizontal, parent) { Slider::Slider(QWidget *parent) : thumbnail_label(parent), QSlider(Qt::Horizontal, parent) {
timer.callOnTimeout([this]() { setMouseTracking(true);
auto timer = new QTimer(this);
timer->callOnTimeout([this]() {
timeline = can->getTimeline(); timeline = can->getTimeline();
std::sort(timeline.begin(), timeline.end(), sortTimelineBasedOnEventPriority); std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); });
update(); update();
}); });
setMouseTracking(true); timer->start(2000);
QObject::connect(can, &AbstractStream::streamStarted, this, &Slider::streamStarted); thumnail_future = QtConcurrent::run(this, &Slider::loadThumbnails);
} }
Slider::~Slider() { Slider::~Slider() {
@ -172,16 +165,6 @@ Slider::~Slider() {
thumnail_future.waitForFinished(); thumnail_future.waitForFinished();
} }
void Slider::streamStarted() {
abort_load_thumbnail = true;
thumnail_future.waitForFinished();
abort_load_thumbnail = false;
thumbnails.clear();
timeline.clear();
timer.start(2000);
thumnail_future = QtConcurrent::run(this, &Slider::loadThumbnails);
}
void Slider::loadThumbnails() { void Slider::loadThumbnails() {
const auto &segments = can->route()->segments(); const auto &segments = can->route()->segments();
double max_time = 0; double max_time = 0;
@ -318,10 +301,8 @@ void InfoLabel::showAlert(const AlertInfo &alert) {
alert_info = alert; alert_info = alert;
pixmap = {}; pixmap = {};
setVisible(!alert_info.text1.isEmpty()); setVisible(!alert_info.text1.isEmpty());
if (isVisible()) {
update(); update();
} }
}
void InfoLabel::paintEvent(QPaintEvent *event) { void InfoLabel::paintEvent(QPaintEvent *event) {
QPainter p(this); QPainter p(this);

@ -3,12 +3,10 @@
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <QHBoxLayout>
#include <QFuture> #include <QFuture>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QSlider> #include <QSlider>
#include <QTimer>
#include "selfdrive/ui/qt/widgets/cameraview.h" #include "selfdrive/ui/qt/widgets/cameraview.h"
#include "tools/cabana/streams/abstractstream.h" #include "tools/cabana/streams/abstractstream.h"
@ -46,7 +44,6 @@ private:
bool event(QEvent *event) override; bool event(QEvent *event) override;
void sliderChange(QAbstractSlider::SliderChange change) override; void sliderChange(QAbstractSlider::SliderChange change) override;
void paintEvent(QPaintEvent *ev) override; void paintEvent(QPaintEvent *ev) override;
void streamStarted();
void loadThumbnails(); void loadThumbnails();
double max_sec = 0; double max_sec = 0;
@ -58,7 +55,6 @@ private:
std::map<uint64_t, AlertInfo> alerts; std::map<uint64_t, AlertInfo> alerts;
QFuture<void> thumnail_future; QFuture<void> thumnail_future;
InfoLabel thumbnail_label; InfoLabel thumbnail_label;
QTimer timer;
friend class VideoWidget; friend class VideoWidget;
}; };
@ -79,7 +75,6 @@ protected:
double maximum_time = 0; double maximum_time = 0;
QLabel *end_time_label; QLabel *end_time_label;
QLabel *time_label; QLabel *time_label;
QHBoxLayout *slider_layout;
QPushButton *play_btn; QPushButton *play_btn;
InfoLabel *alert_label; InfoLabel *alert_label;
Slider *slider; Slider *slider;

Loading…
Cancel
Save