diff --git a/CMakeLists.txt b/CMakeLists.txt index e4cb88f53..31d0da594 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ set(okularpart_SRCS ui/searchwidget.cpp ui/sidebar.cpp ui/side_reviews.cpp + ui/snapshottaker.cpp ui/thumbnaillist.cpp ui/toc.cpp ui/tocmodel.cpp diff --git a/cmake/modules/FindPoppler.cmake b/cmake/modules/FindPoppler.cmake index ffa6950d2..06e711751 100644 --- a/cmake/modules/FindPoppler.cmake +++ b/cmake/modules/FindPoppler.cmake @@ -97,17 +97,30 @@ int main() } " HAVE_POPPLER_0_20) +check_cxx_source_compiles(" +#include + +int main() +{ + Poppler::MovieObject *movie = 0; + movie->showPosterImage(); + return 0; +} +" HAVE_POPPLER_0_22) + set(CMAKE_REQUIRED_INCLUDES) set(CMAKE_REQUIRED_LIBRARIES) - if (HAVE_POPPLER_0_20) + if (HAVE_POPPLER_0_22) + set(popplerVersionMessage "0.22") + elseif (HAVE_POPPLER_0_20) set(popplerVersionMessage "0.20") elseif (HAVE_POPPLER_0_16) set(popplerVersionMessage "0.16") elseif (HAVE_POPPLER_0_12_1) set(popplerVersionMessage "0.12.1") - else (HAVE_POPPLER_0_20) + else (HAVE_POPPLER_0_22) set(popplerVersionMessage "0.5.4") - endif (HAVE_POPPLER_0_20) + endif (HAVE_POPPLER_0_22) if (NOT Poppler_FIND_QUIETLY) message(STATUS "Found Poppler-Qt4: ${POPPLER_LIBRARY}, (>= ${popplerVersionMessage})") endif (NOT Poppler_FIND_QUIETLY) diff --git a/core/movie.cpp b/core/movie.cpp index 94e71a955..dd780eba0 100644 --- a/core/movie.cpp +++ b/core/movie.cpp @@ -12,6 +12,7 @@ // qt/kde includes #include +#include #include #include @@ -30,7 +31,8 @@ class Movie::Private m_playMode( PlayOnce ), m_tmp( 0 ), m_showControls( false ), - m_autoPlay( false ) + m_autoPlay( false ), + m_showPosterImage( false ) { } @@ -39,8 +41,10 @@ class Movie::Private Rotation m_rotation; PlayMode m_playMode; QTemporaryFile *m_tmp; + QImage m_posterImage; bool m_showControls : 1; bool m_autoPlay : 1; + bool m_showPosterImage : 1; }; Movie::Movie( const QString& fileName ) @@ -130,3 +134,22 @@ bool Movie::autoPlay() const return d->m_autoPlay; } +void Movie::setShowPosterImage( bool show ) +{ + d->m_showPosterImage = show; +} + +bool Movie::showPosterImage() const +{ + return d->m_showPosterImage; +} + +void Movie::setPosterImage( const QImage &image ) +{ + d->m_posterImage = image; +} + +QImage Movie::posterImage() const +{ + return d->m_posterImage; +} diff --git a/core/movie.h b/core/movie.h index cbe382e56..8d0e2fe70 100644 --- a/core/movie.h +++ b/core/movie.h @@ -16,6 +16,8 @@ #include +class QImage; + namespace Okular { /** @@ -107,6 +109,34 @@ class OKULAR_EXPORT Movie */ bool autoPlay() const; + /** + * Sets whether to show a poster image. + * + * @since 4.10 + */ + void setShowPosterImage( bool show ); + + /** + * Whether to show a poster image. + * + * @since 4.10 + */ + bool showPosterImage() const; + + /** + * Sets the poster image. + * + * @since 4.10 + */ + void setPosterImage( const QImage &image ); + + /** + * Returns the poster image. + * + * @since 4.10 + */ + QImage posterImage() const; + private: class Private; Private* const d; diff --git a/generators/poppler/config-okular-poppler.h.cmake b/generators/poppler/config-okular-poppler.h.cmake index df89d6882..2ce6a4a65 100644 --- a/generators/poppler/config-okular-poppler.h.cmake +++ b/generators/poppler/config-okular-poppler.h.cmake @@ -6,3 +6,6 @@ /* Defined if we have the 0.20 version of the Poppler library */ #cmakedefine HAVE_POPPLER_0_20 1 + +/* Defined if we have the 0.22 version of the Poppler library */ +#cmakedefine HAVE_POPPLER_0_22 1 diff --git a/generators/poppler/generator_pdf.cpp b/generators/poppler/generator_pdf.cpp index 71c962ce5..4ae902bc0 100644 --- a/generators/poppler/generator_pdf.cpp +++ b/generators/poppler/generator_pdf.cpp @@ -180,6 +180,10 @@ Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerM movie->setShowControls( popplerMovie->showControls() ); movie->setPlayMode( (Okular::Movie::PlayMode)popplerMovie->playMode() ); movie->setAutoPlay( false ); // will be triggered by external MovieAnnotation +#ifdef HAVE_POPPLER_0_22 + movie->setShowPosterImage( popplerMovie->showPosterImage() ); + movie->setPosterImage( popplerMovie->posterImage() ); +#endif return movie; } diff --git a/ui/pageview.cpp b/ui/pageview.cpp index 5e04412bd..25dc63b40 100644 --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -860,7 +860,7 @@ void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setup Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); VideoWidget * vw = new VideoWidget( movieAnn, d->document, viewport() ); item->videoWidgets().insert( movieAnn->movie(), vw ); - vw->hide(); + vw->pageEntered(); } } } @@ -3961,7 +3961,7 @@ void PageView::slotRequestVisiblePixmaps( int newValue ) if ( vw->isPlaying() && viewportRectAtZeroZero.intersect( vw->geometry() ).isEmpty() ) { vw->stop(); - vw->hide(); + vw->pageLeft(); } } diff --git a/ui/presentationwidget.cpp b/ui/presentationwidget.cpp index f4da5390c..37523e8cd 100644 --- a/ui/presentationwidget.cpp +++ b/ui/presentationwidget.cpp @@ -316,7 +316,7 @@ void PresentationWidget::notifySetup( const QVector< Okular::Page * > & pageSet, Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); VideoWidget * vw = new VideoWidget( movieAnn, m_document, this ); frame->videoWidgets.insert( movieAnn->movie(), vw ); - vw->hide(); + vw->pageEntered(); } } frame->recalcGeometry( m_width, m_height, screenRatio ); @@ -809,7 +809,7 @@ void PresentationWidget::changePage( int newPage ) Q_FOREACH ( VideoWidget *vw, m_frames[ m_frameIndex ]->videoWidgets ) { vw->stop(); - vw->hide(); + vw->pageLeft(); } // stop audio playback, if any diff --git a/ui/snapshottaker.cpp b/ui/snapshottaker.cpp new file mode 100644 index 000000000..d48aafe0a --- /dev/null +++ b/ui/snapshottaker.cpp @@ -0,0 +1,31 @@ +#include "snapshottaker.h" + +#include +#include + +#include + +SnapshotTaker::SnapshotTaker( const QString &url, QObject *parent ) + : QObject( parent ) + , m_player( new Phonon::VideoPlayer( Phonon::NoCategory, 0 ) ) +{ + m_player->load( url ); + m_player->hide(); + + connect(m_player->mediaObject(), SIGNAL(stateChanged(Phonon::State, Phonon::State)), + this, SLOT(stateChanged(Phonon::State, Phonon::State))); + + m_player->play(); +} + +void SnapshotTaker::stateChanged(Phonon::State newState, Phonon::State) +{ + if (newState == Phonon::PlayingState) { + const QImage image = m_player->videoWidget()->snapshot(); + if (!image.isNull()) + emit finished( image ); + + m_player->stop(); + deleteLater(); + } +} diff --git a/ui/snapshottaker.h b/ui/snapshottaker.h new file mode 100644 index 000000000..66138d090 --- /dev/null +++ b/ui/snapshottaker.h @@ -0,0 +1,27 @@ +#ifndef SNAPSHOTTAKER_H +#define SNAPSHOTTAKER_H + +#include + +#include + +class QImage; + +class SnapshotTaker : public QObject +{ + Q_OBJECT + + public: + SnapshotTaker( const QString &url, QObject *parent = 0 ); + + Q_SIGNALS: + void finished( const QImage &image ); + + private Q_SLOTS: + void stateChanged(Phonon::State, Phonon::State); + + private: + Phonon::VideoPlayer *m_player; +}; + +#endif diff --git a/ui/videowidget.cpp b/ui/videowidget.cpp index 7d80b4344..3b7782d7d 100644 --- a/ui/videowidget.cpp +++ b/ui/videowidget.cpp @@ -11,10 +11,13 @@ // qt/kde includes #include +#include #include #include +#include #include #include +#include #include #include #include @@ -29,6 +32,7 @@ #include "core/annotations.h" #include "core/document.h" #include "core/movie.h" +#include "snapshottaker.h" static QAction* createToolBarButtonWithWidgetPopup( QToolBar* toolBar, QWidget *widget, const QIcon &icon ) { @@ -62,6 +66,9 @@ public: void load(); void setupPlayPauseAction( PlayPauseMode mode ); + void setPosterImage( const QImage& ); + void takeSnapshot(); + void videoStopped(); // slots void finished(); @@ -78,6 +85,8 @@ public: QAction *stopAction; QAction *seekSliderAction; QAction *seekSliderMenuAction; + QStackedLayout *pageLayout; + QLabel *posterImagePage; bool loaded : 1; }; @@ -121,6 +130,37 @@ void VideoWidget::Private::setupPlayPauseAction( PlayPauseMode mode ) } } +void VideoWidget::Private::takeSnapshot() +{ + const QString url = anno->movie()->url(); + KUrl newurl; + if ( QDir::isRelativePath( url ) ) + { + newurl = document->currentDocument(); + newurl.setFileName( url ); + } + else + { + newurl = url; + } + + SnapshotTaker *taker = 0; + if ( newurl.isLocalFile() ) + taker = new SnapshotTaker( newurl.toLocalFile(), q ); + else + taker = new SnapshotTaker( newurl.url(), q ); + + q->connect( taker, SIGNAL( finished( const QImage& ) ), q, SLOT( setPosterImage( const QImage& ) ) ); +} + +void VideoWidget::Private::videoStopped() +{ + if ( anno->movie()->showPosterImage() ) + pageLayout->setCurrentIndex( 1 ); + else + q->hide(); +} + void VideoWidget::Private::finished() { switch ( anno->movie()->playMode() ) @@ -132,7 +172,7 @@ void VideoWidget::Private::finished() setupPlayPauseAction( PlayMode ); if ( anno->movie()->playMode() == Okular::Movie::PlayOnce ) controlBar->setVisible( false ); - q->setVisible(false); + videoStopped(); break; case Okular::Movie::PlayRepeat: // repeat the playback @@ -158,6 +198,18 @@ void VideoWidget::Private::playOrPause() } } +void VideoWidget::Private::setPosterImage( const QImage &image ) +{ + if ( !image.isNull() ) + { + // cache the snapshot image + anno->movie()->setPosterImage( image ); + } + + posterImagePage->setPixmap( QPixmap::fromImage( image ) ); + q->show(); +} + VideoWidget::VideoWidget( Okular::MovieAnnotation *movieann, Okular::Document *document, QWidget *parent ) : QWidget( parent ), d( new Private( movieann, document, this ) ) { @@ -165,15 +217,18 @@ VideoWidget::VideoWidget( Okular::MovieAnnotation *movieann, Okular::Document *d // they should be tied to this widget, not spread around... setAttribute( Qt::WA_NoMousePropagation ); - QVBoxLayout *mainlay = new QVBoxLayout( this ); + // Setup player page + QWidget *playerPage = new QWidget; + + QVBoxLayout *mainlay = new QVBoxLayout( playerPage ); mainlay->setMargin( 0 ); mainlay->setSpacing( 0 ); - d->player = new Phonon::VideoPlayer( Phonon::NoCategory, this ); - d->player->installEventFilter( this ); + d->player = new Phonon::VideoPlayer( Phonon::NoCategory, playerPage ); + d->player->installEventFilter( playerPage ); mainlay->addWidget( d->player ); - d->controlBar = new QToolBar( this ); + d->controlBar = new QToolBar( playerPage ); d->controlBar->setIconSize( QSize( 16, 16 ) ); d->controlBar->setAutoFillBackground( true ); mainlay->addWidget( d->controlBar ); @@ -203,6 +258,38 @@ VideoWidget::VideoWidget( Okular::MovieAnnotation *movieann, Okular::Document *d connect( d->playPauseAction, SIGNAL(triggered()), this, SLOT(playOrPause()) ); d->geom = movieann->transformedBoundingRectangle(); + + // Setup poster image page + d->posterImagePage = new QLabel; + d->posterImagePage->setScaledContents( true ); + d->posterImagePage->installEventFilter( this ); + d->posterImagePage->setCursor( Qt::PointingHandCursor ); + + d->pageLayout = new QStackedLayout( this ); + d->pageLayout->setMargin( 0 ); + d->pageLayout->setSpacing( 0 ); + d->pageLayout->addWidget( playerPage ); + d->pageLayout->addWidget( d->posterImagePage ); + + + if ( movieann->movie()->showPosterImage() ) + { + d->pageLayout->setCurrentIndex( 1 ); + + const QImage posterImage = movieann->movie()->posterImage(); + if ( posterImage.isNull() ) + { + d->takeSnapshot(); + } + else + { + d->setPosterImage( posterImage ); + } + } + else + { + d->pageLayout->setCurrentIndex( 0 ); + } } VideoWidget::~VideoWidget() @@ -227,14 +314,29 @@ bool VideoWidget::isPlaying() const void VideoWidget::pageEntered() { - if ( d->anno->movie()->autoPlay() ) { + if ( d->anno->movie()->showPosterImage() ) + { + d->pageLayout->setCurrentIndex( 1 ); + show(); + } + + if ( d->anno->movie()->autoPlay() ) + { show(); QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection); } } +void VideoWidget::pageLeft() +{ + d->videoStopped(); + + hide(); +} + void VideoWidget::play() { + d->pageLayout->setCurrentIndex( 0 ); d->load(); d->player->play(); d->stopAction->setEnabled( true ); @@ -256,7 +358,7 @@ void VideoWidget::pause() bool VideoWidget::eventFilter( QObject * object, QEvent * event ) { - if ( object == d->player ) + if ( object == d->player || object == d->posterImagePage ) { switch ( event->type() ) { @@ -272,6 +374,18 @@ bool VideoWidget::eventFilter( QObject * object, QEvent * event ) event->accept(); } } + case QEvent::Wheel: + { + if ( object == d->posterImagePage ) + { + QWheelEvent * we = static_cast< QWheelEvent * >( event ); + + // forward wheel events to parent widget + QWheelEvent *copy = new QWheelEvent( we->pos(), we->globalPos(), we->delta(), we->buttons(), we->modifiers(), we->orientation() ); + QCoreApplication::postEvent( parentWidget(), copy ); + } + break; + } default: ; } } diff --git a/ui/videowidget.h b/ui/videowidget.h index e82753c87..71dda2273 100644 --- a/ui/videowidget.h +++ b/ui/videowidget.h @@ -35,6 +35,11 @@ class VideoWidget : public QWidget */ void pageEntered(); + /** + * This method is called when the page the video widget is located on has been left. + */ + void pageLeft(); + public slots: void play(); void pause(); @@ -48,6 +53,7 @@ class VideoWidget : public QWidget private: Q_PRIVATE_SLOT( d, void finished() ) Q_PRIVATE_SLOT( d, void playOrPause() ) + Q_PRIVATE_SLOT( d, void setPosterImage( const QImage& ) ) // private storage class Private;