diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index da38054cb0..7ba2e87110 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -484,7 +484,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { if (rubber->width() <= 0) { // no rubber dragged, seek to mouse position can->seekTo(min); - } else if (rubber->width() > 10) { + } else if (rubber->width() > 10 && (max - min) > 0.01) { // Minimum range is 10 milliseconds. charts_widget->zoom_undo_stack->push(new ZoomCommand(charts_widget, {min, max})); } else { viewport()->update(); diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 87066bf9e6..fa91095706 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -190,7 +190,7 @@ void MainWindow::createDockWidgets() { video_splitter = new QSplitter(Qt::Vertical, this); video_widget = new VideoWidget(this); video_splitter->addWidget(video_widget); - QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged); + QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::updateTimeRange); video_splitter->addWidget(charts_container); video_splitter->setStretchFactor(1, 1); diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 9a3ab73183..fb42a58e24 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -57,7 +57,7 @@ public: virtual void pause(bool pause) {} const std::vector &allEvents() const { return all_events_; } const std::vector &events(const MessageId &id) const; - virtual const std::vector> getTimeline() { return {}; } + virtual const std::vector> getTimeline() { return {}; } signals: void paused(); diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index 5087de0329..eddcf514f7 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -23,7 +23,7 @@ public: inline float getSpeed() const { return replay->getSpeed(); } inline bool isPaused() const override { return replay->isPaused(); } void pause(bool pause) override; - inline const std::vector> getTimeline() override { return replay->getTimeline(); } + inline const std::vector> getTimeline() override { return replay->getTimeline(); } static AbstractOpenStreamWidget *widget(AbstractStream **stream); private: diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 2af1db4635..46642e5244 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -40,7 +40,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { for (float speed : {0.1, 0.5, 1., 2.}) { QPushButton *btn = new QPushButton(QString("%1x").arg(speed), this); btn->setCheckable(true); - QObject::connect(btn, &QPushButton::clicked, [=]() { can->setSpeed(speed); }); + QObject::connect(btn, &QPushButton::clicked, [speed]() { can->setSpeed(speed); }); control_layout->addWidget(btn); group->addButton(btn); if (speed == 1.0) btn->setChecked(true); @@ -91,21 +91,19 @@ QWidget *VideoWidget::createCameraWidget() { // slider controls auto slider_layout = new QHBoxLayout(); - time_label = new QLabel("00:00"); - slider_layout->addWidget(time_label); + slider_layout->addWidget(time_label = new QLabel("00:00")); slider = new Slider(this); slider->setSingleStep(0); slider_layout->addWidget(slider); - end_time_label = new QLabel(this); - slider_layout->addWidget(end_time_label); + slider_layout->addWidget(end_time_label = new QLabel(this)); l->addLayout(slider_layout); setMaximumTime(can->totalSeconds()); - 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, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime); + QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); }); + QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(slider->currentSecond())); }); + QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); return w; @@ -114,10 +112,10 @@ QWidget *VideoWidget::createCameraWidget() { void VideoWidget::setMaximumTime(double sec) { maximum_time = sec; end_time_label->setText(utils::formatSeconds(sec)); - slider->setRange(0, sec * 1000); + slider->setTimeRange(0, sec); } -void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { +void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) { if (can->liveStreaming()) return; if (!is_zoomed) { @@ -125,21 +123,14 @@ void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { max = maximum_time; } end_time_label->setText(utils::formatSeconds(max)); - slider->setRange(min * 1000, max * 1000); + slider->setTimeRange(min, max); } void VideoWidget::updateState() { if (!slider->isSliderDown()) { - slider->setValue(can->currentSec() * 1000); - } - std::lock_guard lk(slider->thumbnail_lock); - uint64_t mono_time = (can->currentSec() + can->routeStartTime()) * 1e9; - auto it = slider->alerts.lower_bound(mono_time); - if (it != slider->alerts.end() && (it->first - mono_time) < 1e9) { - alert_label->showAlert(it->second); - } else { - alert_label->showAlert({}); + slider->setCurrentSecond(can->currentSec()); } + alert_label->showAlert(slider->alertInfo(can->currentSec())); } void VideoWidget::updatePlayBtnState() { @@ -148,6 +139,7 @@ void VideoWidget::updatePlayBtnState() { } // Slider + Slider::Slider(QWidget *parent) : thumbnail_label(parent), QSlider(Qt::Horizontal, parent) { setMouseTracking(true); auto timer = new QTimer(this); @@ -171,6 +163,26 @@ Slider::~Slider() { } } +AlertInfo Slider::alertInfo(double seconds) { + std::lock_guard lk(thumbnail_lock); + uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9; + auto alert_it = alerts.lower_bound(mono_time); + bool has_alert = (alert_it != alerts.end()) && ((alert_it->first - mono_time) <= 1e8); + return has_alert ? alert_it->second : AlertInfo{}; +} + +QPixmap Slider::thumbnail(double seconds) { + std::lock_guard lk(thumbnail_lock); + uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9; + auto it = thumbnails.lowerBound(mono_time); + return it != thumbnails.end() ? it.value() : QPixmap(); +} + +void Slider::setTimeRange(double min, double max) { + assert(min < max); + setRange(min * factor, max * factor); +} + void Slider::parseQLog() { const auto &segments = can->route()->segments(); for (auto it = segments.rbegin(); it != segments.rend() && !abort_parse_qlog; ++it) { @@ -178,7 +190,7 @@ void Slider::parseQLog() { std::string qlog = it->second.qlog.toStdString(); if (!qlog.empty() && log.load(qlog, &abort_parse_qlog, {cereal::Event::Which::THUMBNAIL, cereal::Event::Which::CONTROLS_STATE}, true, 0, 3)) { if (it == segments.rbegin() && !log.events.empty()) { - double max_time = (*(log.events.rbegin()))->mono_time / 1e9 - can->routeStartTime(); + double max_time = log.events.back()->mono_time / 1e9 - can->routeStartTime(); emit updateMaximumTime(max_time); } for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_parse_qlog; ++ev) { @@ -192,7 +204,8 @@ void Slider::parseQLog() { } } else if ((*ev)->which == cereal::Event::Which::CONTROLS_STATE) { auto cs = (*ev)->event.getControlsState(); - if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0) { + if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 && + cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) { std::lock_guard lk(thumbnail_lock); alerts.emplace((*ev)->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()}); } @@ -202,30 +215,18 @@ void Slider::parseQLog() { } } -void Slider::sliderChange(QAbstractSlider::SliderChange change) { - if (change == QAbstractSlider::SliderValueChange) { - int x = width() * ((value() - minimum()) / double(maximum() - minimum())); - if (x != slider_x) { - slider_x = x; - update(); - } - } else { - QAbstractSlider::sliderChange(change); - } -} - void Slider::paintEvent(QPaintEvent *ev) { QPainter p(this); QRect r = rect().adjusted(0, 4, 0, -4); p.fillRect(r, timeline_colors[(int)TimelineType::None]); - double min = minimum() / 1000.0; - double max = maximum() / 1000.0; + double min = minimum() / factor; + double max = maximum() / factor; for (auto [begin, end, type] : timeline) { if (begin > max || end < min) continue; - r.setLeft(((std::max(min, (double)begin) - min) / (max - min)) * width()); - r.setRight(((std::min(max, (double)end) - min) / (max - min)) * width()); + r.setLeft(((std::max(min, begin) - min) / (max - min)) * width()); + r.setRight(((std::min(max, end) - min) / (max - min)) * width()); p.fillRect(r, timeline_colors[(int)type]); } @@ -248,23 +249,16 @@ void Slider::mousePressEvent(QMouseEvent *e) { } void Slider::mouseMoveEvent(QMouseEvent *e) { - QPixmap thumb; - AlertInfo alert; int pos = std::clamp(e->pos().x(), 0, width()); - double seconds = (minimum() + pos * ((maximum() - minimum()) / (double)width())) / 1000.0; - { - std::lock_guard lk(thumbnail_lock); - uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9; - auto it = thumbnails.lowerBound(mono_time); - if (it != thumbnails.end()) thumb = it.value(); - auto alert_it = alerts.lower_bound(mono_time); - if (alert_it != alerts.end() && (alert_it->first - mono_time) < 1e9) { - alert = alert_it->second; - } + double seconds = (minimum() + pos * ((maximum() - minimum()) / (double)width())) / factor; + QPixmap thumb = thumbnail(seconds); + if (!thumb.isNull()) { + int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN); + int y = -thumb.height(); + thumbnail_label.showPixmap(mapToParent({x, y}), utils::formatSeconds(seconds), thumb, alertInfo(seconds)); + } else { + thumbnail_label.hide(); } - int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN); - int y = -thumb.height(); - thumbnail_label.showPixmap(mapToParent({x, y}), utils::formatSeconds(seconds), thumb, alert); QSlider::mouseMoveEvent(e); } @@ -291,15 +285,13 @@ InfoLabel::InfoLabel(QWidget *parent) : QWidget(parent, Qt::WindowStaysOnTopHint } void InfoLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm, const AlertInfo &alert) { - pixmap = pm; second = sec; + pixmap = pm; alert_info = alert; - setVisible(!pm.isNull()); - if (isVisible()) { - resize(pm.size()); - move(pt); - update(); - } + resize(pm.size()); + move(pt); + setVisible(true); + update(); } void InfoLabel::showAlert(const AlertInfo &alert) { diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 56edc83809..670e866534 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -34,6 +34,11 @@ class Slider : public QSlider { public: Slider(QWidget *parent); ~Slider(); + double currentSecond() const { return value() / factor; } + void setCurrentSecond(double sec) { setValue(sec * factor); } + void setTimeRange(double min, double max); + AlertInfo alertInfo(double sec); + QPixmap thumbnail(double sec); signals: void updateMaximumTime(double); @@ -42,20 +47,17 @@ private: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; bool event(QEvent *event) override; - void sliderChange(QAbstractSlider::SliderChange change) override; void paintEvent(QPaintEvent *ev) override; void parseQLog(); - double max_sec = 0; - int slider_x = -1; - std::vector> timeline; + const double factor = 1000.0; + std::vector> timeline; std::mutex thumbnail_lock; std::atomic abort_parse_qlog = false; QMap thumbnails; std::map alerts; std::unique_ptr> qlog_future; InfoLabel thumbnail_label; - friend class VideoWidget; }; class VideoWidget : public QFrame { @@ -63,7 +65,7 @@ class VideoWidget : public QFrame { public: VideoWidget(QWidget *parnet = nullptr); - void rangeChanged(double min, double max, bool is_zommed); + void updateTimeRange(double min, double max, bool is_zommed); void setMaximumTime(double sec); protected: diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 077861ff96..5cbd3818a6 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -259,8 +259,8 @@ void ConsoleUI::updateTimeline() { const int total_sec = replay->totalSeconds(); for (auto [begin, end, type] : replay->getTimeline()) { - int start_pos = ((double)begin / total_sec) * width; - int end_pos = ((double)end / total_sec) * width; + int start_pos = (begin / total_sec) * width; + int end_pos = (end / total_sec) * width; if (type == TimelineType::Engaged) { mvwchgat(win, 1, start_pos, end_pos - start_pos + 1, A_COLOR, Color::Engaged, NULL); mvwchgat(win, 2, start_pos, end_pos - start_pos + 1, A_COLOR, Color::Engaged, NULL); diff --git a/tools/replay/replay.h b/tools/replay/replay.h index b7704132c2..4eb8e3ab30 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -67,14 +67,14 @@ public: inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; } inline QDateTime currentDateTime() const { return route_->datetime().addSecs(currentSeconds()); } inline uint64_t routeStartTime() const { return route_start_ts_; } - inline int toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } + inline double toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } inline int totalSeconds() const { return (!segments_.empty()) ? (segments_.rbegin()->first + 1) * 60 : 0; } inline void setSpeed(float speed) { speed_ = speed; } inline float getSpeed() const { return speed_; } inline const std::vector *events() const { return events_.get(); } inline const std::map> &segments() const { return segments_; }; inline const std::string &carFingerprint() const { return car_fingerprint_; } - inline const std::vector> getTimeline() { + inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); return timeline; } @@ -131,7 +131,7 @@ protected: std::mutex timeline_lock; QFuture timeline_future; - std::vector> timeline; + std::vector> timeline; std::set allow_list; std::string car_fingerprint_; float speed_ = 1.0;