File indexing completed on 2024-04-28 15:52:00
0001 /* 0002 SPDX-FileCopyrightText: 2008 Pino Toscano <pino@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "videowidget.h" 0008 0009 // qt/kde includes 0010 #include <qaction.h> 0011 #include <qcoreapplication.h> 0012 #include <qdir.h> 0013 #include <qevent.h> 0014 #include <qlabel.h> 0015 #include <qlayout.h> 0016 #include <qmenu.h> 0017 #include <qstackedlayout.h> 0018 #include <qtoolbar.h> 0019 #include <qtoolbutton.h> 0020 #include <qwidgetaction.h> 0021 0022 #include <KLocalizedString> 0023 #include <QIcon> 0024 0025 #include "config-okular.h" 0026 0027 #if HAVE_PHONON 0028 #include <phonon/mediaobject.h> 0029 #include <phonon/seekslider.h> 0030 #include <phonon/videoplayer.h> 0031 #endif 0032 0033 #include "core/annotations.h" 0034 #include "core/area.h" 0035 #include "core/document.h" 0036 #include "core/movie.h" 0037 #include "snapshottaker.h" 0038 0039 #if HAVE_PHONON 0040 0041 static QAction *createToolBarButtonWithWidgetPopup(QToolBar *toolBar, QWidget *widget, const QIcon &icon) 0042 { 0043 QToolButton *button = new QToolButton(toolBar); 0044 QAction *action = toolBar->addWidget(button); 0045 button->setAutoRaise(true); 0046 button->setIcon(icon); 0047 button->setPopupMode(QToolButton::InstantPopup); 0048 QMenu *menu = new QMenu(button); 0049 button->setMenu(menu); 0050 QWidgetAction *widgetAction = new QWidgetAction(menu); 0051 QWidget *dummy = new QWidget(menu); 0052 widgetAction->setDefaultWidget(dummy); 0053 QVBoxLayout *dummyLayout = new QVBoxLayout(dummy); 0054 dummyLayout->setContentsMargins(5, 5, 5, 5); 0055 dummyLayout->addWidget(widget); 0056 menu->addAction(widgetAction); 0057 return action; 0058 } 0059 0060 /* Private storage. */ 0061 class VideoWidget::Private 0062 { 0063 public: 0064 Private(Okular::Movie *m, Okular::Document *doc, VideoWidget *qq) 0065 : q(qq) 0066 , movie(m) 0067 , document(doc) 0068 , player(nullptr) 0069 , loaded(false) 0070 { 0071 } 0072 0073 ~Private() 0074 { 0075 if (player) { 0076 player->stop(); 0077 } 0078 } 0079 0080 enum PlayPauseMode { PlayMode, PauseMode }; 0081 0082 void load(); 0083 void setupPlayPauseAction(PlayPauseMode mode); 0084 void setPosterImage(const QImage &); 0085 void takeSnapshot(); 0086 void videoStopped(); 0087 void stateChanged(Phonon::State newState); 0088 0089 // slots 0090 void finished(); 0091 void playOrPause(); 0092 0093 VideoWidget *q; 0094 Okular::Movie *movie; 0095 Okular::Document *document; 0096 Okular::NormalizedRect geom; 0097 Phonon::VideoPlayer *player; 0098 Phonon::SeekSlider *seekSlider; 0099 QToolBar *controlBar; 0100 QAction *playPauseAction; 0101 QAction *stopAction; 0102 QAction *seekSliderAction; 0103 QAction *seekSliderMenuAction; 0104 QStackedLayout *pageLayout; 0105 QLabel *posterImagePage; 0106 bool loaded : 1; 0107 double repetitionsLeft; 0108 }; 0109 0110 static QUrl urlFromUrlString(const QString &url, Okular::Document *document) 0111 { 0112 QUrl newurl; 0113 if (url.startsWith(QLatin1Char('/'))) { 0114 newurl = QUrl::fromLocalFile(url); 0115 } else { 0116 newurl = QUrl(url); 0117 if (newurl.isRelative()) { 0118 newurl = document->currentDocument().adjusted(QUrl::RemoveFilename); 0119 newurl.setPath(newurl.path() + url); 0120 } 0121 } 0122 return newurl; 0123 } 0124 0125 void VideoWidget::Private::load() 0126 { 0127 repetitionsLeft = movie->playRepetitions(); 0128 if (loaded) { 0129 return; 0130 } 0131 0132 loaded = true; 0133 0134 player->load(urlFromUrlString(movie->url(), document)); 0135 0136 connect(player->mediaObject(), &Phonon::MediaObject::stateChanged, q, [this](Phonon::State s) { stateChanged(s); }); 0137 0138 seekSlider->setEnabled(true); 0139 } 0140 0141 void VideoWidget::Private::setupPlayPauseAction(PlayPauseMode mode) 0142 { 0143 if (mode == PlayMode) { 0144 playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); 0145 playPauseAction->setText(i18nc("start the movie playback", "Play")); 0146 } else if (mode == PauseMode) { 0147 playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); 0148 playPauseAction->setText(i18nc("pause the movie playback", "Pause")); 0149 } 0150 } 0151 0152 void VideoWidget::Private::takeSnapshot() 0153 { 0154 const QUrl url = urlFromUrlString(movie->url(), document); 0155 SnapshotTaker *taker = new SnapshotTaker(url, q); 0156 0157 q->connect(taker, &SnapshotTaker::finished, q, [this](const QImage &image) { setPosterImage(image); }); 0158 } 0159 0160 void VideoWidget::Private::videoStopped() 0161 { 0162 if (movie->showPosterImage()) { 0163 pageLayout->setCurrentIndex(1); 0164 } else { 0165 q->hide(); 0166 } 0167 } 0168 0169 void VideoWidget::Private::finished() 0170 { 0171 switch (movie->playMode()) { 0172 case Okular::Movie::PlayLimited: 0173 case Okular::Movie::PlayOpen: 0174 repetitionsLeft -= 1.0; 0175 if (repetitionsLeft < 1e-5) { // allow for some calculation error 0176 // playback has ended 0177 stopAction->setEnabled(false); 0178 setupPlayPauseAction(PlayMode); 0179 if (movie->playMode() == Okular::Movie::PlayLimited) { 0180 controlBar->setVisible(false); 0181 } 0182 videoStopped(); 0183 } else { 0184 // not done yet, repeat 0185 // if repetitionsLeft is less than 1, we are supposed to stop midway, but not even Adobe reader does this 0186 player->play(); 0187 } 0188 break; 0189 case Okular::Movie::PlayRepeat: 0190 // repeat the playback 0191 player->play(); 0192 break; 0193 case Okular::Movie::PlayPalindrome: 0194 // FIXME we should play backward, but we cannot 0195 player->play(); 0196 break; 0197 } 0198 } 0199 0200 void VideoWidget::Private::playOrPause() 0201 { 0202 if (player->isPlaying()) { 0203 player->pause(); 0204 setupPlayPauseAction(PlayMode); 0205 } else { 0206 q->play(); 0207 } 0208 } 0209 0210 void VideoWidget::Private::setPosterImage(const QImage &image) 0211 { 0212 if (!image.isNull()) { 0213 // cache the snapshot image 0214 movie->setPosterImage(image); 0215 } 0216 0217 posterImagePage->setPixmap(QPixmap::fromImage(image)); 0218 } 0219 0220 void VideoWidget::Private::stateChanged(Phonon::State newState) 0221 { 0222 if (newState == Phonon::PlayingState) { 0223 pageLayout->setCurrentIndex(0); 0224 } 0225 } 0226 0227 VideoWidget::VideoWidget(const Okular::Annotation *annotation, Okular::Movie *movie, Okular::Document *document, QWidget *parent) 0228 : QWidget(parent) 0229 , d(new Private(movie, document, this)) 0230 { 0231 // do not propagate the mouse events to the parent widget; 0232 // they should be tied to this widget, not spread around... 0233 setAttribute(Qt::WA_NoMousePropagation); 0234 0235 // Setup player page 0236 QWidget *playerPage = new QWidget(this); 0237 0238 QVBoxLayout *mainlay = new QVBoxLayout(playerPage); 0239 mainlay->setContentsMargins(0, 0, 0, 0); 0240 mainlay->setSpacing(0); 0241 0242 d->player = new Phonon::VideoPlayer(Phonon::NoCategory, playerPage); 0243 d->player->installEventFilter(playerPage); 0244 mainlay->addWidget(d->player); 0245 0246 d->controlBar = new QToolBar(playerPage); 0247 d->controlBar->setIconSize(QSize(16, 16)); 0248 d->controlBar->setAutoFillBackground(true); 0249 mainlay->addWidget(d->controlBar); 0250 0251 d->playPauseAction = new QAction(d->controlBar); 0252 d->controlBar->addAction(d->playPauseAction); 0253 d->setupPlayPauseAction(Private::PlayMode); 0254 d->stopAction = d->controlBar->addAction(QIcon::fromTheme(QStringLiteral("media-playback-stop")), i18nc("stop the movie playback", "Stop"), this, SLOT(stop())); 0255 d->stopAction->setEnabled(false); 0256 d->controlBar->addSeparator(); 0257 d->seekSlider = new Phonon::SeekSlider(d->player->mediaObject(), d->controlBar); 0258 d->seekSliderAction = d->controlBar->addWidget(d->seekSlider); 0259 d->seekSlider->setEnabled(false); 0260 0261 Phonon::SeekSlider *verticalSeekSlider = new Phonon::SeekSlider(d->player->mediaObject(), nullptr); 0262 verticalSeekSlider->setMaximumHeight(100); 0263 d->seekSliderMenuAction = createToolBarButtonWithWidgetPopup(d->controlBar, verticalSeekSlider, QIcon::fromTheme(QStringLiteral("player-time"))); 0264 d->seekSliderMenuAction->setVisible(false); 0265 0266 d->controlBar->setVisible(movie->showControls()); 0267 0268 connect(d->player, &Phonon::VideoPlayer::finished, this, [this] { d->finished(); }); 0269 connect(d->playPauseAction, &QAction::triggered, this, [this] { d->playOrPause(); }); 0270 0271 d->geom = annotation->transformedBoundingRectangle(); 0272 0273 // Setup poster image page 0274 d->posterImagePage = new QLabel; 0275 d->posterImagePage->setScaledContents(true); 0276 d->posterImagePage->installEventFilter(this); 0277 d->posterImagePage->setCursor(Qt::PointingHandCursor); 0278 0279 d->pageLayout = new QStackedLayout(this); 0280 d->pageLayout->setContentsMargins({}); 0281 d->pageLayout->setSpacing(0); 0282 d->pageLayout->addWidget(playerPage); 0283 d->pageLayout->addWidget(d->posterImagePage); 0284 0285 if (movie->showPosterImage()) { 0286 d->pageLayout->setCurrentIndex(1); 0287 0288 const QImage posterImage = movie->posterImage(); 0289 if (posterImage.isNull()) { 0290 d->takeSnapshot(); 0291 } else { 0292 d->setPosterImage(posterImage); 0293 } 0294 } else { 0295 d->pageLayout->setCurrentIndex(0); 0296 } 0297 } 0298 0299 VideoWidget::~VideoWidget() 0300 { 0301 delete d; 0302 } 0303 0304 void VideoWidget::setNormGeometry(const Okular::NormalizedRect &rect) 0305 { 0306 d->geom = rect; 0307 } 0308 0309 Okular::NormalizedRect VideoWidget::normGeometry() const 0310 { 0311 return d->geom; 0312 } 0313 0314 bool VideoWidget::isPlaying() const 0315 { 0316 return d->player->isPlaying(); 0317 } 0318 0319 void VideoWidget::pageInitialized() 0320 { 0321 hide(); 0322 } 0323 0324 void VideoWidget::pageEntered() 0325 { 0326 if (d->movie->showPosterImage()) { 0327 d->pageLayout->setCurrentIndex(1); 0328 show(); 0329 } 0330 0331 if (d->movie->autoPlay()) { 0332 show(); 0333 QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection); 0334 if (d->movie->startPaused()) { 0335 QMetaObject::invokeMethod(this, "pause", Qt::QueuedConnection); 0336 } 0337 } 0338 } 0339 0340 void VideoWidget::pageLeft() 0341 { 0342 d->player->stop(); 0343 d->videoStopped(); 0344 0345 hide(); 0346 } 0347 0348 void VideoWidget::play() 0349 { 0350 d->controlBar->setVisible(d->movie->showControls()); 0351 d->load(); 0352 // if d->repetitionsLeft is less than 1, we are supposed to stop midway, but not even Adobe reader does this 0353 d->player->play(); 0354 d->stopAction->setEnabled(true); 0355 d->setupPlayPauseAction(Private::PauseMode); 0356 } 0357 0358 void VideoWidget::stop() 0359 { 0360 d->player->stop(); 0361 d->stopAction->setEnabled(false); 0362 d->setupPlayPauseAction(Private::PlayMode); 0363 } 0364 0365 void VideoWidget::pause() 0366 { 0367 d->player->pause(); 0368 d->setupPlayPauseAction(Private::PlayMode); 0369 } 0370 0371 bool VideoWidget::eventFilter(QObject *object, QEvent *event) 0372 { 0373 if (object == d->player || object == d->posterImagePage) { 0374 switch (event->type()) { 0375 case QEvent::MouseButtonPress: { 0376 QMouseEvent *me = static_cast<QMouseEvent *>(event); 0377 if (me->button() == Qt::LeftButton) { 0378 if (!d->player->isPlaying()) { 0379 play(); 0380 } 0381 event->accept(); 0382 } 0383 break; 0384 } 0385 case QEvent::Wheel: { 0386 if (object == d->posterImagePage) { 0387 QWheelEvent *we = static_cast<QWheelEvent *>(event); 0388 0389 // forward wheel events to parent widget 0390 QWheelEvent *copy = new QWheelEvent(we->position(), we->globalPosition(), we->pixelDelta(), we->angleDelta(), we->buttons(), we->modifiers(), we->phase(), we->inverted(), we->source()); 0391 QCoreApplication::postEvent(parentWidget(), copy); 0392 } 0393 break; 0394 } 0395 default:; 0396 } 0397 } 0398 0399 return false; 0400 } 0401 0402 bool VideoWidget::event(QEvent *event) 0403 { 0404 switch (event->type()) { 0405 case QEvent::ToolTip: 0406 // "eat" the help events (= tooltips), avoid parent widgets receiving them 0407 event->accept(); 0408 return true; 0409 break; 0410 default:; 0411 } 0412 0413 return QWidget::event(event); 0414 } 0415 0416 void VideoWidget::resizeEvent(QResizeEvent *event) 0417 { 0418 const QSize &s = event->size(); 0419 int usedSpace = d->seekSlider->geometry().left() + d->seekSlider->iconSize().width(); 0420 // try to give the slider at least 30px of space 0421 if (s.width() < (usedSpace + 30)) { 0422 d->seekSliderAction->setVisible(false); 0423 d->seekSliderMenuAction->setVisible(true); 0424 } else { 0425 d->seekSliderAction->setVisible(true); 0426 d->seekSliderMenuAction->setVisible(false); 0427 } 0428 } 0429 #else 0430 0431 class VideoWidget::Private 0432 { 0433 public: 0434 Okular::NormalizedRect geom; 0435 }; 0436 0437 bool VideoWidget::event(QEvent *event) 0438 { 0439 return QWidget::event(event); 0440 } 0441 0442 bool VideoWidget::eventFilter(QObject *object, QEvent *event) 0443 { 0444 return QWidget::eventFilter(object, event); 0445 } 0446 0447 bool VideoWidget::isPlaying() const 0448 { 0449 return false; 0450 } 0451 0452 Okular::NormalizedRect VideoWidget::normGeometry() const 0453 { 0454 return d->geom; 0455 } 0456 0457 void VideoWidget::pageEntered() 0458 { 0459 show(); 0460 } 0461 0462 void VideoWidget::pageInitialized() 0463 { 0464 } 0465 0466 void VideoWidget::pageLeft() 0467 { 0468 } 0469 void VideoWidget::pause() 0470 { 0471 } 0472 void VideoWidget::play() 0473 { 0474 } 0475 0476 void VideoWidget::resizeEvent(QResizeEvent *event) 0477 { 0478 QWidget::resizeEvent(event); 0479 } 0480 0481 void VideoWidget::setNormGeometry(const Okular::NormalizedRect &rect) 0482 { 0483 d->geom = rect; 0484 } 0485 0486 void VideoWidget::stop() 0487 { 0488 } 0489 0490 VideoWidget::VideoWidget(const Okular::Annotation *annotation, Okular::Movie *movie, Okular::Document *document, QWidget *parent) 0491 : QWidget(parent) 0492 , d(new VideoWidget::Private) 0493 { 0494 auto layout = new QVBoxLayout(); 0495 d->geom = annotation->transformedBoundingRectangle(); 0496 auto poster = new QLabel; 0497 if (movie->showPosterImage()) { 0498 auto posterImage = movie->posterImage(); 0499 if (!posterImage.isNull()) { 0500 poster->setPixmap(QPixmap::fromImage(posterImage)); 0501 } 0502 } 0503 Q_EMIT document->warning(i18n("Videos not supported in this okular"), 5000); 0504 poster->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0505 layout->addWidget(poster, 2); 0506 0507 auto label = new QLabel(i18n("Videos not supported in this Okular")); 0508 label->setAutoFillBackground(true); 0509 label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); 0510 layout->addWidget(label, 1, Qt::AlignCenter); 0511 setLayout(layout); 0512 } 0513 0514 VideoWidget::~VideoWidget() noexcept 0515 { 0516 } 0517 0518 #endif 0519 #include "moc_videowidget.cpp"