diff --git a/conf/dlgpresentationbase.ui b/conf/dlgpresentationbase.ui index 2e29743dc..5b9d13b80 100644 --- a/conf/dlgpresentationbase.ui +++ b/conf/dlgpresentationbase.ui @@ -1,349 +1,382 @@ DlgPresentationBase 0 0 400 525 0 0 0 0 Navigation 6 9 9 9 9 6 0 0 0 0 Advance every: false 5 Loop after last page + + + + + + Touch navigation: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Tap left/right side to go back/forward + + + + + Tap anywhere to go forward + + + + + Disabled + + + + + + Appearance Background color: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Mouse cursor: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Hidden After Delay Always Visible Always Hidden Show &progress indicator Show s&ummary page Enable transitions true true Default transition: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Blinds Vertical Blinds Horizontal Box In Box Out Dissolve Fade Glitter Down Glitter Right Glitter Right-Down Random Transition Replace Split Horizontal In Split Horizontal Out Split Vertical In Split Vertical Out Wipe Down Wipe Right Wipe Left Wipe Up Placement Screen: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Drawing Tool Configuration KColorButton QPushButton
kcolorbutton.h
KPluralHandlingSpinBox QSpinBox
KPluralHandlingSpinBox
kcfg_SlidesAdvance toggled(bool) kcfg_SlidesAdvanceTime setEnabled(bool) 88 43 280 49
diff --git a/conf/okular_core.kcfg b/conf/okular_core.kcfg index 6a3c7bc1d..6c679657d 100644 --- a/conf/okular_core.kcfg +++ b/conf/okular_core.kcfg @@ -1,96 +1,104 @@ kuser.h Normal true Enabled Enabled Disabled Qt::white false Inverted true false Kate kate --line %l --column %c false 5 1 3600 false + + Forward + + + + + + diff --git a/ui/presentationwidget.cpp b/ui/presentationwidget.cpp index a64309b86..eb90c1217 100644 --- a/ui/presentationwidget.cpp +++ b/ui/presentationwidget.cpp @@ -1,2463 +1,2501 @@ /*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "presentationwidget.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_LINUX #include #include // For ::close() for sleep inhibition #endif // system includes #include #include // local includes #include "annotationtools.h" #include "debug_ui.h" #include "drawingtoolactions.h" #include "guiutils.h" #include "pagepainter.h" #include "presentationsearchbar.h" #include "priorities.h" #include "videowidget.h" #include "core/action.h" #include "core/annotations.h" #include "core/audioplayer.h" #include "core/document.h" #include "core/generator.h" #include "core/movie.h" #include "core/page.h" #include "settings.h" #include "settings_core.h" // comment this to disable the top-right progress indicator #define ENABLE_PROGRESS_OVERLAY // a frame contains a pointer to the page object, its geometry and the // transition effect to the next frame struct PresentationFrame { PresentationFrame() = default; ~PresentationFrame() { qDeleteAll( videoWidgets ); } PresentationFrame(const PresentationFrame &) = delete; PresentationFrame &operator=(const PresentationFrame &) = delete; void recalcGeometry( int width, int height, float screenRatio ) { // calculate frame geometry keeping constant aspect ratio float pageRatio = page->ratio(); int pageWidth = width, pageHeight = height; if ( pageRatio > screenRatio ) pageWidth = (int)( (float)pageHeight / pageRatio ); else pageHeight = (int)( (float)pageWidth * pageRatio ); geometry.setRect( ( width - pageWidth ) / 2, ( height - pageHeight ) / 2, pageWidth, pageHeight ); Q_FOREACH ( VideoWidget *vw, videoWidgets ) { const Okular::NormalizedRect r = vw->normGeometry(); QRect vwgeom = r.geometry( geometry.width(), geometry.height() ); vw->resize( vwgeom.size() ); vw->move( geometry.topLeft() + vwgeom.topLeft() ); } } const Okular::Page * page; QRect geometry; QHash< Okular::Movie *, VideoWidget * > videoWidgets; QLinkedList< SmoothPath > drawings; }; // a custom QToolBar that basically does not propagate the event if the widget // background is not automatically filled class PresentationToolBar : public QToolBar { Q_OBJECT public: PresentationToolBar( QWidget * parent = Q_NULLPTR ) : QToolBar( parent ) {} protected: void mousePressEvent( QMouseEvent * e ) override { QToolBar::mousePressEvent( e ); e->accept(); } void mouseReleaseEvent( QMouseEvent * e ) override { QToolBar::mouseReleaseEvent( e ); e->accept(); } }; PresentationWidget::PresentationWidget( QWidget * parent, Okular::Document * doc, DrawingToolActions * drawingToolActions, KActionCollection * collection ) : QWidget( nullptr /* must be null, to have an independent widget */, Qt::FramelessWindowHint ), m_pressedLink( nullptr ), m_handCursor( false ), m_drawingEngine( nullptr ), m_screenInhibitCookie(0), m_sleepInhibitFd(-1), m_parentWidget( parent ), m_document( doc ), m_frameIndex( -1 ), m_topBar( nullptr ), m_pagesEdit( nullptr ), m_searchBar( nullptr ), m_ac( collection ), m_screenSelect( nullptr ), m_isSetup( false ), m_blockNotifications( false ), m_inBlackScreenMode( false ), m_showSummaryView( Okular::Settings::slidesShowSummary() ), m_advanceSlides( Okular::SettingsCore::slidesAdvance() ), + m_goToPreviousPageOnRelease( false ), m_goToNextPageOnRelease( false ) { Q_UNUSED( parent ) setAttribute( Qt::WA_DeleteOnClose ); setAttribute( Qt::WA_OpaquePaintEvent ); setObjectName( QStringLiteral( "presentationWidget" ) ); QString caption = doc->metaData( QStringLiteral("DocumentTitle") ).toString(); if ( caption.trimmed().isEmpty() ) caption = doc->currentDocument().fileName(); caption = i18nc( "[document title/filename] – Presentation", "%1 – Presentation", caption ); setWindowTitle( caption ); m_width = -1; m_screen = -2; // create top toolbar m_topBar = new PresentationToolBar( this ); m_topBar->setObjectName( QStringLiteral( "presentationBar" ) ); m_topBar->setMovable( false ); m_topBar->layout()->setContentsMargins(0, 0, 0, 0); m_topBar->addAction( QIcon::fromTheme( layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-next") : QStringLiteral("go-previous") ), i18n( "Previous Page" ), this, SLOT(slotPrevPage()) ); m_pagesEdit = new KLineEdit( m_topBar ); QSizePolicy sp = m_pagesEdit->sizePolicy(); sp.setHorizontalPolicy( QSizePolicy::Minimum ); m_pagesEdit->setSizePolicy( sp ); QFontMetrics fm( m_pagesEdit->font() ); QStyleOptionFrame option; option.initFrom( m_pagesEdit ); m_pagesEdit->setMaximumWidth( fm.width( QString::number( m_document->pages() ) ) + 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth, &option, m_pagesEdit ) + 4 ); // the 4 comes from 2*horizontalMargin, horizontalMargin being a define in qlineedit.cpp QIntValidator *validator = new QIntValidator( 1, m_document->pages(), m_pagesEdit ); m_pagesEdit->setValidator( validator ); m_topBar->addWidget( m_pagesEdit ); QLabel *pagesLabel = new QLabel( m_topBar ); pagesLabel->setText( QLatin1String( " / " ) + QString::number( m_document->pages() ) + QLatin1String( " " ) ); m_topBar->addWidget( pagesLabel ); connect(m_pagesEdit, &QLineEdit::returnPressed, this, &PresentationWidget::slotPageChanged); m_topBar->addAction( QIcon::fromTheme( layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-previous") : QStringLiteral("go-next") ), i18n( "Next Page" ), this, SLOT(slotNextPage()) ); m_topBar->addSeparator(); QAction *playPauseAct = collection->action( QStringLiteral("presentation_play_pause") ); playPauseAct->setEnabled( true ); connect(playPauseAct, &QAction::triggered, this, &PresentationWidget::slotTogglePlayPause); m_topBar->addAction( playPauseAct ); setPlayPauseIcon(); addAction( playPauseAct ); m_topBar->addSeparator(); foreach(QAction *action, drawingToolActions->actions()) { action->setEnabled( true ); m_topBar->addAction( action ); addAction( action ); } connect( drawingToolActions, &DrawingToolActions::changeEngine, this, &PresentationWidget::slotChangeDrawingToolEngine ); connect( drawingToolActions, &DrawingToolActions::actionsRecreated, this, &PresentationWidget::slotAddDrawingToolActions ); QAction *eraseDrawingAct = collection->action( QStringLiteral("presentation_erase_drawings") ); eraseDrawingAct->setEnabled( true ); connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings); m_topBar->addAction( eraseDrawingAct ); addAction( eraseDrawingAct ); QDesktopWidget *desktop = QApplication::desktop(); if ( desktop->numScreens() > 1 ) { m_topBar->addSeparator(); m_screenSelect = new KSelectAction( QIcon::fromTheme( QStringLiteral("video-display") ), i18n( "Switch Screen" ), m_topBar ); m_screenSelect->setToolBarMode( KSelectAction::MenuMode ); m_screenSelect->setToolButtonPopupMode( QToolButton::InstantPopup ); m_topBar->addAction( m_screenSelect ); const int screenCount = desktop->numScreens(); for ( int i = 0; i < screenCount; ++i ) { QAction *act = m_screenSelect->addAction( i18nc( "%1 is the screen number (0, 1, ...)", "Screen %1", i ) ); act->setData( qVariantFromValue( i ) ); } } QWidget *spacer = new QWidget( m_topBar ); spacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::MinimumExpanding ); m_topBar->addWidget( spacer ); m_topBar->addAction( QIcon::fromTheme( QStringLiteral("application-exit") ), i18n( "Exit Presentation Mode" ), this, SLOT(close()) ); m_topBar->setAutoFillBackground( true ); showTopBar( false ); // change topbar background color QPalette p = m_topBar->palette(); p.setColor( QPalette::Active, QPalette::Button, Qt::gray ); p.setColor( QPalette::Active, QPalette::Window, Qt::darkGray ); m_topBar->setPalette( p ); // Grab swipe gestures to change pages grabGesture(Qt::SwipeGesture); // misc stuff setMouseTracking( true ); setContextMenuPolicy( Qt::PreventContextMenu ); m_transitionTimer = new QTimer( this ); m_transitionTimer->setSingleShot( true ); connect(m_transitionTimer, &QTimer::timeout, this, &PresentationWidget::slotTransitionStep); m_overlayHideTimer = new QTimer( this ); m_overlayHideTimer->setSingleShot( true ); connect(m_overlayHideTimer, &QTimer::timeout, this, &PresentationWidget::slotHideOverlay); m_nextPageTimer = new QTimer( this ); m_nextPageTimer->setSingleShot( true ); connect(m_nextPageTimer, &QTimer::timeout, this, &PresentationWidget::slotNextPage); connect(m_document, &Okular::Document::processMovieAction, this, &PresentationWidget::slotProcessMovieAction); connect(m_document, &Okular::Document::processRenditionAction, this, &PresentationWidget::slotProcessRenditionAction); // handle cursor appearance as specified in configuration if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, true ); KCursor::setHideCursorDelay( 3000 ); } else if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) { setCursor( QCursor( Qt::BlankCursor ) ); } setupActions(); // inhibit power management inhibitPowerManagement(); show(); QTimer::singleShot( 0, this, &PresentationWidget::slotDelayedEvents ); // setFocus() so KCursor::setAutoHideCursor() goes into effect if it's enabled setFocus( Qt::OtherFocusReason ); } PresentationWidget::~PresentationWidget() { // allow power management saver again allowPowerManagement(); // stop the audio playbacks Okular::AudioPlayer::instance()->stopPlaybacks(); // remove our highlights if ( m_searchBar ) { m_document->resetSearch( PRESENTATION_SEARCH_ID ); } // remove this widget from document observer m_document->removeObserver( this ); foreach( QAction *action, m_topBar->actions() ) { action->setChecked( false ); action->setEnabled( false ); } delete m_drawingEngine; // delete frames QVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end(); for ( ; fIt != fEnd; ++fIt ) delete *fIt; } void PresentationWidget::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) { // same document, nothing to change - here we assume the document sets up // us with the whole document set as first notifySetup() if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) ) return; // delete previous frames (if any (shouldn't be)) QVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end(); for ( ; fIt != fEnd; ++fIt ) delete *fIt; if ( !m_frames.isEmpty() ) qCWarning(OkularUiDebug) << "Frames setup changed while a Presentation is in progress."; m_frames.clear(); // create the new frames QVector< Okular::Page * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end(); float screenRatio = (float)m_height / (float)m_width; for ( ; setIt != setEnd; ++setIt ) { PresentationFrame * frame = new PresentationFrame(); frame->page = *setIt; const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations(); QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.begin(), aEnd = annotations.end(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; if ( a->subType() == Okular::Annotation::AMovie ) { Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); VideoWidget * vw = new VideoWidget( movieAnn, movieAnn->movie(), m_document, this ); frame->videoWidgets.insert( movieAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::ARichMedia ) { Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); if ( richMediaAnn->movie() ) { VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), m_document, this ); frame->videoWidgets.insert( richMediaAnn->movie(), vw ); vw->pageInitialized(); } } else if ( a->subType() == Okular::Annotation::AScreen ) { const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation( screenAnn ); if ( movie ) { VideoWidget * vw = new VideoWidget( screenAnn, movie, m_document, this ); frame->videoWidgets.insert( movie, vw ); vw->pageInitialized(); } } } frame->recalcGeometry( m_width, m_height, screenRatio ); // add the frame to the vector m_frames.push_back( frame ); } // get metadata from the document m_metaStrings.clear(); const Okular::DocumentInfo info = m_document->documentInfo( QSet() << Okular::DocumentInfo::Title << Okular::DocumentInfo::Author ); if ( !info.get( Okular::DocumentInfo::Title ).isNull() ) m_metaStrings += i18n( "Title: %1", info.get( Okular::DocumentInfo::Title ) ); if ( !info.get( Okular::DocumentInfo::Author ).isNull() ) m_metaStrings += i18n( "Author: %1", info.get( Okular::DocumentInfo::Author ) ); m_metaStrings += i18n( "Pages: %1", m_document->pages() ); m_metaStrings += i18n( "Click to begin" ); m_isSetup = true; } void PresentationWidget::notifyViewportChanged( bool /*smoothMove*/ ) { // display the current page changePage( m_document->viewport().pageNumber ); // auto advance to the next page if set startAutoChangeTimer(); } void PresentationWidget::notifyPageChanged( int pageNumber, int changedFlags ) { // if we are blocking the notifications, do nothing if ( m_blockNotifications ) return; // check if it's the last requested pixmap. if so update the widget. if ( (changedFlags & ( DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights ) ) && pageNumber == m_frameIndex ) generatePage( changedFlags & ( DocumentObserver::Annotations | DocumentObserver::Highlights ) ); } void PresentationWidget::notifyCurrentPageChanged( int previousPage, int currentPage ) { if ( previousPage != -1 ) { // stop video playback Q_FOREACH ( VideoWidget *vw, m_frames[ previousPage ]->videoWidgets ) { vw->stop(); vw->pageLeft(); } // stop audio playback, if any Okular::AudioPlayer::instance()->stopPlaybacks(); // perform the page closing action, if any if ( m_document->page( previousPage )->pageAction( Okular::Page::Closing ) ) m_document->processAction( m_document->page( previousPage )->pageAction( Okular::Page::Closing ) ); // perform the additional actions of the page's annotations, if any Q_FOREACH ( const Okular::Annotation *annotation, m_document->page( previousPage )->annotations() ) { Okular::Action *action = nullptr; if ( annotation->subType() == Okular::Annotation::AScreen ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageClosing ); else if ( annotation->subType() == Okular::Annotation::AWidget ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageClosing ); if ( action ) m_document->processAction( action ); } } if ( currentPage != -1 ) { m_frameIndex = currentPage; // check if pixmap exists or else request it PresentationFrame * frame = m_frames[ m_frameIndex ]; int pixW = frame->geometry.width(); int pixH = frame->geometry.height(); bool signalsBlocked = m_pagesEdit->signalsBlocked(); m_pagesEdit->blockSignals( true ); m_pagesEdit->setText( QString::number( m_frameIndex + 1 ) ); m_pagesEdit->blockSignals( signalsBlocked ); // if pixmap not inside the Okular::Page we request it and wait for // notifyPixmapChanged call or else we can proceed to pixmap generation if ( !frame->page->hasPixmap( this, ceil(pixW * qApp->devicePixelRatio()), ceil(pixH * qApp->devicePixelRatio()) ) ) { requestPixmaps(); } else { // make the background pixmap generatePage(); } // perform the page opening action, if any if ( m_document->page( m_frameIndex )->pageAction( Okular::Page::Opening ) ) m_document->processAction( m_document->page( m_frameIndex )->pageAction( Okular::Page::Opening ) ); // perform the additional actions of the page's annotations, if any Q_FOREACH ( const Okular::Annotation *annotation, m_document->page( m_frameIndex )->annotations() ) { Okular::Action *action = nullptr; if ( annotation->subType() == Okular::Annotation::AScreen ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageOpening ); else if ( annotation->subType() == Okular::Annotation::AWidget ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageOpening ); if ( action ) m_document->processAction( action ); } // start autoplay video playback Q_FOREACH ( VideoWidget *vw, m_frames[ m_frameIndex ]->videoWidgets ) vw->pageEntered(); } } bool PresentationWidget::canUnloadPixmap( int pageNumber ) const { if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal ) { // can unload all pixmaps except for the currently visible one return pageNumber != m_frameIndex; } else { // can unload all pixmaps except for the currently visible one, previous and next return qAbs(pageNumber - m_frameIndex) <= 1; } } void PresentationWidget::setupActions() { addAction( m_ac->action( QStringLiteral("first_page") ) ); addAction( m_ac->action( QStringLiteral("last_page") ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::Prior ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::Next ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::DocumentBack ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::DocumentForward ) ) ) ); QAction *action = m_ac->action( QStringLiteral("switch_blackscreen_mode") ); connect(action, &QAction::toggled, this, &PresentationWidget::toggleBlackScreenMode); action->setEnabled( true ); addAction( action ); } void PresentationWidget::setPlayPauseIcon() { QAction *playPauseAction = m_ac->action( QStringLiteral("presentation_play_pause") ); if ( m_advanceSlides ) { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-pause") ) ); playPauseAction->setToolTip( i18nc( "For Presentation", "Pause" ) ); } else { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-start") ) ); playPauseAction->setToolTip( i18nc( "For Presentation", "Play" ) ); } } // bool PresentationWidget::event( QEvent * e ) { if ( e->type() == QEvent::Gesture ) return gestureEvent(static_cast(e)); if ( e->type() == QEvent::ToolTip ) { QHelpEvent * he = (QHelpEvent*)e; QRect r; const Okular::Action * link = getLink( he->x(), he->y(), &r ); if ( link ) { QString tip = link->actionTip(); if ( !tip.isEmpty() ) QToolTip::showText( he->globalPos(), tip, this, r ); } e->accept(); return true; } else // do not stop the event return QWidget::event( e ); } bool PresentationWidget::gestureEvent( QGestureEvent * event ) { // Swiping left or right on a touch screen will go to the previous or next slide, respectively. // The precise gesture is the standard Qt swipe: with three(!) fingers. if (QGesture *swipe = event->gesture(Qt::SwipeGesture)) { QSwipeGesture * swipeEvent = static_cast(swipe); if (swipeEvent->state() == Qt::GestureFinished) { if (swipeEvent->horizontalDirection() == QSwipeGesture::Left) { slotPrevPage(); event->accept(); return true; } if (swipeEvent->horizontalDirection() == QSwipeGesture::Right) { slotNextPage(); event->accept(); return true; } } } return false; } void PresentationWidget::keyPressEvent( QKeyEvent * e ) { if ( !m_isSetup ) return; switch ( e->key() ) { case Qt::Key_Left: case Qt::Key_Backspace: case Qt::Key_PageUp: case Qt::Key_Up: slotPrevPage(); break; case Qt::Key_Right: case Qt::Key_Space: case Qt::Key_PageDown: case Qt::Key_Down: slotNextPage(); break; case Qt::Key_Home: slotFirstPage(); break; case Qt::Key_End: slotLastPage(); break; case Qt::Key_Escape: if ( !m_topBar->isHidden() ) showTopBar( false ); else close(); break; } } void PresentationWidget::wheelEvent( QWheelEvent * e ) { if ( !m_isSetup ) return; // performance note: don't remove the clipping int div = e->delta() / 120; if ( div > 0 ) { if ( div > 3 ) div = 3; while ( div-- ) slotPrevPage(); } else if ( div < 0 ) { if ( div < -3 ) div = -3; while ( div++ ) slotNextPage(); } } void PresentationWidget::mousePressEvent( QMouseEvent * e ) { if ( !m_isSetup ) return; if ( m_drawingEngine ) { QRect r = routeMouseDrawingEvent( e ); if ( r.isValid() ) { m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() ); update( m_drawingRect ); } return; } // pressing left button if ( e->button() == Qt::LeftButton ) { // if pressing on a link, skip other checks if ( ( m_pressedLink = getLink( e->x(), e->y() ) ) ) return; const Okular::Annotation *annotation = getAnnotation( e->x(), e->y() ); if ( annotation ) { if ( annotation->subType() == Okular::Annotation::AMovie ) { const Okular::MovieAnnotation *movieAnnotation = static_cast( annotation ); VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movieAnnotation->movie() ); vw->show(); vw->play(); return; } else if ( annotation->subType() == Okular::Annotation::ARichMedia ) { const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast( annotation ); VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( richMediaAnnotation->movie() ); vw->show(); vw->play(); return; } else if ( annotation->subType() == Okular::Annotation::AScreen ) { m_document->processAction( static_cast( annotation )->action() ); return; } } // handle clicking on top-right overlay if ( !( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) && m_overlayGeometry.contains( e->pos() ) ) { overlayClick( e->pos() ); return; } - m_goToNextPageOnRelease = true; + // Actual mouse press events always lead to the next page page + if ( e->source() == Qt::MouseEventNotSynthesized ) + { + m_goToNextPageOnRelease = true; + } + // Touch events may lead to the previous or next page + else if ( Okular::Settings::slidesTapNavigation() != Okular::Settings::EnumSlidesTapNavigation::Disabled ) + { + switch ( Okular::Settings::slidesTapNavigation() ) + { + case Okular::Settings::EnumSlidesTapNavigation::ForwardBackward: + { + if ( e->x() < ( geometry().width()/2 ) ) + { + m_goToPreviousPageOnRelease = true; + } + else + { + m_goToNextPageOnRelease = true; + } + break; + } + case Okular::Settings::EnumSlidesTapNavigation::Forward: + { + m_goToNextPageOnRelease = true; + break; + } + case Okular::Settings::EnumSlidesTapNavigation::Disabled: + { + // Do Nothing + } + } + } } // pressing the "move forward" mouse button: unlike the left button this // always means "show next page", so we unconditionally delegate to that // action on mouse button press else if ( e->button() == Qt::ForwardButton ) { slotNextPage(); } // pressing right or backward button else if ( e->button() == Qt::RightButton || e->button() == Qt::BackButton ) slotPrevPage(); } void PresentationWidget::mouseReleaseEvent( QMouseEvent * e ) { if ( m_drawingEngine ) { routeMouseDrawingEvent( e ); return; } // if releasing on the same link we pressed over, execute it if ( m_pressedLink && e->button() == Qt::LeftButton ) { const Okular::Action * link = getLink( e->x(), e->y() ); if ( link == m_pressedLink ) m_document->processAction( link ); m_pressedLink = nullptr; } + if ( m_goToPreviousPageOnRelease ) { + slotPrevPage(); + m_goToPreviousPageOnRelease = false; + } + if ( m_goToNextPageOnRelease ) { slotNextPage(); m_goToNextPageOnRelease = false; } } void PresentationWidget::mouseMoveEvent( QMouseEvent * e ) { // safety check if ( !m_isSetup ) return; // update cursor and tooltip if hovering a link if ( !m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden ) testCursorOnLink( e->x(), e->y() ); if ( !m_topBar->isHidden() ) { // hide a shown bar when exiting the area if ( e->y() > ( m_topBar->height() + 1 ) ) { showTopBar( false ); setFocus( Qt::OtherFocusReason ); } } else { if ( m_drawingEngine && e->buttons() != Qt::NoButton ) { QRect r = routeMouseDrawingEvent( e ); if ( r.isValid() ) { m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() ); update( m_drawingRect ); } } else { // show the bar if reaching top 2 pixels if ( e->y() <= 1 ) showTopBar( true ); // handle "dragging the wheel" if clicking on its geometry else if ( ( QApplication::mouseButtons() & Qt::LeftButton ) && m_overlayGeometry.contains( e->pos() ) ) overlayClick( e->pos() ); } } } void PresentationWidget::paintEvent( QPaintEvent * pe ) { qreal dpr = devicePixelRatioF(); if ( m_inBlackScreenMode ) { QPainter painter( this ); painter.fillRect( pe->rect(), Qt::black ); return; } if ( !m_isSetup ) { m_width = width(); m_height = height(); connect(m_document, &Okular::Document::linkFind, this, &PresentationWidget::slotFind); // register this observer in document. events will come immediately m_document->addObserver( this ); // show summary if requested if ( Okular::Settings::slidesShowSummary() ) generatePage(); } // check painting rect consistency QRect r = pe->rect().intersected( QRect( QPoint( 0, 0 ), geometry().size() ) ); if ( r.isNull() ) return; if ( m_lastRenderedPixmap.isNull() ) { QPainter painter( this ); painter.fillRect( pe->rect(), Okular::Settings::slidesBackgroundColor() ); return; } // blit the pixmap to the screen QVector allRects = pe->region().rects(); uint numRects = allRects.count(); QPainter painter( this ); for ( uint i = 0; i < numRects; i++ ) { const QRect & r = allRects[i]; if ( !r.isValid() ) continue; #ifdef ENABLE_PROGRESS_OVERLAY const QRect dR(QRectF(r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr).toAlignedRect()); if ( Okular::Settings::slidesShowProgress() && r.intersects( m_overlayGeometry ) ) { // backbuffer the overlay operation QPixmap backPixmap( dR.size() ); backPixmap.setDevicePixelRatio( dpr ); QPainter pixPainter( &backPixmap ); // first draw the background on the backbuffer pixPainter.drawPixmap( QPoint(0,0), m_lastRenderedPixmap, dR ); // then blend the overlay (a piece of) over the background QRect ovr = m_overlayGeometry.intersected( r ); pixPainter.drawPixmap( (ovr.left() - r.left()), (ovr.top() - r.top()), m_lastRenderedOverlay, (ovr.left() - m_overlayGeometry.left()) * dpr, (ovr.top() - m_overlayGeometry.top()) * dpr, ovr.width() * dpr, ovr.height() * dpr ); // finally blit the pixmap to the screen pixPainter.end(); const QRect backPixmapRect = backPixmap.rect(); const QRect dBackPixmapRect(QRectF(backPixmapRect.x() * dpr, backPixmapRect.y() * dpr, backPixmapRect.width() * dpr, backPixmapRect.height() * dpr).toAlignedRect()); painter.drawPixmap( r.topLeft(), backPixmap, dBackPixmapRect ); } else #endif // copy the rendered pixmap to the screen painter.drawPixmap( r.topLeft(), m_lastRenderedPixmap, dR ); } // paint drawings if ( m_frameIndex != -1 ) { painter.save(); const QRect & geom = m_frames[ m_frameIndex ]->geometry; QPixmap pm( geom.size() ); pm.fill( Qt::transparent ); QPainter pmPainter( &pm ); pmPainter.setRenderHints( QPainter::Antialiasing ); foreach ( const SmoothPath &drawing, m_frames[ m_frameIndex ]->drawings ) drawing.paint( &pmPainter, geom.width(), geom.height() ); if ( m_drawingEngine && m_drawingRect.intersects( pe->rect() ) ) m_drawingEngine->paint( &pmPainter, geom.width(), geom.height(), m_drawingRect.intersected( pe->rect() ) ); painter.setRenderHints( QPainter::Antialiasing ); painter.drawPixmap( geom.topLeft() , pm ); painter.restore(); } painter.end(); } void PresentationWidget::resizeEvent( QResizeEvent *re ) { // qCDebug(OkularUiDebug) << re->oldSize() << "=>" << re->size(); if ( re->oldSize() == QSize( -1, -1 ) ) return; m_screen = QApplication::desktop()->screenNumber( this ); applyNewScreenSize( re->oldSize() ); } void PresentationWidget::leaveEvent( QEvent * e ) { Q_UNUSED( e ) if ( !m_topBar->isHidden() ) { showTopBar( false ); } } // const void * PresentationWidget::getObjectRect( Okular::ObjectRect::ObjectType type, int x, int y, QRect * geometry ) const { // no links on invalid pages if ( geometry && !geometry->isNull() ) geometry->setRect( 0, 0, 0, 0 ); if ( m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size() ) return nullptr; // get frame, page and geometry const PresentationFrame * frame = m_frames[ m_frameIndex ]; const Okular::Page * page = frame->page; const QRect & frameGeometry = frame->geometry; // compute normalized x and y double nx = (double)(x - frameGeometry.left()) / (double)frameGeometry.width(); double ny = (double)(y - frameGeometry.top()) / (double)frameGeometry.height(); // no links outside the pages if ( nx < 0 || nx > 1 || ny < 0 || ny > 1 ) return nullptr; // check if 1) there is an object and 2) it's a link const QRect d = QApplication::desktop()->screenGeometry( m_screen ); const Okular::ObjectRect * object = page->objectRect( type, nx, ny, d.width(), d.height() ); if ( !object ) return nullptr; // compute link geometry if destination rect present if ( geometry ) { *geometry = object->boundingRect( frameGeometry.width(), frameGeometry.height() ); geometry->translate( frameGeometry.left(), frameGeometry.top() ); } // return the link pointer return object->object(); } const Okular::Action * PresentationWidget::getLink( int x, int y, QRect * geometry ) const { return reinterpret_cast( getObjectRect( Okular::ObjectRect::Action, x, y, geometry ) ); } const Okular::Annotation * PresentationWidget::getAnnotation( int x, int y, QRect * geometry ) const { return reinterpret_cast( getObjectRect( Okular::ObjectRect::OAnnotation, x, y, geometry ) ); } void PresentationWidget::testCursorOnLink( int x, int y ) { const Okular::Action * link = getLink( x, y, nullptr ); const Okular::Annotation *annotation = getAnnotation( x, y, nullptr ); const bool needsHandCursor = ( ( link != nullptr ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::AMovie ) ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::ARichMedia ) ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::AScreen ) && ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( annotation ) ) != nullptr ) ) ); // only react on changes (in/out from a link) if ( ( needsHandCursor && !m_handCursor ) || ( !needsHandCursor && m_handCursor ) ) { // change cursor shape m_handCursor = needsHandCursor; setCursor( QCursor( m_handCursor ? Qt::PointingHandCursor : Qt::ArrowCursor ) ); } } void PresentationWidget::overlayClick( const QPoint & position ) { // clicking the progress indicator int xPos = position.x() - m_overlayGeometry.x() - m_overlayGeometry.width() / 2, yPos = m_overlayGeometry.height() / 2 - position.y(); if ( !xPos && !yPos ) return; // compute angle relative to indicator (note coord transformation) float angle = 0.5 + 0.5 * atan2( (double)-xPos, (double)-yPos ) / M_PI; int pageIndex = (int)( angle * ( m_frames.count() - 1 ) + 0.5 ); // go to selected page changePage( pageIndex ); } void PresentationWidget::changePage( int newPage ) { if ( m_showSummaryView ) { m_showSummaryView = false; m_frameIndex = -1; return; } if ( m_frameIndex == newPage ) return; // switch to newPage m_document->setViewportPage( newPage, this ); if ( (Okular::Settings::slidesShowSummary() && !m_showSummaryView) || m_frameIndex == -1 ) notifyCurrentPageChanged( -1, newPage ); } void PresentationWidget::generatePage( bool disableTransition ) { if ( m_lastRenderedPixmap.isNull() ) { qreal dpr = qApp->devicePixelRatio(); m_lastRenderedPixmap = QPixmap( m_width * dpr, m_height * dpr ); m_lastRenderedPixmap.setDevicePixelRatio(dpr); m_previousPagePixmap = QPixmap(); } else { m_previousPagePixmap = m_lastRenderedPixmap; } // opens the painter over the pixmap QPainter pixmapPainter; pixmapPainter.begin( &m_lastRenderedPixmap ); // generate welcome page if ( m_frameIndex == -1 ) generateIntroPage( pixmapPainter ); // generate a normal pixmap with extended margin filling if ( m_frameIndex >= 0 && m_frameIndex < (int)m_document->pages() ) generateContentsPage( m_frameIndex, pixmapPainter ); pixmapPainter.end(); // generate the top-right corner overlay #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() && m_frameIndex != -1 ) generateOverlay(); #endif // start transition on pages that have one if ( !disableTransition && Okular::Settings::slidesTransitionsEnabled() ) { const Okular::PageTransition * transition = m_frameIndex != -1 ? m_frames[ m_frameIndex ]->page->transition() : nullptr; if ( transition ) initTransition( transition ); else { Okular::PageTransition trans = defaultTransition(); initTransition( &trans ); } } else { Okular::PageTransition trans = defaultTransition( Okular::Settings::EnumSlidesTransition::Replace ); initTransition( &trans ); } // update cursor + tooltip if ( !m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden ) { QPoint p = mapFromGlobal( QCursor::pos() ); testCursorOnLink( p.x(), p.y() ); } } void PresentationWidget::generateIntroPage( QPainter & p ) { qreal dpr = qApp->devicePixelRatio(); // use a vertical gray gradient background int blend1 = m_height / 10, blend2 = 9 * m_height / 10; int baseTint = QColor(Qt::gray).red(); for ( int i = 0; i < m_height; i++ ) { int k = baseTint; if ( i < blend1 ) k -= (int)( baseTint * (i-blend1)*(i-blend1) / (float)(blend1 * blend1) ); if ( i > blend2 ) k += (int)( (255-baseTint) * (i-blend2)*(i-blend2) / (float)(blend1 * blend1) ); p.fillRect( 0, i, m_width, 1, QColor( k, k, k ) ); } // draw okular logo in the four corners QPixmap logo = DesktopIcon( QStringLiteral("okular"), 64 * dpr ); logo.setDevicePixelRatio( dpr ); if ( !logo.isNull() ) { p.drawPixmap( 5, 5, logo ); p.drawPixmap( m_width - 5 - logo.width(), 5, logo ); p.drawPixmap( m_width - 5 - logo.width(), m_height - 5 - logo.height(), logo ); p.drawPixmap( 5, m_height - 5 - logo.height(), logo ); } // draw metadata text (the last line is 'click to begin') int strNum = m_metaStrings.count(), strHeight = m_height / ( strNum + 4 ), fontHeight = 2 * strHeight / 3; QFont font( p.font() ); font.setPixelSize( fontHeight ); QFontMetrics metrics( font ); for ( int i = 0; i < strNum; i++ ) { // set a font to fit text width float wScale = (float)metrics.boundingRect( m_metaStrings[i] ).width() / (float)m_width; QFont f( font ); if ( wScale > 1.0 ) f.setPixelSize( (int)( (float)fontHeight / (float)wScale ) ); p.setFont( f ); // text shadow p.setPen( Qt::darkGray ); p.drawText( 2, m_height / 4 + strHeight * i + 2, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i] ); // text body p.setPen( 128 + (127 * i) / strNum ); p.drawText( 0, m_height / 4 + strHeight * i, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i] ); } } void PresentationWidget::generateContentsPage( int pageNum, QPainter & p ) { PresentationFrame * frame = m_frames[ pageNum ]; // translate painter and contents rect QRect geom( frame->geometry ); p.translate( geom.left(), geom.top() ); geom.translate( -geom.left(), -geom.top() ); // draw the page using the shared PagePainter class int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations; PagePainter::paintPageOnPainter( &p, frame->page, this, flags, geom.width(), geom.height(), geom ); // restore painter p.translate( -frame->geometry.left(), -frame->geometry.top() ); // fill unpainted areas with background color QRegion unpainted( QRect( 0, 0, m_width, m_height ) ); QVector rects = unpainted.subtracted( frame->geometry ).rects(); for ( int i = 0; i < rects.count(); i++ ) { const QRect & r = rects[i]; p.fillRect( r, Okular::Settings::slidesBackgroundColor() ); } } // from Arthur - Qt4 - (is defined elsewhere as 'qt_div_255' to not break final compilation) inline int qt_div255(int x) { return (x + (x>>8) + 0x80) >> 8; } void PresentationWidget::generateOverlay() { #ifdef ENABLE_PROGRESS_OVERLAY qreal dpr = qApp->devicePixelRatio(); // calculate overlay geometry and resize pixmap if needed int side = m_width / 16; m_overlayGeometry.setRect( m_width - side - 4, 4, side, side ); // note: to get a sort of antialiasing, we render the pixmap double sized // and the resulting image is smoothly scaled down. So here we open a // painter on the double sized pixmap. side *= 2; QPixmap doublePixmap( side * dpr, side * dpr ); doublePixmap.setDevicePixelRatio( dpr ); doublePixmap.fill( Qt::black ); QPainter pixmapPainter( &doublePixmap ); pixmapPainter.setRenderHints( QPainter::Antialiasing ); // draw PIE SLICES in blue levels (the levels will then be the alpha component) int pages = m_document->pages(); if ( pages > 28 ) { // draw continuous slices int degrees = (int)( 360 * (float)(m_frameIndex + 1) / (float)pages ); pixmapPainter.setPen( 0x05 ); pixmapPainter.setBrush( QColor( 0x40 ) ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, (360-degrees)*16 ); pixmapPainter.setPen( 0x40 ); pixmapPainter.setBrush( QColor( 0xF0 ) ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, -degrees*16 ); } else { // draw discrete slices float oldCoord = -90; for ( int i = 0; i < pages; i++ ) { float newCoord = -90 + 360 * (float)(i + 1) / (float)pages; pixmapPainter.setPen( i <= m_frameIndex ? 0x40 : 0x05 ); pixmapPainter.setBrush( QColor( i <= m_frameIndex ? 0xF0 : 0x40 ) ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, (int)( -16*(oldCoord + 1) ), (int)( -16*(newCoord - (oldCoord + 2)) ) ); oldCoord = newCoord; } } int circleOut = side / 4; pixmapPainter.setPen( Qt::black ); pixmapPainter.setBrush( Qt::black ); pixmapPainter.drawEllipse( circleOut, circleOut, side - 2*circleOut, side - 2*circleOut ); // draw TEXT using maximum opacity QFont f( pixmapPainter.font() ); f.setPixelSize( side / 4 ); pixmapPainter.setFont( f ); pixmapPainter.setPen( 0xFF ); // use a little offset to prettify output pixmapPainter.drawText( 2, 2, side, side, Qt::AlignCenter, QString::number( m_frameIndex + 1 ) ); // end drawing pixmap and halve image pixmapPainter.end(); QImage image( doublePixmap.toImage().scaled( (side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); image.setDevicePixelRatio( dpr ); image = image.convertToFormat( QImage::Format_ARGB32 ); image.setDevicePixelRatio( dpr ); // draw circular shadow using the same technique doublePixmap.fill( Qt::black ); pixmapPainter.begin( &doublePixmap ); pixmapPainter.setPen( 0x40 ); pixmapPainter.setBrush( QColor( 0x80 ) ); pixmapPainter.drawEllipse( 0, 0, side, side ); pixmapPainter.end(); QImage shadow( doublePixmap.toImage().scaled( (side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); shadow.setDevicePixelRatio( dpr ); // generate a 2 colors pixmap using mixing shadow (made with highlight color) // and image (made with highlightedText color) QPalette pal = palette(); QColor color = pal.color( QPalette::Active, QPalette::HighlightedText ); int red = color.red(), green = color.green(), blue = color.blue(); color = pal.color( QPalette::Active, QPalette::Highlight ); int sRed = color.red(), sGreen = color.green(), sBlue = color.blue(); // pointers unsigned int * data = (unsigned int *)image.bits(), * shadowData = (unsigned int *)shadow.bits(), pixels = image.width() * image.height(); // cache data (reduce computation time to 26%!) int c1 = -1, c2 = -1, cR = 0, cG = 0, cB = 0, cA = 0; // foreach pixel for( unsigned int i = 0; i < pixels; ++i ) { // alpha for shadow and image int shadowAlpha = shadowData[i] & 0xFF, srcAlpha = data[i] & 0xFF; // cache values if ( srcAlpha != c1 || shadowAlpha != c2 ) { c1 = srcAlpha; c2 = shadowAlpha; // fuse color components and alpha value of image over shadow data[i] = qRgba( cR = qt_div255( srcAlpha * red + (255 - srcAlpha) * sRed ), cG = qt_div255( srcAlpha * green + (255 - srcAlpha) * sGreen ), cB = qt_div255( srcAlpha * blue + (255 - srcAlpha) * sBlue ), cA = qt_div255( srcAlpha * srcAlpha + (255 - srcAlpha) * shadowAlpha ) ); } else data[i] = qRgba( cR, cG, cB, cA ); } m_lastRenderedOverlay = QPixmap::fromImage( image ); m_lastRenderedOverlay.setDevicePixelRatio( dpr ); // start the autohide timer //repaint( m_overlayGeometry ); // toggle with next line update( m_overlayGeometry ); m_overlayHideTimer->start( 2500 ); #endif } QRect PresentationWidget::routeMouseDrawingEvent( QMouseEvent * e ) { if ( m_frameIndex == -1 ) // Can't draw on the summary page return QRect(); const QRect & geom = m_frames[ m_frameIndex ]->geometry; const Okular::Page * page = m_frames[ m_frameIndex ]->page; AnnotatorEngine::EventType eventType; AnnotatorEngine::Button button; // figure out the event type and button AnnotatorEngine::decodeEvent( e, &eventType, &button ); static bool hasclicked = false; if ( eventType == AnnotatorEngine::Press ) hasclicked = true; double nX = ( (double)e->x() - (double)geom.left() ) / (double)geom.width(); double nY = ( (double)e->y() - (double)geom.top() ) / (double)geom.height(); QRect ret; bool isInside = nX >= 0 && nX < 1 && nY >= 0 && nY < 1; if ( hasclicked && !isInside ) { // Fake a move to the last border pos nX = qBound(0., nX, 1.); nY = qBound(0., nY, 1.); m_drawingEngine->event( AnnotatorEngine::Move, button, nX, nY, geom.width(), geom.height(), page ); // Fake a release in the following lines eventType = AnnotatorEngine::Release; isInside = true; } else if ( !hasclicked && isInside ) { // we're coming from the outside, pretend we started clicking at the closest border if ( nX < ( 1 - nX ) && nX < nY && nX < ( 1 - nY ) ) nX = 0; else if ( nY < ( 1 - nY ) && nY < nX && nY < ( 1 - nX ) ) nY = 0; else if ( ( 1 - nX ) < nX && ( 1 - nX ) < nY && ( 1 - nX ) < ( 1 - nY ) ) nX = 1; else nY = 1; hasclicked = true; eventType = AnnotatorEngine::Press; } if ( hasclicked && isInside ) { ret = m_drawingEngine->event( eventType, button, nX, nY, geom.width(), geom.height(), page ); } if ( eventType == AnnotatorEngine::Release ) { hasclicked = false; } if ( m_drawingEngine->creationCompleted() ) { // add drawing to current page m_frames[ m_frameIndex ]->drawings << m_drawingEngine->endSmoothPath(); // manually disable and re-enable the pencil mode, so we can do // cleaning of the actual drawer and create a new one just after // that - that gives continuous drawing slotChangeDrawingToolEngine( QDomElement() ); slotChangeDrawingToolEngine( m_currentDrawingToolElement ); // schedule repaint update(); } return ret; } void PresentationWidget::startAutoChangeTimer() { double pageDuration = m_frameIndex >= 0 && m_frameIndex < (int)m_frames.count() ? m_frames[ m_frameIndex ]->page->duration() : -1; if ( m_advanceSlides || pageDuration >= 0.0 ) { double secs; if ( pageDuration < 0.0 ) secs = Okular::SettingsCore::slidesAdvanceTime(); else if ( m_advanceSlides ) secs = qMin( pageDuration, Okular::SettingsCore::slidesAdvanceTime() ); else secs = pageDuration; m_nextPageTimer->start( (int)( secs * 1000 ) ); } } void PresentationWidget::recalcGeometry() { QDesktopWidget *desktop = QApplication::desktop(); const int preferenceScreen = Okular::Settings::slidesScreen(); int screen = 0; if ( preferenceScreen == -2 ) { screen = desktop->screenNumber( m_parentWidget ); } else if ( preferenceScreen == -1 ) { screen = desktop->primaryScreen(); } else if ( preferenceScreen >= 0 && preferenceScreen < desktop->numScreens() ) { screen = preferenceScreen; } else { screen = desktop->screenNumber( m_parentWidget ); Okular::Settings::setSlidesScreen( -2 ); } const QRect screenGeom = desktop->screenGeometry( screen ); // qCDebug(OkularUiDebug) << screen << "=>" << screenGeom; m_screen = screen; setGeometry( screenGeom ); } void PresentationWidget::repositionContent() { const QRect ourGeom = geometry(); // tool bar height in pixels, make it large enough to hold the text fields with the page numbers const int toolBarHeight = m_pagesEdit->height() * 1.5; m_topBar->setGeometry( 0, 0, ourGeom.width(), toolBarHeight ); m_topBar->setIconSize( QSize( toolBarHeight * 0.75, toolBarHeight * 0.75 ) ); } void PresentationWidget::requestPixmaps() { PresentationFrame * frame = m_frames[ m_frameIndex ]; int pixW = frame->geometry.width(); int pixH = frame->geometry.height(); // operation will take long: set busy cursor QApplication::setOverrideCursor( QCursor( Qt::BusyCursor ) ); // request the pixmap QLinkedList< Okular::PixmapRequest * > requests; requests.push_back( new Okular::PixmapRequest( this, m_frameIndex, pixW, pixH, PRESENTATION_PRIO, Okular::PixmapRequest::NoFeature ) ); // restore cursor QApplication::restoreOverrideCursor(); // ask for next and previous page if not in low memory usage setting if ( Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low ) { int pagesToPreload = 1; // If greedy, preload everything if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) pagesToPreload = (int)m_document->pages(); Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload; requestFeatures |= Okular::PixmapRequest::Asynchronous; for( int j = 1; j <= pagesToPreload; j++ ) { int tailRequest = m_frameIndex + j; if ( tailRequest < (int)m_document->pages() ) { PresentationFrame *nextFrame = m_frames[ tailRequest ]; pixW = nextFrame->geometry.width(); pixH = nextFrame->geometry.height(); if ( !nextFrame->page->hasPixmap( this, pixW, pixH ) ) requests.push_back( new Okular::PixmapRequest( this, tailRequest, pixW, pixH, PRESENTATION_PRELOAD_PRIO, requestFeatures ) ); } int headRequest = m_frameIndex - j; if ( headRequest >= 0 ) { PresentationFrame *prevFrame = m_frames[ headRequest ]; pixW = prevFrame->geometry.width(); pixH = prevFrame->geometry.height(); if ( !prevFrame->page->hasPixmap( this, pixW, pixH ) ) requests.push_back( new Okular::PixmapRequest( this, headRequest, pixW, pixH, PRESENTATION_PRELOAD_PRIO, requestFeatures ) ); } // stop if we've already reached both ends of the document if ( headRequest < 0 && tailRequest >= (int)m_document->pages() ) break; } } m_document->requestPixmaps( requests ); } void PresentationWidget::slotNextPage() { int nextIndex = m_frameIndex + 1; // loop when configured if ( nextIndex == m_frames.count() && Okular::Settings::slidesLoop() ) nextIndex = 0; if ( nextIndex < m_frames.count() ) { // go to next page changePage( nextIndex ); // auto advance to the next page if set startAutoChangeTimer(); } else { #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() ) generateOverlay(); #endif if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); m_lastRenderedPixmap = m_currentPagePixmap; update(); } } // we need the setFocus() call here to let KCursor::autoHide() work correctly setFocus(); } void PresentationWidget::slotPrevPage() { if ( m_frameIndex > 0 ) { // go to previous page changePage( m_frameIndex - 1 ); // auto advance to the next page if set startAutoChangeTimer(); } else { #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() ) generateOverlay(); #endif if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); m_lastRenderedPixmap = m_currentPagePixmap; update(); } } } void PresentationWidget::slotFirstPage() { changePage( 0 ); } void PresentationWidget::slotLastPage() { changePage( (int)m_frames.count() - 1 ); } void PresentationWidget::slotHideOverlay() { QRect geom( m_overlayGeometry ); m_overlayGeometry.setCoords( 0, 0, -1, -1 ); update( geom ); } void PresentationWidget::slotTransitionStep() { switch( m_currentTransition.type() ) { case Okular::PageTransition::Fade: { QPainter pixmapPainter; m_currentPixmapOpacity += 1.0 / m_transitionSteps; m_lastRenderedPixmap = QPixmap( m_lastRenderedPixmap.size() ); m_lastRenderedPixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); m_lastRenderedPixmap.fill( Qt::transparent ); pixmapPainter.begin( &m_lastRenderedPixmap ); pixmapPainter.setCompositionMode( QPainter::CompositionMode_Source ); pixmapPainter.setOpacity( 1 - m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_previousPagePixmap ); pixmapPainter.setOpacity( m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_currentPagePixmap ); update(); if( m_currentPixmapOpacity >= 1 ) return; } break; default: { if ( m_transitionRects.empty() ) { // it's better to fix the transition to cover the whole screen than // enabling the following line that wastes cpu for nothing //update(); return; } for ( int i = 0; i < m_transitionMul && !m_transitionRects.empty(); i++ ) { update( m_transitionRects.first() ); m_transitionRects.pop_front(); } } break; } m_transitionTimer->start( m_transitionDelay ); } void PresentationWidget::slotDelayedEvents() { recalcGeometry(); repositionContent(); if ( m_screenSelect ) { m_screenSelect->setCurrentItem( m_screen ); connect( m_screenSelect->selectableActionGroup(), &QActionGroup::triggered, this, &PresentationWidget::chooseScreen ); } // show widget and take control show(); setWindowState( windowState() | Qt::WindowFullScreen ); connect( QApplication::desktop(), &QDesktopWidget::resized, this, &PresentationWidget::screenResized ); // inform user on how to exit from presentation mode KMessageBox::information( this, i18n("There are two ways of exiting presentation mode, you can press either ESC key or click with the quit button that appears when placing the mouse in the top-right corner. Of course you can cycle windows (Alt+TAB by default)"), QString(), QStringLiteral("presentationInfo") ); } void PresentationWidget::slotPageChanged() { bool ok = true; int p = m_pagesEdit->text().toInt( &ok ); if ( !ok ) return; changePage( p - 1 ); } void PresentationWidget::slotChangeDrawingToolEngine( const QDomElement &element ) { if ( element.isNull() ) { delete m_drawingEngine; m_drawingEngine = nullptr; m_drawingRect = QRect(); setCursor( Qt::ArrowCursor ); } else { m_drawingEngine = new SmoothPathEngine( element ); setCursor( QCursor( QPixmap( QStringLiteral("pencil") ), Qt::ArrowCursor ) ); m_currentDrawingToolElement = element; } } void PresentationWidget::slotAddDrawingToolActions() { DrawingToolActions *drawingToolActions = qobject_cast(sender()); foreach(QAction *action, drawingToolActions->actions()) { action->setEnabled( true ); m_topBar->addAction( action ); addAction( action ); } } void PresentationWidget::clearDrawings() { if ( m_frameIndex != -1 ) m_frames[ m_frameIndex ]->drawings.clear(); update(); } void PresentationWidget::screenResized( int screen ) { // we can ignore if a screen was resized in the case the screen is not // where we are on if ( screen != m_screen ) return; setScreen( screen ); } void PresentationWidget::chooseScreen( QAction *act ) { if ( !act || act->data().type() != QVariant::Int ) return; const int newScreen = act->data().toInt(); setScreen( newScreen ); } void PresentationWidget::toggleBlackScreenMode( bool ) { m_inBlackScreenMode = !m_inBlackScreenMode; update(); } void PresentationWidget::setScreen( int newScreen ) { const QRect screenGeom = QApplication::desktop()->screenGeometry( newScreen ); const QSize oldSize = size(); // qCDebug(OkularUiDebug) << newScreen << "=>" << screenGeom; m_screen = newScreen; setGeometry( screenGeom ); applyNewScreenSize( oldSize ); } void PresentationWidget::applyNewScreenSize( const QSize & oldSize ) { repositionContent(); // if by chance the new screen has the same resolution of the previous, // do not invalidate pixmaps and such.. if ( size() == oldSize ) return; m_width = width(); m_height = height(); // update the frames QVector< PresentationFrame * >::const_iterator fIt = m_frames.constBegin(), fEnd = m_frames.constEnd(); const float screenRatio = (float)m_height / (float)m_width; for ( ; fIt != fEnd; ++fIt ) { (*fIt)->recalcGeometry( m_width, m_height, screenRatio ); } if ( m_frameIndex != -1 ) { // ugliness alarm! const_cast< Okular::Page * >( m_frames[ m_frameIndex ]->page )->deletePixmap( this ); // force the regeneration of the pixmap m_lastRenderedPixmap = QPixmap(); m_blockNotifications = true; requestPixmaps(); m_blockNotifications = false; } if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); } generatePage( true /* no transitions */ ); } void PresentationWidget::inhibitPowerManagement() { #ifdef Q_OS_LINUX QString reason = i18nc( "Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a presentation" ); if (!m_screenInhibitCookie) { QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver", "Inhibit"); message << QCoreApplication::applicationName(); message << reason; QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(message); reply.waitForFinished(); if (reply.isValid()) { m_screenInhibitCookie = reply.value(); qCDebug(OkularUiDebug) << "Screen inhibition cookie" << m_screenInhibitCookie; } else { qCWarning(OkularUiDebug) << "Unable to inhibit screensaver" << reply.error(); } } if (m_sleepInhibitFd != -1) { QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("Inhibit") ); message << QStringLiteral("sleep"); message << QCoreApplication::applicationName(); message << reason; message << QStringLiteral("block"); QDBusPendingReply reply = QDBusConnection::systemBus().asyncCall(message); reply.waitForFinished(); if (reply.isValid()) { m_sleepInhibitFd = dup(reply.value().fileDescriptor()); } else { qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error(); } } #endif } void PresentationWidget::allowPowerManagement() { #ifdef Q_OS_LINUX if (m_sleepInhibitFd != -1) { ::close(m_sleepInhibitFd); m_sleepInhibitFd = -1; } if (m_screenInhibitCookie) { QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver", "UnInhibit"); message << m_screenInhibitCookie; QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(message); reply.waitForFinished(); m_screenInhibitCookie = 0; } #endif } void PresentationWidget::showTopBar( bool show ) { if ( show ) { m_topBar->show(); // Don't autohide the mouse cursor if it's over the toolbar if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, false ); } // Always show a cursor when topBar is visible if ( !m_drawingEngine ) { setCursor( QCursor( Qt::ArrowCursor ) ); } } else { m_topBar->hide(); // Reenable autohide if need be when leaving the toolbar if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, true ); } // Or hide the cursor again if hidden cursor is enabled else if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) { // Don't hide the cursor if drawing mode is on if ( !m_drawingEngine ) { setCursor( QCursor( Qt::BlankCursor ) ); } } } // Make sure mouse tracking isn't off after the KCursor::setAutoHideCursor() calls setMouseTracking( true ); } void PresentationWidget::slotFind() { if ( !m_searchBar ) { m_searchBar = new PresentationSearchBar( m_document, this, this ); m_searchBar->forceSnap(); } m_searchBar->focusOnSearchEdit(); m_searchBar->show(); } const Okular::PageTransition PresentationWidget::defaultTransition() const { return defaultTransition( Okular::Settings::slidesTransition() ); } const Okular::PageTransition PresentationWidget::defaultTransition( int type ) const { switch ( type ) { case Okular::Settings::EnumSlidesTransition::BlindsHorizontal: { Okular::PageTransition transition( Okular::PageTransition::Blinds ); transition.setAlignment( Okular::PageTransition::Horizontal ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BlindsVertical: { Okular::PageTransition transition( Okular::PageTransition::Blinds ); transition.setAlignment( Okular::PageTransition::Vertical ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BoxIn: { Okular::PageTransition transition( Okular::PageTransition::Box ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BoxOut: { Okular::PageTransition transition( Okular::PageTransition::Box ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Dissolve: { return Okular::PageTransition( Okular::PageTransition::Dissolve ); break; } case Okular::Settings::EnumSlidesTransition::GlitterDown: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 270 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::GlitterRight: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 0 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::GlitterRightDown: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 315 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Random: { return defaultTransition( KRandom::random() % 18 ); break; } case Okular::Settings::EnumSlidesTransition::SplitHorizontalIn: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Horizontal ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitHorizontalOut: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Horizontal ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitVerticalIn: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Vertical ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitVerticalOut: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Vertical ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeDown: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 270 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeRight: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 0 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeLeft: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 180 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeUp: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 90 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Fade: { return Okular::PageTransition( Okular::PageTransition::Fade ); break; } case Okular::Settings::EnumSlidesTransition::Replace: default: return Okular::PageTransition( Okular::PageTransition::Replace ); break; } // should not happen, just make gcc happy return Okular::PageTransition(); } /** ONLY the TRANSITIONS GENERATION function from here on **/ void PresentationWidget::initTransition( const Okular::PageTransition *transition ) { // if it's just a 'replace' transition, repaint the screen if ( transition->type() == Okular::PageTransition::Replace ) { update(); return; } const bool isInward = transition->direction() == Okular::PageTransition::Inward; const bool isHorizontal = transition->alignment() == Okular::PageTransition::Horizontal; const float totalTime = transition->duration(); m_transitionRects.clear(); m_currentTransition = *transition; m_currentPagePixmap = m_lastRenderedPixmap; switch( transition->type() ) { // split: horizontal / vertical and inward / outward case Okular::PageTransition::Split: { const int steps = isHorizontal ? 100 : 75; if ( isHorizontal ) { if ( isInward ) { int xPosition = 0; for ( int i = 0; i < steps; i++ ) { int xNext = ((i + 1) * m_width) / (2 * steps); m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) ); m_transitionRects.push_back( QRect( m_width - xNext, 0, xNext - xPosition, m_height ) ); xPosition = xNext; } } else { int xPosition = m_width / 2; for ( int i = 0; i < steps; i++ ) { int xNext = ((steps - (i + 1)) * m_width) / (2 * steps); m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) ); m_transitionRects.push_back( QRect( m_width - xPosition, 0, xPosition - xNext, m_height ) ); xPosition = xNext; } } } else { if ( isInward ) { int yPosition = 0; for ( int i = 0; i < steps; i++ ) { int yNext = ((i + 1) * m_height) / (2 * steps); m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) ); m_transitionRects.push_back( QRect( 0, m_height - yNext, m_width, yNext - yPosition ) ); yPosition = yNext; } } else { int yPosition = m_height / 2; for ( int i = 0; i < steps; i++ ) { int yNext = ((steps - (i + 1)) * m_height) / (2 * steps); m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) ); m_transitionRects.push_back( QRect( 0, m_height - yPosition, m_width, yPosition - yNext ) ); yPosition = yNext; } } } m_transitionMul = 2; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // blinds: horizontal(l-to-r) / vertical(t-to-b) case Okular::PageTransition::Blinds: { const int blinds = isHorizontal ? 8 : 6; const int steps = m_width / (4 * blinds); if ( isHorizontal ) { int xPosition[ 8 ]; for ( int b = 0; b < blinds; b++ ) xPosition[ b ] = (b * m_width) / blinds; for ( int i = 0; i < steps; i++ ) { int stepOffset = (int)( ((float)i * (float)m_width) / ((float)blinds * (float)steps) ); for ( int b = 0; b < blinds; b++ ) { m_transitionRects.push_back( QRect( xPosition[ b ], 0, stepOffset, m_height ) ); xPosition[ b ] = stepOffset + (b * m_width) / blinds; } } } else { int yPosition[ 6 ]; for ( int b = 0; b < blinds; b++ ) yPosition[ b ] = (b * m_height) / blinds; for ( int i = 0; i < steps; i++ ) { int stepOffset = (int)( ((float)i * (float)m_height) / ((float)blinds * (float)steps) ); for ( int b = 0; b < blinds; b++ ) { m_transitionRects.push_back( QRect( 0, yPosition[ b ], m_width, stepOffset ) ); yPosition[ b ] = stepOffset + (b * m_height) / blinds; } } } m_transitionMul = blinds; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // box: inward / outward case Okular::PageTransition::Box: { const int steps = m_width / 10; if ( isInward ) { int L = 0, T = 0, R = m_width, B = m_height; for ( int i = 0; i < steps; i++ ) { // compute shrunk box coords int newL = ((i + 1) * m_width) / (2 * steps); int newT = ((i + 1) * m_height) / (2 * steps); int newR = m_width - newL; int newB = m_height - newT; // add left, right, topcenter, bottomcenter rects m_transitionRects.push_back( QRect( L, T, newL - L, B - T ) ); m_transitionRects.push_back( QRect( newR, T, R - newR, B - T ) ); m_transitionRects.push_back( QRect( newL, T, newR - newL, newT - T ) ); m_transitionRects.push_back( QRect( newL, newB, newR - newL, B - newB ) ); L = newL; T = newT; R = newR, B = newB; } } else { int L = m_width / 2, T = m_height / 2, R = L, B = T; for ( int i = 0; i < steps; i++ ) { // compute shrunk box coords int newL = ((steps - (i + 1)) * m_width) / (2 * steps); int newT = ((steps - (i + 1)) * m_height) / (2 * steps); int newR = m_width - newL; int newB = m_height - newT; // add left, right, topcenter, bottomcenter rects m_transitionRects.push_back( QRect( newL, newT, L - newL, newB - newT ) ); m_transitionRects.push_back( QRect( R, newT, newR - R, newB - newT ) ); m_transitionRects.push_back( QRect( L, newT, R - L, T - newT ) ); m_transitionRects.push_back( QRect( L, B, R - L, newB - B ) ); L = newL; T = newT; R = newR, B = newB; } } m_transitionMul = 4; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // wipe: implemented for 4 canonical angles case Okular::PageTransition::Wipe: { const int angle = transition->angle(); const int steps = (angle == 0) || (angle == 180) ? m_width / 8 : m_height / 8; if ( angle == 0 ) { int xPosition = 0; for ( int i = 0; i < steps; i++ ) { int xNext = ((i + 1) * m_width) / steps; m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) ); xPosition = xNext; } } else if ( angle == 90 ) { int yPosition = m_height; for ( int i = 0; i < steps; i++ ) { int yNext = ((steps - (i + 1)) * m_height) / steps; m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) ); yPosition = yNext; } } else if ( angle == 180 ) { int xPosition = m_width; for ( int i = 0; i < steps; i++ ) { int xNext = ((steps - (i + 1)) * m_width) / steps; m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) ); xPosition = xNext; } } else if ( angle == 270 ) { int yPosition = 0; for ( int i = 0; i < steps; i++ ) { int yNext = ((i + 1) * m_height) / steps; m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) ); yPosition = yNext; } } else { update(); return; } m_transitionMul = 1; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // dissolve: replace 'random' rects case Okular::PageTransition::Dissolve: { const int gridXsteps = 50; const int gridYsteps = 38; const int steps = gridXsteps * gridYsteps; int oldX = 0; int oldY = 0; // create a grid of gridXstep by gridYstep QRects for ( int y = 0; y < gridYsteps; y++ ) { int newY = (int)( m_height * ((float)(y+1) / (float)gridYsteps) ); for ( int x = 0; x < gridXsteps; x++ ) { int newX = (int)( m_width * ((float)(x+1) / (float)gridXsteps) ); m_transitionRects.push_back( QRect( oldX, oldY, newX - oldX, newY - oldY ) ); oldX = newX; } oldX = 0; oldY = newY; } // randomize the grid for ( int i = 0; i < steps; i++ ) { #ifndef Q_OS_WIN int n1 = (int)(steps * drand48()); int n2 = (int)(steps * drand48()); #else int n1 = (int)(steps * (std::rand() / RAND_MAX)); int n2 = (int)(steps * (std::rand() / RAND_MAX)); #endif // swap items if index differs if ( n1 != n2 ) { QRect r = m_transitionRects[ n2 ]; m_transitionRects[ n2 ] = m_transitionRects[ n1 ]; m_transitionRects[ n1 ] = r; } } // set global transition parameters m_transitionMul = 40; m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps ); } break; // glitter: similar to dissolve but has a direction case Okular::PageTransition::Glitter: { const int gridXsteps = 50; const int gridYsteps = 38; const int steps = gridXsteps * gridYsteps; const int angle = transition->angle(); // generate boxes using a given direction if ( angle == 90 ) { int yPosition = m_height; for ( int i = 0; i < gridYsteps; i++ ) { int yNext = ((gridYsteps - (i + 1)) * m_height) / gridYsteps; int xPosition = 0; for ( int j = 0; j < gridXsteps; j++ ) { int xNext = ((j + 1) * m_width) / gridXsteps; m_transitionRects.push_back( QRect( xPosition, yNext, xNext - xPosition, yPosition - yNext ) ); xPosition = xNext; } yPosition = yNext; } } else if ( angle == 180 ) { int xPosition = m_width; for ( int i = 0; i < gridXsteps; i++ ) { int xNext = ((gridXsteps - (i + 1)) * m_width) / gridXsteps; int yPosition = 0; for ( int j = 0; j < gridYsteps; j++ ) { int yNext = ((j + 1) * m_height) / gridYsteps; m_transitionRects.push_back( QRect( xNext, yPosition, xPosition - xNext, yNext - yPosition ) ); yPosition = yNext; } xPosition = xNext; } } else if ( angle == 270 ) { int yPosition = 0; for ( int i = 0; i < gridYsteps; i++ ) { int yNext = ((i + 1) * m_height) / gridYsteps; int xPosition = 0; for ( int j = 0; j < gridXsteps; j++ ) { int xNext = ((j + 1) * m_width) / gridXsteps; m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) ); xPosition = xNext; } yPosition = yNext; } } else // if angle is 0 or 315 { int xPosition = 0; for ( int i = 0; i < gridXsteps; i++ ) { int xNext = ((i + 1) * m_width) / gridXsteps; int yPosition = 0; for ( int j = 0; j < gridYsteps; j++ ) { int yNext = ((j + 1) * m_height) / gridYsteps; m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) ); yPosition = yNext; } xPosition = xNext; } } // add a 'glitter' (1 over 10 pieces is randomized) int randomSteps = steps / 20; for ( int i = 0; i < randomSteps; i++ ) { #ifndef Q_OS_WIN int n1 = (int)(steps * drand48()); int n2 = (int)(steps * drand48()); #else int n1 = (int)(steps * (std::rand() / RAND_MAX)); int n2 = (int)(steps * (std::rand() / RAND_MAX)); #endif // swap items if index differs if ( n1 != n2 ) { QRect r = m_transitionRects[ n2 ]; m_transitionRects[ n2 ] = m_transitionRects[ n1 ]; m_transitionRects[ n1 ] = r; } } // set global transition parameters m_transitionMul = (angle == 90) || (angle == 270) ? gridYsteps : gridXsteps; m_transitionMul /= 2; m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps ); } break; case Okular::PageTransition::Fade: { enum {FADE_TRANSITION_FPS = 20}; const int steps = totalTime * FADE_TRANSITION_FPS; m_transitionSteps = steps; QPainter pixmapPainter; m_currentPixmapOpacity = (double) 1 / steps; m_transitionDelay = (int)( totalTime * 1000 ) / steps; m_lastRenderedPixmap = QPixmap( m_lastRenderedPixmap.size() ); m_lastRenderedPixmap.fill( Qt::transparent ); pixmapPainter.begin( &m_lastRenderedPixmap ); pixmapPainter.setCompositionMode( QPainter::CompositionMode_Source ); pixmapPainter.setOpacity( 1 - m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_previousPagePixmap ); pixmapPainter.setOpacity( m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_currentPagePixmap ); pixmapPainter.end(); update(); } break; // implement missing transitions (a binary raster engine needed here) case Okular::PageTransition::Fly: case Okular::PageTransition::Push: case Okular::PageTransition::Cover: case Okular::PageTransition::Uncover: default: update(); return; } // send the first start to the timer m_transitionTimer->start( 0 ); } void PresentationWidget::slotProcessMovieAction( const Okular::MovieAction *action ) { const Okular::MovieAnnotation *movieAnnotation = action->annotation(); if ( !movieAnnotation ) return; Okular::Movie *movie = movieAnnotation->movie(); if ( !movie ) return; VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movieAnnotation->movie() ); if ( !vw ) return; vw->show(); switch ( action->operation() ) { case Okular::MovieAction::Play: vw->stop(); vw->play(); break; case Okular::MovieAction::Stop: vw->stop(); break; case Okular::MovieAction::Pause: vw->pause(); break; case Okular::MovieAction::Resume: vw->play(); break; }; } void PresentationWidget::slotProcessRenditionAction( const Okular::RenditionAction *action ) { Okular::Movie *movie = action->movie(); if ( !movie ) return; VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movie ); if ( !vw ) return; if ( action->operation() == Okular::RenditionAction::None ) return; vw->show(); switch ( action->operation() ) { case Okular::RenditionAction::Play: vw->stop(); vw->play(); break; case Okular::RenditionAction::Stop: vw->stop(); break; case Okular::RenditionAction::Pause: vw->pause(); break; case Okular::RenditionAction::Resume: vw->play(); break; default: return; }; } void PresentationWidget::slotTogglePlayPause() { m_advanceSlides = !m_advanceSlides; setPlayPauseIcon(); if ( m_advanceSlides ) { startAutoChangeTimer(); } else { m_nextPageTimer->stop(); } } #include "presentationwidget.moc" diff --git a/ui/presentationwidget.h b/ui/presentationwidget.h index 68931e2bd..c039013f2 100644 --- a/ui/presentationwidget.h +++ b/ui/presentationwidget.h @@ -1,171 +1,172 @@ /*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _OKULAR_PRESENTATIONWIDGET_H_ #define _OKULAR_PRESENTATIONWIDGET_H_ #include #include #include #include #include #include "core/area.h" #include "core/observer.h" #include "core/pagetransition.h" class QLineEdit; class QToolBar; class QTimer; class QGestureEvent; class KActionCollection; class KSelectAction; class SmoothPathEngine; struct PresentationFrame; class PresentationSearchBar; class DrawingToolActions; namespace Okular { class Action; class Annotation; class Document; class MovieAction; class Page; class RenditionAction; } /** * @short A widget that shows pages as fullscreen slides (with transitions fx). * * This is a fullscreen widget that displays */ class PresentationWidget : public QWidget, public Okular::DocumentObserver { Q_OBJECT public: PresentationWidget( QWidget * parent, Okular::Document * doc, DrawingToolActions * drawingToolActions, KActionCollection * collection ); ~PresentationWidget(); // inherited from DocumentObserver void notifySetup( const QVector< Okular::Page * > & pages, int setupFlags ) override; void notifyViewportChanged( bool smoothMove ) override; void notifyPageChanged( int pageNumber, int changedFlags ) override; bool canUnloadPixmap( int pageNumber ) const override; void notifyCurrentPageChanged( int previous, int current ) override; public Q_SLOTS: void slotFind(); protected: // widget events bool event( QEvent * e ) override; void keyPressEvent( QKeyEvent * e ) override; void wheelEvent( QWheelEvent * e ) override; void mousePressEvent( QMouseEvent * e ) override; void mouseReleaseEvent( QMouseEvent * e ) override; void mouseMoveEvent( QMouseEvent * e ) override; void paintEvent( QPaintEvent * e ) override; void resizeEvent( QResizeEvent * e ) override; void leaveEvent( QEvent * e ) override; bool gestureEvent (QGestureEvent * e ); private: const void * getObjectRect( Okular::ObjectRect::ObjectType type, int x, int y, QRect * geometry = nullptr ) const; const Okular::Action * getLink( int x, int y, QRect * geometry = nullptr ) const; const Okular::Annotation * getAnnotation( int x, int y, QRect * geometry = nullptr ) const; void testCursorOnLink( int x, int y ); void overlayClick( const QPoint & position ); void changePage( int newPage ); void generatePage( bool disableTransition = false ); void generateIntroPage( QPainter & p ); void generateContentsPage( int page, QPainter & p ); void generateOverlay(); void initTransition( const Okular::PageTransition *transition ); const Okular::PageTransition defaultTransition() const; const Okular::PageTransition defaultTransition( int ) const; QRect routeMouseDrawingEvent( QMouseEvent * ); void startAutoChangeTimer(); void recalcGeometry(); void repositionContent(); void requestPixmaps(); void setScreen( int ); void applyNewScreenSize( const QSize & oldSize ); void inhibitPowerManagement(); void allowPowerManagement(); void showTopBar( bool ); // create actions that interact with this widget void setupActions(); void setPlayPauseIcon(); // cache stuff int m_width; int m_height; QPixmap m_lastRenderedPixmap; QPixmap m_lastRenderedOverlay; QRect m_overlayGeometry; const Okular::Action * m_pressedLink; bool m_handCursor; SmoothPathEngine * m_drawingEngine; QRect m_drawingRect; int m_screen; uint m_screenInhibitCookie; int m_sleepInhibitFd; // transition related QTimer * m_transitionTimer; QTimer * m_overlayHideTimer; QTimer * m_nextPageTimer; int m_transitionDelay; int m_transitionMul; int m_transitionSteps; QList< QRect > m_transitionRects; Okular::PageTransition m_currentTransition; QPixmap m_currentPagePixmap; QPixmap m_previousPagePixmap; double m_currentPixmapOpacity; // misc stuff QWidget * m_parentWidget; Okular::Document * m_document; QVector< PresentationFrame * > m_frames; int m_frameIndex; QStringList m_metaStrings; QToolBar * m_topBar; QLineEdit *m_pagesEdit; PresentationSearchBar *m_searchBar; KActionCollection * m_ac; KSelectAction * m_screenSelect; QDomElement m_currentDrawingToolElement; bool m_isSetup; bool m_blockNotifications; bool m_inBlackScreenMode; bool m_showSummaryView; bool m_advanceSlides; + bool m_goToPreviousPageOnRelease; bool m_goToNextPageOnRelease; private Q_SLOTS: void slotNextPage(); void slotPrevPage(); void slotFirstPage(); void slotLastPage(); void slotHideOverlay(); void slotTransitionStep(); void slotDelayedEvents(); void slotPageChanged(); void clearDrawings(); void screenResized( int ); void chooseScreen( QAction * ); void toggleBlackScreenMode( bool ); void slotProcessMovieAction( const Okular::MovieAction *action ); void slotProcessRenditionAction( const Okular::RenditionAction *action ); void slotTogglePlayPause(); void slotChangeDrawingToolEngine( const QDomElement &doc ); void slotAddDrawingToolActions(); }; #endif