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
KPluralHandlingSpinBox
QSpinBox
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