mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-22 00:33:44 +08:00
cabana: startup stream chooser dialog (#27938)
* new StreamDialog * choose dbc file * update last_dir * move to /streams * cleanup * add stretch * catch panda exception * cleanup * cleanup * small cleanup * fix pandaStream crash caused by a failed connection * static function to create stream widget * cleanup old-commit-hash: 590b1bc2062d8a7f1ec253e0d0c5ab94799524c1
This commit is contained in:
@@ -32,7 +32,7 @@ cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
|
||||
cabana_lib = cabana_env.Library("cabana_lib", ['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',
|
||||
'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
|
||||
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
|
||||
'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
|
||||
if GetOption('test'):
|
||||
|
||||
+23
-12
@@ -4,7 +4,7 @@
|
||||
#include "common/prefix.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "tools/cabana/mainwin.h"
|
||||
#include "tools/cabana/route.h"
|
||||
#include "tools/cabana/streamselector.h"
|
||||
#include "tools/cabana/streams/devicestream.h"
|
||||
#include "tools/cabana/streams/pandastream.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
@@ -33,6 +33,8 @@ int main(int argc, char *argv[]) {
|
||||
cmd_parser.addOption({"dbc", "dbc file to open", "dbc"});
|
||||
cmd_parser.process(app);
|
||||
|
||||
QString dbc_file = cmd_parser.isSet("dbc") ? cmd_parser.value("dbc") : "";
|
||||
|
||||
std::unique_ptr<OpenpilotPrefix> op_prefix;
|
||||
std::unique_ptr<AbstractStream> stream;
|
||||
|
||||
@@ -45,10 +47,6 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
stream.reset(new PandaStream(&app, config));
|
||||
} else {
|
||||
// TODO: Remove when OpenpilotPrefix supports ZMQ
|
||||
#ifndef __APPLE__
|
||||
op_prefix.reset(new OpenpilotPrefix());
|
||||
#endif
|
||||
uint32_t replay_flags = REPLAY_FLAG_NONE;
|
||||
if (cmd_parser.isSet("ecam")) {
|
||||
replay_flags |= REPLAY_FLAG_ECAM;
|
||||
@@ -66,22 +64,35 @@ int main(int argc, char *argv[]) {
|
||||
route = DEMO_ROUTE;
|
||||
}
|
||||
|
||||
auto replay_stream = new ReplayStream(&app);
|
||||
stream.reset(replay_stream);
|
||||
if (route.isEmpty()) {
|
||||
if (OpenRouteDialog dlg(nullptr); !dlg.exec()) {
|
||||
AbstractStream *out_stream = nullptr;
|
||||
StreamSelector dlg;
|
||||
dlg.addStreamWidget(ReplayStream::widget(&out_stream));
|
||||
dlg.addStreamWidget(PandaStream::widget(&out_stream));
|
||||
dlg.addStreamWidget(DeviceStream::widget(&out_stream));
|
||||
if (!dlg.exec()) {
|
||||
return 0;
|
||||
}
|
||||
dbc_file = dlg.dbcFile();
|
||||
stream.reset(out_stream);
|
||||
} else {
|
||||
// TODO: Remove when OpenpilotPrefix supports ZMQ
|
||||
#ifndef __APPLE__
|
||||
op_prefix.reset(new OpenpilotPrefix());
|
||||
#endif
|
||||
auto replay_stream = new ReplayStream(&app);
|
||||
stream.reset(replay_stream);
|
||||
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
|
||||
return 0;
|
||||
}
|
||||
} else if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow w;
|
||||
|
||||
// Load DBC
|
||||
if (cmd_parser.isSet("dbc")) {
|
||||
w.loadFile(cmd_parser.value("dbc"));
|
||||
if (!dbc_file.isEmpty()) {
|
||||
w.loadFile(dbc_file);
|
||||
}
|
||||
|
||||
w.show();
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
#include <QWidgetAction>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/route.h"
|
||||
#include "tools/cabana/streamselector.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
static MainWindow *main_win = nullptr;
|
||||
void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
@@ -248,12 +249,13 @@ void MainWindow::DBCFileChanged() {
|
||||
}
|
||||
|
||||
void MainWindow::openRoute() {
|
||||
OpenRouteDialog dlg(this);
|
||||
StreamSelector dlg(this);
|
||||
dlg.addStreamWidget(ReplayStream::widget(&can));
|
||||
if (dlg.exec()) {
|
||||
center_widget->clear();
|
||||
charts_widget->removeAll();
|
||||
statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000);
|
||||
} else if (dlg.failedToLoad()) {
|
||||
} else if (dlg.failed()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#include "tools/cabana/route.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
OpenRouteDialog::OpenRouteDialog(QWidget *parent) : QDialog(parent) {
|
||||
// TODO: get route list from api.comma.ai
|
||||
QGridLayout *grid_layout = new QGridLayout();
|
||||
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
|
||||
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
|
||||
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
|
||||
auto file_btn = new QPushButton(tr("Browse..."), this);
|
||||
grid_layout->addWidget(file_btn, 0, 2);
|
||||
|
||||
grid_layout->addWidget(new QLabel(tr("Video")), 1, 0);
|
||||
grid_layout->addWidget(choose_video_cb = new QComboBox(this), 1, 1);
|
||||
QString items[] = {tr("No Video"), tr("Road Camera"), tr("Wide Road Camera"), tr("Driver Camera"), tr("QCamera")};
|
||||
for (int i = 0; i < std::size(items); ++i) {
|
||||
choose_video_cb->addItem(items[i]);
|
||||
}
|
||||
choose_video_cb->setCurrentIndex(1); // default is road camera;
|
||||
|
||||
btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
|
||||
btn_box->button(QDialogButtonBox::Open)->setEnabled(false);
|
||||
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addLayout(grid_layout);
|
||||
main_layout->addWidget(btn_box);
|
||||
main_layout->addStretch(0);
|
||||
setMinimumSize({550, 120});
|
||||
|
||||
QObject::connect(btn_box, &QDialogButtonBox::accepted, this, &OpenRouteDialog::loadRoute);
|
||||
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
QObject::connect(route_edit, &QLineEdit::textChanged, [this]() {
|
||||
btn_box->button(QDialogButtonBox::Open)->setEnabled(!route_edit->text().isEmpty());
|
||||
});
|
||||
QObject::connect(file_btn, &QPushButton::clicked, [=]() {
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir);
|
||||
if (!dir.isEmpty()) {
|
||||
route_edit->setText(dir);
|
||||
settings.last_route_dir = QFileInfo(dir).absolutePath();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OpenRouteDialog::loadRoute() {
|
||||
btn_box->setEnabled(false);
|
||||
|
||||
QString route = route_edit->text();
|
||||
QString data_dir;
|
||||
if (int idx = route.lastIndexOf('/'); idx != -1) {
|
||||
data_dir = route.mid(0, idx + 1);
|
||||
route = route.mid(idx + 1);
|
||||
}
|
||||
|
||||
bool is_valid_format = Route::parseRoute(route).str.size() > 0;
|
||||
if (!is_valid_format) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
|
||||
} else {
|
||||
uint32_t flags[] = {REPLAY_FLAG_NO_VIPC, REPLAY_FLAG_NONE, REPLAY_FLAG_ECAM, REPLAY_FLAG_DCAM, REPLAY_FLAG_QCAMERA};
|
||||
failed_to_load = !dynamic_cast<ReplayStream *>(can)->loadRoute(route, data_dir, flags[choose_video_cb->currentIndex()]);
|
||||
if (failed_to_load) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));
|
||||
} else {
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
btn_box->setEnabled(true);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLineEdit>
|
||||
#include <QDialog>
|
||||
|
||||
class OpenRouteDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenRouteDialog(QWidget *parent);
|
||||
void loadRoute();
|
||||
inline bool failedToLoad() const { return failed_to_load; }
|
||||
|
||||
private:
|
||||
QLineEdit *route_edit;
|
||||
QComboBox *choose_video_cb;
|
||||
QDialogButtonBox *btn_box;
|
||||
bool failed_to_load = false;
|
||||
};
|
||||
@@ -89,5 +89,16 @@ protected:
|
||||
std::deque<std::unique_ptr<char[]>> memory_blocks;
|
||||
};
|
||||
|
||||
class AbstractOpenStreamWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AbstractOpenStreamWidget(AbstractStream **stream, QWidget *parent = nullptr) : stream(stream), QWidget(parent) {}
|
||||
virtual bool open() = 0;
|
||||
virtual QString title() = 0;
|
||||
|
||||
protected:
|
||||
AbstractStream **stream = nullptr;
|
||||
};
|
||||
|
||||
// A global pointer referring to the unique AbstractStream object
|
||||
extern AbstractStream *can;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
#include "tools/cabana/streams/devicestream.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QFormLayout>
|
||||
#include <QRadioButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionValidator>
|
||||
|
||||
// DeviceStream
|
||||
|
||||
DeviceStream::DeviceStream(QObject *parent, QString address) : zmq_address(address), LiveStream(parent) {
|
||||
startStreamThread();
|
||||
}
|
||||
@@ -26,3 +34,39 @@ void DeviceStream::streamThread() {
|
||||
handleEvent(messages.emplace_back(msg).event);
|
||||
}
|
||||
}
|
||||
|
||||
AbstractOpenStreamWidget *DeviceStream::widget(AbstractStream **stream) {
|
||||
return new OpenDeviceWidget(stream);
|
||||
}
|
||||
|
||||
// OpenDeviceWidget
|
||||
|
||||
OpenDeviceWidget::OpenDeviceWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
QRadioButton *msgq = new QRadioButton(tr("MSGQ"));
|
||||
QRadioButton *zmq = new QRadioButton(tr("ZMQ"));
|
||||
ip_address = new QLineEdit(this);
|
||||
ip_address->setPlaceholderText(tr("Enter device Ip Address"));
|
||||
QString ip_range = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])";
|
||||
QString pattern("^" + ip_range + "\\." + ip_range + "\\." + ip_range + "\\." + ip_range + "$");
|
||||
QRegularExpression re(pattern);
|
||||
ip_address->setValidator(new QRegularExpressionValidator(re, this));
|
||||
|
||||
group = new QButtonGroup(this);
|
||||
group->addButton(msgq, 0);
|
||||
group->addButton(zmq, 1);
|
||||
|
||||
QFormLayout *form_layout = new QFormLayout(this);
|
||||
form_layout->addRow(msgq);
|
||||
form_layout->addRow(zmq, ip_address);
|
||||
QObject::connect(group, qOverload<QAbstractButton *, bool>(&QButtonGroup::buttonToggled), [=](QAbstractButton *button, bool checked) {
|
||||
ip_address->setEnabled(button == zmq && checked);
|
||||
});
|
||||
zmq->setChecked(true);
|
||||
}
|
||||
|
||||
bool OpenDeviceWidget::open() {
|
||||
QString ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text();
|
||||
bool msgq = group->checkedId() == 0;
|
||||
*stream = new DeviceStream(qApp, msgq ? "" : ip);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ class DeviceStream : public LiveStream {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DeviceStream(QObject *parent, QString address = {});
|
||||
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
inline QString routeName() const override {
|
||||
return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address);
|
||||
}
|
||||
@@ -15,3 +15,16 @@ protected:
|
||||
void streamThread() override;
|
||||
const QString zmq_address;
|
||||
};
|
||||
|
||||
class OpenDeviceWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenDeviceWidget(AbstractStream **stream);
|
||||
bool open() override;
|
||||
QString title() override { return tr("&Device"); }
|
||||
|
||||
private:
|
||||
QLineEdit *ip_address;
|
||||
QButtonGroup *group;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "tools/cabana/streams/pandastream.h"
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(config_), LiveStream(parent) {
|
||||
if (config.serial.isEmpty()) {
|
||||
auto serials = Panda::list();
|
||||
@@ -84,3 +88,32 @@ void PandaStream::streamThread() {
|
||||
panda->send_heartbeat(false);
|
||||
}
|
||||
}
|
||||
|
||||
AbstractOpenStreamWidget *PandaStream::widget(AbstractStream **stream) {
|
||||
return new OpenPandaWidget(stream);
|
||||
}
|
||||
|
||||
// OpenPandaWidget
|
||||
|
||||
OpenPandaWidget::OpenPandaWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addStretch(1);
|
||||
|
||||
QFormLayout *form_layout = new QFormLayout();
|
||||
form_layout->addRow(tr("Serial"), serial_edit = new QLineEdit(this));
|
||||
serial_edit->setPlaceholderText(tr("Leave empty to use default serial"));
|
||||
|
||||
main_layout->addLayout(form_layout);
|
||||
main_layout->addStretch(1);
|
||||
}
|
||||
|
||||
bool OpenPandaWidget::open() {
|
||||
try {
|
||||
PandaStreamConfig config = {.serial = serial_edit->text()};
|
||||
*stream = new PandaStream(qApp, config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class PandaStream : public LiveStream {
|
||||
Q_OBJECT
|
||||
public:
|
||||
PandaStream(QObject *parent, PandaStreamConfig config_ = {});
|
||||
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
inline QString routeName() const override {
|
||||
return QString("Live Streaming From Panda %1").arg(config.serial);
|
||||
}
|
||||
@@ -31,3 +31,14 @@ protected:
|
||||
PandaStreamConfig config = {};
|
||||
};
|
||||
|
||||
class OpenPandaWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenPandaWidget(AbstractStream **stream);
|
||||
bool open() override;
|
||||
QString title() override { return tr("&Panda"); }
|
||||
|
||||
private:
|
||||
QLineEdit *serial_edit;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "common/prefix.h"
|
||||
|
||||
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent, false) {
|
||||
QObject::connect(&settings, &Settings::changed, [this]() {
|
||||
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);
|
||||
@@ -50,3 +58,74 @@ void ReplayStream::pause(bool pause) {
|
||||
replay->pause(pause);
|
||||
emit(pause ? paused() : resume());
|
||||
}
|
||||
|
||||
|
||||
AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) {
|
||||
return new OpenReplayWidget(stream);
|
||||
}
|
||||
|
||||
// OpenReplayWidget
|
||||
|
||||
static std::unique_ptr<OpenpilotPrefix> op_prefix;
|
||||
|
||||
OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
// TODO: get route list from api.comma.ai
|
||||
QGridLayout *grid_layout = new QGridLayout();
|
||||
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
|
||||
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
|
||||
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
|
||||
auto file_btn = new QPushButton(tr("Browse..."), this);
|
||||
grid_layout->addWidget(file_btn, 0, 2);
|
||||
|
||||
grid_layout->addWidget(new QLabel(tr("Video")), 1, 0);
|
||||
grid_layout->addWidget(choose_video_cb = new QComboBox(this), 1, 1);
|
||||
QString items[] = {tr("No Video"), tr("Road Camera"), tr("Wide Road Camera"), tr("Driver Camera"), tr("QCamera")};
|
||||
for (int i = 0; i < std::size(items); ++i) {
|
||||
choose_video_cb->addItem(items[i]);
|
||||
}
|
||||
choose_video_cb->setCurrentIndex(1); // default is road camera;
|
||||
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addLayout(grid_layout);
|
||||
setMinimumWidth(550);
|
||||
|
||||
QObject::connect(file_btn, &QPushButton::clicked, [=]() {
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir);
|
||||
if (!dir.isEmpty()) {
|
||||
route_edit->setText(dir);
|
||||
settings.last_route_dir = QFileInfo(dir).absolutePath();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool OpenReplayWidget::open() {
|
||||
QString route = route_edit->text();
|
||||
QString data_dir;
|
||||
if (int idx = route.lastIndexOf('/'); idx != -1) {
|
||||
data_dir = route.mid(0, idx + 1);
|
||||
route = route.mid(idx + 1);
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
bool is_valid_format = Route::parseRoute(route).str.size() > 0;
|
||||
if (!is_valid_format) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
|
||||
} 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};
|
||||
ReplayStream *replay_stream = *stream ? (ReplayStream *)*stream : new ReplayStream(qApp);
|
||||
ret = replay_stream->loadRoute(route, data_dir, flags[choose_video_cb->currentIndex()]);
|
||||
if (!ret) {
|
||||
if (replay_stream != *stream) {
|
||||
delete replay_stream;
|
||||
}
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));
|
||||
} else {
|
||||
*stream = replay_stream;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,23 @@ public:
|
||||
void pause(bool pause) override;
|
||||
const std::vector<Event*> *rawEvents() const override { return replay->events(); }
|
||||
inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() override { return replay->getTimeline(); }
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
|
||||
private:
|
||||
void mergeSegments();
|
||||
std::unique_ptr<Replay> replay = nullptr;
|
||||
std::set<int> processed_segments;
|
||||
};
|
||||
|
||||
class OpenReplayWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenReplayWidget(AbstractStream **stream);
|
||||
bool open() override;
|
||||
QString title() override { return tr("&Replay"); }
|
||||
|
||||
private:
|
||||
QLineEdit *route_edit;
|
||||
QComboBox *choose_video_cb;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "tools/cabana/streamselector.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Open stream"));
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
|
||||
tab = new QTabWidget(this);
|
||||
tab->setTabBarAutoHide(true);
|
||||
main_layout->addWidget(tab);
|
||||
|
||||
QHBoxLayout *dbc_layout = new QHBoxLayout();
|
||||
dbc_file = new QLineEdit(this);
|
||||
dbc_file->setReadOnly(true);
|
||||
dbc_file->setPlaceholderText(tr("Choose a dbc file to open"));
|
||||
QPushButton *file_btn = new QPushButton(tr("Browse..."));
|
||||
dbc_layout->addWidget(new QLabel(tr("dbc File")));
|
||||
dbc_layout->addWidget(dbc_file);
|
||||
dbc_layout->addWidget(file_btn);
|
||||
main_layout->addLayout(dbc_layout);
|
||||
|
||||
QFrame *line = new QFrame(this);
|
||||
line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
|
||||
main_layout->addWidget(line);
|
||||
|
||||
auto btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
|
||||
main_layout->addWidget(btn_box);
|
||||
|
||||
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() {
|
||||
success = ((AbstractOpenStreamWidget *)tab->currentWidget())->open();
|
||||
if (success) {
|
||||
accept();
|
||||
}
|
||||
});
|
||||
QObject::connect(file_btn, &QPushButton::clicked, [this]() {
|
||||
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
|
||||
if (!fn.isEmpty()) {
|
||||
dbc_file->setText(fn);
|
||||
settings.last_dir = QFileInfo(fn).absolutePath();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StreamSelector::addStreamWidget(AbstractOpenStreamWidget *w) {
|
||||
tab->addTab(w, w->title());
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QTabWidget>
|
||||
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
class StreamSelector : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StreamSelector(QWidget *parent = nullptr);
|
||||
void addStreamWidget(AbstractOpenStreamWidget *w);
|
||||
QString dbcFile() const { return dbc_file->text(); }
|
||||
inline bool failed() const { return !success; }
|
||||
|
||||
private:
|
||||
QLineEdit *dbc_file;
|
||||
QTabWidget *tab;
|
||||
bool success = true;
|
||||
};
|
||||
Reference in New Issue
Block a user