diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index e36e62131f..0e9714994e 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,18 +1,15 @@ #include "tools/cabana/videowidget.h" -#include #include #include #include #include -#include #include -#include -#include #include #include const int MIN_VIDEO_HEIGHT = 100; +const int THUMBNAIL_MARGIN = 3; static const QColor timeline_colors[] = { [(int)TimelineType::None] = QColor(111, 143, 175), @@ -27,16 +24,11 @@ static inline QString formatTime(int seconds) { return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); } -VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(0, 0, 0, 0); - QFrame *frame = new QFrame(this); - frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); - main_layout->addWidget(frame); - - QVBoxLayout *frame_layout = new QVBoxLayout(frame); +VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); + auto main_layout = new QVBoxLayout(this); if (!can->liveStreaming()) { - frame_layout->addWidget(createCameraWidget()); + main_layout->addWidget(createCameraWidget()); } // btn controls @@ -57,7 +49,8 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { group->addButton(btn); if (speed == 1.0) btn->setChecked(true); } - frame_layout->addLayout(control_layout); + main_layout->addLayout(control_layout); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); QObject::connect(play_btn, &QPushButton::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); @@ -91,12 +84,13 @@ QWidget *VideoWidget::createCameraWidget() { QVBoxLayout *l = new QVBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); cam_widget = new CameraWidget("camerad", can->visionStreamType(), false); + cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); + cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); l->addWidget(cam_widget); // slider controls slider_layout = new QHBoxLayout(); - time_label = new ElidedLabel("00:00"); - time_label->setToolTip(tr("Click to set current time")); + time_label = new QLabel("00:00"); slider_layout->addWidget(time_label); slider = new Slider(this); @@ -106,12 +100,6 @@ QWidget *VideoWidget::createCameraWidget() { end_time_label = new QLabel(this); slider_layout->addWidget(end_time_label); l->addLayout(slider_layout); - - cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); - cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - - QObject::connect(time_label, &ElidedLabel::clicked, this, &VideoWidget::timeLabelClicked); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); @@ -123,29 +111,7 @@ QWidget *VideoWidget::createCameraWidget() { return w; } -void VideoWidget::timeLabelClicked() { - auto time_edit = new QTimeEdit(this); - auto init_date_time = can->currentDateTime(); - time_edit->setDateTime(init_date_time); - time_edit->setDisplayFormat("hh:mm:ss"); - time_label->setVisible(false); - slider_layout->insertWidget(0, time_edit); - QTimer::singleShot(0, [=]() { time_edit->setFocus(); }); - - QObject::connect(time_edit, &QTimeEdit::editingFinished, [=]() { - if (time_edit->dateTime() != init_date_time) { - int seconds = can->route()->datetime().secsTo(time_edit->dateTime()); - can->seekTo(seconds); - } - time_edit->setVisible(false); - time_label->setVisible(true); - time_edit->deleteLater(); - }); -} - void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { - if (can->liveStreaming()) return; - if (!is_zoomed) { min = 0; max = can->totalSeconds(); @@ -165,7 +131,7 @@ void VideoWidget::updatePlayBtnState() { } // Slider -Slider::Slider(QWidget *parent) : timer(this), QSlider(Qt::Horizontal, parent) { +Slider::Slider(QWidget *parent) : timer(this), thumbnail_label(this), QSlider(Qt::Horizontal, parent) { timer.callOnTimeout([this]() { timeline = can->getTimeline(); update(); @@ -198,29 +164,18 @@ void Slider::loadThumbnails() { if (log.load(qlog, &abort_load_thumbnail, {cereal::Event::Which::THUMBNAIL}, true, 0, 3)) { for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_load_thumbnail; ++ev) { auto thumb = (*ev)->event.getThumbnail(); - QString str = getThumbnailString(thumb.getThumbnail()); - std::lock_guard lk(thumbnail_lock); - thumbnails[thumb.getTimestampEof()] = str; + auto data = thumb.getThumbnail(); + if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) { + pm = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation); + std::lock_guard lk(thumbnail_lock); + thumbnails[thumb.getTimestampEof()] = pm; + } } } } } } -QString Slider::getThumbnailString(const capnp::Data::Reader &data) { - QPixmap thumb; - if (thumb.loadFromData(data.begin(), data.size(), "jpeg")) { - thumb = thumb.scaled({thumb.width()/3, thumb.height()/3}, Qt::KeepAspectRatio); - thumbnail_size = thumb.size(); - QByteArray bytes; - QBuffer buffer(&bytes); - buffer.open(QIODevice::WriteOnly); - thumb.save(&buffer, "png"); - return QString("").arg(QString(bytes.toBase64())); - } - return {}; -} - void Slider::sliderChange(QAbstractSlider::SliderChange change) { if (change == QAbstractSlider::SliderValueChange) { int x = width() * ((value() - minimum()) / double(maximum() - minimum())); @@ -266,21 +221,45 @@ void Slider::mousePressEvent(QMouseEvent *e) { } void Slider::mouseMoveEvent(QMouseEvent *e) { - QString thumb; + QPixmap thumb; + double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0; { - double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0; std::lock_guard lk(thumbnail_lock); auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9); - if (it != thumbnails.end()) { - thumb = it.value(); - } + if (it != thumbnails.end()) thumb = it.value(); } - QPoint pt = mapToGlobal({e->pos().x() - thumbnail_size.width() / 2, -thumbnail_size.height() - 30}); - QToolTip::showText(pt, thumb, this, rect()); + int x = std::clamp(e->pos().x() - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN); + int y = -thumb.height() - THUMBNAIL_MARGIN - style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing); + thumbnail_label.showPixmap(mapToGlobal({x, y}), formatTime(seconds), thumb); QSlider::mouseMoveEvent(e); } void Slider::leaveEvent(QEvent *event) { - QToolTip::hideText(); + thumbnail_label.hide(); QSlider::leaveEvent(event); } + +// ThumbnailLabel + +ThumbnailLabel::ThumbnailLabel(QWidget *parent) : QWidget(parent, Qt::Tool | Qt::FramelessWindowHint) { + setAttribute(Qt::WA_ShowWithoutActivating); + setVisible(false); +} + +void ThumbnailLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm) { + pixmap = pm; + second = sec; + setVisible(!pm.isNull()); + if (isVisible()) { + setGeometry({pt, pm.size()}); + update(); + } +} + +void ThumbnailLabel::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.drawPixmap(0, 0, pixmap); + p.setPen(QPen(Qt::white, 2)); + p.drawRect(rect()); + p.drawText(rect().adjusted(0, 0, 0, -THUMBNAIL_MARGIN), second, Qt::AlignHCenter | Qt::AlignBottom); +} diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 40a85662b6..00b059428d 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -11,10 +11,17 @@ #include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" +class ThumbnailLabel : public QWidget { +public: + ThumbnailLabel(QWidget *parent); + void showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm); + void paintEvent(QPaintEvent *event) override; + QPixmap pixmap; + QString second; +}; + class Slider : public QSlider { Q_OBJECT @@ -30,19 +37,18 @@ private: void paintEvent(QPaintEvent *ev) override; void streamStarted(); void loadThumbnails(); - QString getThumbnailString(const capnp::Data::Reader &data); int slider_x = -1; std::vector> timeline; std::mutex thumbnail_lock; std::atomic abort_load_thumbnail = false; - QMap thumbnails; + QMap thumbnails; QFuture thumnail_future; - QSize thumbnail_size = {}; + ThumbnailLabel thumbnail_label; QTimer timer; }; -class VideoWidget : public QWidget { +class VideoWidget : public QFrame { Q_OBJECT public: @@ -52,12 +58,11 @@ public: protected: void updateState(); void updatePlayBtnState(); - void timeLabelClicked(); QWidget *createCameraWidget(); CameraWidget *cam_widget; QLabel *end_time_label; - ElidedLabel *time_label; + QLabel *time_label; QHBoxLayout *slider_layout; QPushButton *play_btn; Slider *slider;