okular/ui/presentationwidget.cpp
Lukas Hetzenecker ecc1141e02 HiDPI Support for Okular
Summary:
This patch enables HiDPI throughout the application

Every pixmap is multiplied by the devicePixelRatioF
QPainter code is ajusted to take the DPR value into account

All pixmaps get cached with the highest DPR of all screens. When moving the application to another screen, the cache doesn't have to be invalidated.

BUGS: 362856 383589
REVIEW: D6268
2017-10-14 14:47:20 +02:00

2457 lines
88 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/***************************************************************************
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
* *
* 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 <QtCore/qloggingcategory.h>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusReply>
#include <qevent.h>
#include <qfontmetrics.h>
#include <QIcon>
#include <qtimer.h>
#include <qimage.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qpainter.h>
#include <qstyle.h>
#include <qstyleoption.h>
#include <qtooltip.h>
#include <qvalidator.h>
#include <qapplication.h>
#include <qdesktopwidget.h>
#include <QGestureEvent>
#include <kcursor.h>
#include <krandom.h>
#include <qtoolbar.h>
#include <qaction.h>
#include <kactioncollection.h>
#include <klineedit.h>
#include <KLocalizedString>
#include <kiconloader.h>
#include <kmessagebox.h>
#include <kselectaction.h>
#include <QDialog>
#ifdef Q_OS_LINUX
#include <QDBusUnixFileDescriptor>
#include <unistd.h> // For ::close() for sleep inhibition
#endif
// system includes
#include <stdlib.h>
#include <math.h>
// 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()
{
qDeleteAll( videoWidgets );
}
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_sleepInhibitCookie(0),
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_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()->setMargin(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();
QAction *eraseDrawingAct = collection->action( QStringLiteral("presentation_erase_drawings") );
eraseDrawingAct->setEnabled( true );
connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings);
m_topBar->addAction( eraseDrawingAct );
addAction( eraseDrawingAct );
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 );
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::Background, 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::Key>() << 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<const Okular::ScreenAnnotation*>( annotation )->additionalAction( Okular::Annotation::PageClosing );
else if ( annotation->subType() == Okular::Annotation::AWidget )
action = static_cast<const Okular::WidgetAnnotation*>( 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<const Okular::ScreenAnnotation*>( annotation )->additionalAction( Okular::Annotation::PageOpening );
else if ( annotation->subType() == Okular::Annotation::AWidget )
action = static_cast<const Okular::WidgetAnnotation*>( 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" ) );
}
}
// <widget events>
bool PresentationWidget::event( QEvent * e )
{
if ( e->type() == QEvent::Gesture )
return gestureEvent(static_cast<QGestureEvent*>(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<QSwipeGesture *>(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<const Okular::MovieAnnotation*>( 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<const Okular::RichMediaAnnotation*>( 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<const Okular::ScreenAnnotation*>( 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;
}
// 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_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 consistancy
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<QRect> 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 );
}
}
// </widget events>
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<const Okular::Action*>( getObjectRect( Okular::ObjectRect::Action, x, y, geometry ) );
}
const Okular::Annotation * PresentationWidget::getAnnotation( int x, int y, QRect * geometry ) const
{
return reinterpret_cast<const Okular::Annotation*>( 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<QRect> 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<double>( 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<DrawingToolActions*>(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<uint> 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_sleepInhibitCookie) {
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<QDBusUnixFileDescriptor> reply = QDBusConnection::systemBus().asyncCall(message);
reply.waitForFinished();
if (reply.isValid()) {
m_sleepInhibitCookie = reply.value().fileDescriptor();
} else {
qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error();
}
}
#endif
}
void PresentationWidget::allowPowerManagement()
{
#ifdef Q_OS_LINUX
if (m_sleepInhibitCookie) {
::close(m_sleepInhibitCookie);
m_sleepInhibitCookie = 0;
}
if (m_screenInhibitCookie) {
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.ScreenSaver", "/ScreenSaver",
"org.freedesktop.ScreenSaver", "UnInhibit");
message << m_screenInhibitCookie;
QDBusPendingReply<uint> 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++ )
{
// compure shrinked 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++ )
{
// compure shrinked 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"