1 /*
2     SPDX-FileCopyrightText: 2004 Enrico Ros <eros.kde@email.it>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "presentationwidget.h"
8 
9 // qt/kde includes
10 #include <QDBusConnection>
11 #include <QDBusMessage>
12 #include <QDBusReply>
13 #include <QLoggingCategory>
14 
15 #include <KActionCollection>
16 #include <KCursor>
17 #include <KLineEdit>
18 #include <KLocalizedString>
19 #include <KMessageBox>
20 #include <KRandom>
21 #include <KSelectAction>
22 #include <QAction>
23 #include <QApplication>
24 #include <QDialog>
25 #include <QEvent>
26 #include <QFontMetrics>
27 #include <QGestureEvent>
28 #include <QIcon>
29 #include <QImage>
30 #include <QLabel>
31 #include <QLayout>
32 #include <QPainter>
33 #include <QScreen>
34 #include <QStyle>
35 #include <QStyleOption>
36 #include <QTimer>
37 #include <QToolBar>
38 #include <QToolTip>
39 #include <QValidator>
40 
41 #ifdef Q_OS_LINUX
42 #include <QDBusUnixFileDescriptor>
43 #include <unistd.h> // For ::close() for sleep inhibition
44 #endif
45 
46 // system includes
47 #include <math.h>
48 #include <stdlib.h>
49 
50 // local includes
51 #include "annotationtools.h"
52 #include "core/action.h"
53 #include "core/annotations.h"
54 #include "core/audioplayer.h"
55 #include "core/document.h"
56 #include "core/generator.h"
57 #include "core/movie.h"
58 #include "core/page.h"
59 #include "debug_ui.h"
60 #include "drawingtoolactions.h"
61 #include "guiutils.h"
62 #include "pagepainter.h"
63 #include "presentationsearchbar.h"
64 #include "priorities.h"
65 #include "settings.h"
66 #include "settings_core.h"
67 #include "videowidget.h"
68 
69 // comment this to disable the top-right progress indicator
70 #define ENABLE_PROGRESS_OVERLAY
71 
72 // a frame contains a pointer to the page object, its geometry and the
73 // transition effect to the next frame
74 struct PresentationFrame {
75     PresentationFrame() = default;
76 
~PresentationFramePresentationFrame77     ~PresentationFrame()
78     {
79         qDeleteAll(videoWidgets);
80     }
81 
82     PresentationFrame(const PresentationFrame &) = delete;
83     PresentationFrame &operator=(const PresentationFrame &) = delete;
84 
recalcGeometryPresentationFrame85     void recalcGeometry(int width, int height, float screenRatio)
86     {
87         // calculate frame geometry keeping constant aspect ratio
88         float pageRatio = page->ratio();
89         int pageWidth = width, pageHeight = height;
90         if (pageRatio > screenRatio)
91             pageWidth = (int)((float)pageHeight / pageRatio);
92         else
93             pageHeight = (int)((float)pageWidth * pageRatio);
94         geometry.setRect((width - pageWidth) / 2, (height - pageHeight) / 2, pageWidth, pageHeight);
95 
96         for (VideoWidget *vw : qAsConst(videoWidgets)) {
97             const Okular::NormalizedRect r = vw->normGeometry();
98             QRect vwgeom = r.geometry(geometry.width(), geometry.height());
99             vw->resize(vwgeom.size());
100             vw->move(geometry.topLeft() + vwgeom.topLeft());
101         }
102     }
103 
104     const Okular::Page *page;
105     QRect geometry;
106     QHash<Okular::Movie *, VideoWidget *> videoWidgets;
107     QLinkedList<SmoothPath> drawings;
108 };
109 
110 // a custom QToolBar that basically does not propagate the event if the widget
111 // background is not automatically filled
112 class PresentationToolBar : public QToolBar
113 {
114     Q_OBJECT
115 
116 public:
PresentationToolBar(QWidget * parent=Q_NULLPTR)117     explicit PresentationToolBar(QWidget *parent = Q_NULLPTR)
118         : QToolBar(parent)
119     {
120     }
121 
122 protected:
mousePressEvent(QMouseEvent * e)123     void mousePressEvent(QMouseEvent *e) override
124     {
125         QToolBar::mousePressEvent(e);
126         e->accept();
127     }
128 
mouseReleaseEvent(QMouseEvent * e)129     void mouseReleaseEvent(QMouseEvent *e) override
130     {
131         QToolBar::mouseReleaseEvent(e);
132         e->accept();
133     }
134 };
135 
PresentationWidget(QWidget * parent,Okular::Document * doc,DrawingToolActions * drawingToolActions,KActionCollection * collection)136 PresentationWidget::PresentationWidget(QWidget *parent, Okular::Document *doc, DrawingToolActions *drawingToolActions, KActionCollection *collection)
137     : QWidget(nullptr /* must be null, to have an independent widget */, Qt::FramelessWindowHint)
138     , m_pressedLink(nullptr)
139     , m_handCursor(false)
140     , m_drawingEngine(nullptr)
141     , m_screenInhibitCookie(0)
142     , m_sleepInhibitFd(-1)
143     , m_parentWidget(parent)
144     , m_document(doc)
145     , m_frameIndex(-1)
146     , m_topBar(nullptr)
147     , m_pagesEdit(nullptr)
148     , m_searchBar(nullptr)
149     , m_ac(collection)
150     , m_screenSelect(nullptr)
151     , m_isSetup(false)
152     , m_blockNotifications(false)
153     , m_inBlackScreenMode(false)
154     , m_showSummaryView(Okular::Settings::slidesShowSummary())
155     , m_advanceSlides(Okular::SettingsCore::slidesAdvance())
156     , m_goToPreviousPageOnRelease(false)
157     , m_goToNextPageOnRelease(false)
158 {
159     setAttribute(Qt::WA_DeleteOnClose);
160     setAttribute(Qt::WA_OpaquePaintEvent);
161     setObjectName(QStringLiteral("presentationWidget"));
162     QString caption = doc->metaData(QStringLiteral("DocumentTitle")).toString();
163     if (caption.trimmed().isEmpty())
164         caption = doc->currentDocument().fileName();
165     caption = i18nc("[document title/filename] – Presentation", "%1 – Presentation", caption);
166     setWindowTitle(caption);
167 
168     m_width = -1;
169 
170     // create top toolbar
171     m_topBar = new PresentationToolBar(this);
172     m_topBar->setObjectName(QStringLiteral("presentationBar"));
173     m_topBar->setMovable(false);
174     m_topBar->layout()->setContentsMargins(0, 0, 0, 0);
175     m_topBar->addAction(QIcon::fromTheme(layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-next") : QStringLiteral("go-previous")), i18n("Previous Page"), this, SLOT(slotPrevPage()));
176     m_pagesEdit = new KLineEdit(m_topBar);
177     QSizePolicy sp = m_pagesEdit->sizePolicy();
178     sp.setHorizontalPolicy(QSizePolicy::Minimum);
179     m_pagesEdit->setSizePolicy(sp);
180     QFontMetrics fm(m_pagesEdit->font());
181     QStyleOptionFrame option;
182     option.initFrom(m_pagesEdit);
183     m_pagesEdit->setMaximumWidth(fm.horizontalAdvance(QString::number(m_document->pages())) + 2 * style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, m_pagesEdit) +
184                                  4); // the 4 comes from 2*horizontalMargin, horizontalMargin being a define in qlineedit.cpp
185     QIntValidator *validator = new QIntValidator(1, m_document->pages(), m_pagesEdit);
186     m_pagesEdit->setValidator(validator);
187     m_topBar->addWidget(m_pagesEdit);
188     QLabel *pagesLabel = new QLabel(m_topBar);
189     pagesLabel->setText(QLatin1String(" / ") + QString::number(m_document->pages()) + QLatin1String(" "));
190     m_topBar->addWidget(pagesLabel);
191     connect(m_pagesEdit, &QLineEdit::returnPressed, this, &PresentationWidget::slotPageChanged);
192     m_topBar->addAction(QIcon::fromTheme(layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-previous") : QStringLiteral("go-next")), i18n("Next Page"), this, SLOT(slotNextPage()));
193     m_topBar->addSeparator();
194     QAction *playPauseAct = collection->action(QStringLiteral("presentation_play_pause"));
195     playPauseAct->setEnabled(true);
196     connect(playPauseAct, &QAction::triggered, this, &PresentationWidget::slotTogglePlayPause);
197     m_topBar->addAction(playPauseAct);
198     addAction(playPauseAct);
199     m_topBar->addSeparator();
200 
201     const QList<QAction *> actionsList = drawingToolActions->actions();
202     for (QAction *action : actionsList) {
203         action->setEnabled(true);
204         m_topBar->addAction(action);
205         addAction(action);
206     }
207     connect(drawingToolActions, &DrawingToolActions::changeEngine, this, &PresentationWidget::slotChangeDrawingToolEngine);
208     connect(drawingToolActions, &DrawingToolActions::actionsRecreated, this, &PresentationWidget::slotAddDrawingToolActions);
209 
210     QAction *eraseDrawingAct = collection->action(QStringLiteral("presentation_erase_drawings"));
211     eraseDrawingAct->setEnabled(true);
212     connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings);
213     m_topBar->addAction(eraseDrawingAct);
214     addAction(eraseDrawingAct);
215 
216     const int screenCount = QApplication::screens().count();
217     if (screenCount > 1) {
218         m_topBar->addSeparator();
219         m_screenSelect = new KSelectAction(QIcon::fromTheme(QStringLiteral("video-display")), i18n("Switch Screen"), m_topBar);
220         m_screenSelect->setToolBarMode(KSelectAction::MenuMode);
221         m_screenSelect->setToolButtonPopupMode(QToolButton::InstantPopup);
222         m_topBar->addAction(m_screenSelect);
223         for (int i = 0; i < screenCount; ++i) {
224             QAction *act = m_screenSelect->addAction(i18nc("%1 is the screen number (0, 1, ...)", "Screen %1", i));
225             act->setData(QVariant::fromValue(i));
226         }
227     }
228     QWidget *spacer = new QWidget(m_topBar);
229     spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
230     m_topBar->addWidget(spacer);
231     m_topBar->addAction(QIcon::fromTheme(QStringLiteral("application-exit")), i18n("Exit Presentation Mode"), this, SLOT(close()));
232     m_topBar->setAutoFillBackground(true);
233     showTopBar(false);
234     // change topbar background color
235     QPalette p = m_topBar->palette();
236     p.setColor(QPalette::Active, QPalette::Button, Qt::gray);
237     p.setColor(QPalette::Active, QPalette::Window, Qt::darkGray);
238     m_topBar->setPalette(p);
239 
240     // Grab swipe gestures to change pages
241     grabGesture(Qt::SwipeGesture);
242 
243     // misc stuff
244     setMouseTracking(true);
245     setContextMenuPolicy(Qt::PreventContextMenu);
246     m_transitionTimer = new QTimer(this);
247     m_transitionTimer->setSingleShot(true);
248     connect(m_transitionTimer, &QTimer::timeout, this, &PresentationWidget::slotTransitionStep);
249     m_overlayHideTimer = new QTimer(this);
250     m_overlayHideTimer->setSingleShot(true);
251     connect(m_overlayHideTimer, &QTimer::timeout, this, &PresentationWidget::slotHideOverlay);
252     m_nextPageTimer = new QTimer(this);
253     m_nextPageTimer->setSingleShot(true);
254     connect(m_nextPageTimer, &QTimer::timeout, this, &PresentationWidget::slotNextPage);
255     setPlayPauseIcon();
256 
257     connect(m_document, &Okular::Document::processMovieAction, this, &PresentationWidget::slotProcessMovieAction);
258     connect(m_document, &Okular::Document::processRenditionAction, this, &PresentationWidget::slotProcessRenditionAction);
259 
260     // handle cursor appearance as specified in configuration
261     if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
262         KCursor::setAutoHideCursor(this, true);
263         KCursor::setHideCursorDelay(3000);
264     } else if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) {
265         setCursor(QCursor(Qt::BlankCursor));
266     }
267 
268     setupActions();
269 
270     // inhibit power management
271     inhibitPowerManagement();
272 
273     QTimer::singleShot(0, this, &PresentationWidget::slotDelayedEvents);
274 
275     // setFocus() so KCursor::setAutoHideCursor() goes into effect if it's enabled
276     setFocus(Qt::OtherFocusReason);
277 
278     // Catch TabletEnterProximity and TabletLeaveProximity events from the QApplication
279     qApp->installEventFilter(this);
280 }
281 
~PresentationWidget()282 PresentationWidget::~PresentationWidget()
283 {
284     // allow power management saver again
285     allowPowerManagement();
286 
287     // stop the audio playbacks
288     Okular::AudioPlayer::instance()->stopPlaybacks();
289 
290     // remove our highlights
291     if (m_searchBar) {
292         m_document->resetSearch(PRESENTATION_SEARCH_ID);
293     }
294 
295     // remove this widget from document observer
296     m_document->removeObserver(this);
297 
298     const QList<QAction *> actionsList = actions();
299     for (QAction *action : actionsList) {
300         action->setChecked(false);
301         action->setEnabled(false);
302     }
303 
304     delete m_drawingEngine;
305 
306     // delete frames
307     qDeleteAll(m_frames);
308 
309     qApp->removeEventFilter(this);
310 }
311 
notifySetup(const QVector<Okular::Page * > & pageSet,int setupFlags)312 void PresentationWidget::notifySetup(const QVector<Okular::Page *> &pageSet, int setupFlags)
313 {
314     // same document, nothing to change - here we assume the document sets up
315     // us with the whole document set as first notifySetup()
316     if (!(setupFlags & Okular::DocumentObserver::DocumentChanged))
317         return;
318 
319     // delete previous frames (if any (shouldn't be))
320     qDeleteAll(m_frames);
321     if (!m_frames.isEmpty())
322         qCWarning(OkularUiDebug) << "Frames setup changed while a Presentation is in progress.";
323     m_frames.clear();
324 
325     // create the new frames
326     float screenRatio = (float)m_height / (float)m_width;
327     for (const Okular::Page *page : pageSet) {
328         PresentationFrame *frame = new PresentationFrame();
329         frame->page = page;
330         const QLinkedList<Okular::Annotation *> annotations = page->annotations();
331         for (Okular::Annotation *a : annotations) {
332             if (a->subType() == Okular::Annotation::AMovie) {
333                 Okular::MovieAnnotation *movieAnn = static_cast<Okular::MovieAnnotation *>(a);
334                 VideoWidget *vw = new VideoWidget(movieAnn, movieAnn->movie(), m_document, this);
335                 frame->videoWidgets.insert(movieAnn->movie(), vw);
336                 vw->pageInitialized();
337             } else if (a->subType() == Okular::Annotation::ARichMedia) {
338                 Okular::RichMediaAnnotation *richMediaAnn = static_cast<Okular::RichMediaAnnotation *>(a);
339                 if (richMediaAnn->movie()) {
340                     VideoWidget *vw = new VideoWidget(richMediaAnn, richMediaAnn->movie(), m_document, this);
341                     frame->videoWidgets.insert(richMediaAnn->movie(), vw);
342                     vw->pageInitialized();
343                 }
344             } else if (a->subType() == Okular::Annotation::AScreen) {
345                 const Okular::ScreenAnnotation *screenAnn = static_cast<Okular::ScreenAnnotation *>(a);
346                 Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation(screenAnn);
347                 if (movie) {
348                     VideoWidget *vw = new VideoWidget(screenAnn, movie, m_document, this);
349                     frame->videoWidgets.insert(movie, vw);
350                     vw->pageInitialized();
351                 }
352             }
353         }
354         frame->recalcGeometry(m_width, m_height, screenRatio);
355         // add the frame to the vector
356         m_frames.push_back(frame);
357     }
358 
359     // get metadata from the document
360     m_metaStrings.clear();
361     const Okular::DocumentInfo info = m_document->documentInfo(QSet<Okular::DocumentInfo::Key>() << Okular::DocumentInfo::Title << Okular::DocumentInfo::Author);
362     if (!info.get(Okular::DocumentInfo::Title).isNull())
363         m_metaStrings += i18n("Title: %1", info.get(Okular::DocumentInfo::Title));
364     if (!info.get(Okular::DocumentInfo::Author).isNull())
365         m_metaStrings += i18n("Author: %1", info.get(Okular::DocumentInfo::Author));
366     m_metaStrings += i18n("Pages: %1", m_document->pages());
367     m_metaStrings += i18n("Click to begin");
368 
369     m_isSetup = true;
370 }
371 
notifyViewportChanged(bool)372 void PresentationWidget::notifyViewportChanged(bool /*smoothMove*/)
373 {
374     // display the current page
375     changePage(m_document->viewport().pageNumber);
376 
377     // auto advance to the next page if set
378     startAutoChangeTimer();
379 }
380 
notifyPageChanged(int pageNumber,int changedFlags)381 void PresentationWidget::notifyPageChanged(int pageNumber, int changedFlags)
382 {
383     // if we are blocking the notifications, do nothing
384     if (m_blockNotifications)
385         return;
386 
387     // check if it's the last requested pixmap. if so update the widget.
388     if ((changedFlags & (DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights)) && pageNumber == m_frameIndex)
389         generatePage(changedFlags & (DocumentObserver::Annotations | DocumentObserver::Highlights));
390 }
391 
notifyCurrentPageChanged(int previousPage,int currentPage)392 void PresentationWidget::notifyCurrentPageChanged(int previousPage, int currentPage)
393 {
394     if (previousPage != -1) {
395         // stop video playback
396         for (VideoWidget *vw : qAsConst(m_frames[previousPage]->videoWidgets)) {
397             vw->stop();
398             vw->pageLeft();
399         }
400 
401         // stop audio playback, if any
402         Okular::AudioPlayer::instance()->stopPlaybacks();
403 
404         // perform the page closing action, if any
405         if (m_document->page(previousPage)->pageAction(Okular::Page::Closing))
406             m_document->processAction(m_document->page(previousPage)->pageAction(Okular::Page::Closing));
407 
408         // perform the additional actions of the page's annotations, if any
409         const QLinkedList<Okular::Annotation *> annotationsList = m_document->page(previousPage)->annotations();
410         for (const Okular::Annotation *annotation : annotationsList) {
411             Okular::Action *action = nullptr;
412 
413             if (annotation->subType() == Okular::Annotation::AScreen)
414                 action = static_cast<const Okular::ScreenAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageClosing);
415             else if (annotation->subType() == Okular::Annotation::AWidget)
416                 action = static_cast<const Okular::WidgetAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageClosing);
417 
418             if (action)
419                 m_document->processAction(action);
420         }
421     }
422 
423     if (currentPage != -1) {
424         m_frameIndex = currentPage;
425 
426         // check if pixmap exists or else request it
427         PresentationFrame *frame = m_frames[m_frameIndex];
428         int pixW = frame->geometry.width();
429         int pixH = frame->geometry.height();
430 
431         bool signalsBlocked = m_pagesEdit->signalsBlocked();
432         m_pagesEdit->blockSignals(true);
433         m_pagesEdit->setText(QString::number(m_frameIndex + 1));
434         m_pagesEdit->blockSignals(signalsBlocked);
435 
436         // if pixmap not inside the Okular::Page we request it and wait for
437         // notifyPixmapChanged call or else we can proceed to pixmap generation
438         if (!frame->page->hasPixmap(this, ceil(pixW * devicePixelRatioF()), ceil(pixH * devicePixelRatioF()))) {
439             requestPixmaps();
440         } else {
441             // make the background pixmap
442             generatePage();
443         }
444 
445         // perform the page opening action, if any
446         if (m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening))
447             m_document->processAction(m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening));
448 
449         // perform the additional actions of the page's annotations, if any
450         const QLinkedList<Okular::Annotation *> annotationsList = m_document->page(m_frameIndex)->annotations();
451         for (const Okular::Annotation *annotation : annotationsList) {
452             Okular::Action *action = nullptr;
453 
454             if (annotation->subType() == Okular::Annotation::AScreen)
455                 action = static_cast<const Okular::ScreenAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageOpening);
456             else if (annotation->subType() == Okular::Annotation::AWidget)
457                 action = static_cast<const Okular::WidgetAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageOpening);
458 
459             if (action)
460                 m_document->processAction(action);
461         }
462 
463         // start autoplay video playback
464         for (VideoWidget *vw : qAsConst(m_frames[m_frameIndex]->videoWidgets)) {
465             vw->pageEntered();
466         }
467     }
468 }
469 
canUnloadPixmap(int pageNumber) const470 bool PresentationWidget::canUnloadPixmap(int pageNumber) const
471 {
472     if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal) {
473         // can unload all pixmaps except for the currently visible one
474         return pageNumber != m_frameIndex;
475     } else {
476         // can unload all pixmaps except for the currently visible one, previous and next
477         return qAbs(pageNumber - m_frameIndex) <= 1;
478     }
479 }
480 
setupActions()481 void PresentationWidget::setupActions()
482 {
483     addAction(m_ac->action(QStringLiteral("first_page")));
484     addAction(m_ac->action(QStringLiteral("last_page")));
485     addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::Prior))));
486     addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::Next))));
487     addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::DocumentBack))));
488     addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::DocumentForward))));
489 
490     QAction *action = m_ac->action(QStringLiteral("switch_blackscreen_mode"));
491     connect(action, &QAction::toggled, this, &PresentationWidget::toggleBlackScreenMode);
492     action->setEnabled(true);
493     addAction(action);
494 }
495 
setPlayPauseIcon()496 void PresentationWidget::setPlayPauseIcon()
497 {
498     QAction *playPauseAction = m_ac->action(QStringLiteral("presentation_play_pause"));
499     if (m_nextPageTimer->isActive()) {
500         playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
501         playPauseAction->setToolTip(i18nc("For Presentation", "Pause"));
502     } else {
503         playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
504         playPauseAction->setToolTip(i18nc("For Presentation", "Play"));
505     }
506 }
507 
eventFilter(QObject * o,QEvent * e)508 bool PresentationWidget::eventFilter(QObject *o, QEvent *e)
509 {
510     if (o == qApp) {
511         if (e->type() == QTabletEvent::TabletEnterProximity) {
512             setCursor(QCursor(Qt::CrossCursor));
513         } else if (e->type() == QTabletEvent::TabletLeaveProximity) {
514             setCursor(QCursor(Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ? Qt::BlankCursor : Qt::ArrowCursor));
515             if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
516                 // Trick KCursor to hide the cursor if needed by sending an "unknown" key press event
517                 // Send also the key release to make the world happy even it's probably not needed
518                 QKeyEvent kp(QEvent::KeyPress, 0, Qt::NoModifier);
519                 qApp->sendEvent(this, &kp);
520                 QKeyEvent kr(QEvent::KeyRelease, 0, Qt::NoModifier);
521                 qApp->sendEvent(this, &kr);
522             }
523         }
524     }
525     return false;
526 }
527 
528 // <widget events>
event(QEvent * e)529 bool PresentationWidget::event(QEvent *e)
530 {
531     if (e->type() == QEvent::Gesture)
532         return gestureEvent(static_cast<QGestureEvent *>(e));
533 
534     if (e->type() == QEvent::ToolTip) {
535         QHelpEvent *he = (QHelpEvent *)e;
536 
537         QRect r;
538         const Okular::Action *link = getLink(he->x(), he->y(), &r);
539 
540         if (link) {
541             QString tip = link->actionTip();
542             if (!tip.isEmpty())
543                 QToolTip::showText(he->globalPos(), tip, this, r);
544         }
545         e->accept();
546         return true;
547     } else
548         // do not stop the event
549         return QWidget::event(e);
550 }
551 
gestureEvent(QGestureEvent * event)552 bool PresentationWidget::gestureEvent(QGestureEvent *event)
553 {
554     // Swiping left or right on a touch screen will go to the previous or next slide, respectively.
555     // The precise gesture is the standard Qt swipe: with three(!) fingers.
556     if (QGesture *swipe = event->gesture(Qt::SwipeGesture)) {
557         QSwipeGesture *swipeEvent = static_cast<QSwipeGesture *>(swipe);
558 
559         if (swipeEvent->state() == Qt::GestureFinished) {
560             if (swipeEvent->horizontalDirection() == QSwipeGesture::Left) {
561                 slotPrevPage();
562                 event->accept();
563                 return true;
564             }
565             if (swipeEvent->horizontalDirection() == QSwipeGesture::Right) {
566                 slotNextPage();
567                 event->accept();
568                 return true;
569             }
570         }
571     }
572 
573     return false;
574 }
keyPressEvent(QKeyEvent * e)575 void PresentationWidget::keyPressEvent(QKeyEvent *e)
576 {
577     if (!m_isSetup)
578         return;
579 
580     switch (e->key()) {
581     case Qt::Key_Left:
582     case Qt::Key_Backspace:
583     case Qt::Key_PageUp:
584     case Qt::Key_Up:
585         slotPrevPage();
586         break;
587     case Qt::Key_Right:
588     case Qt::Key_Space:
589     case Qt::Key_PageDown:
590     case Qt::Key_Down:
591         slotNextPage();
592         break;
593     case Qt::Key_Home:
594         slotFirstPage();
595         break;
596     case Qt::Key_End:
597         slotLastPage();
598         break;
599     case Qt::Key_Escape:
600         if (!m_topBar->isHidden())
601             showTopBar(false);
602         else
603             close();
604         break;
605     }
606 }
607 
wheelEvent(QWheelEvent * e)608 void PresentationWidget::wheelEvent(QWheelEvent *e)
609 {
610     if (!m_isSetup)
611         return;
612 
613     // performance note: don't remove the clipping
614     int div = e->angleDelta().y() / 120;
615     if (div > 0) {
616         if (div > 3)
617             div = 3;
618         while (div--)
619             slotPrevPage();
620     } else if (div < 0) {
621         if (div < -3)
622             div = -3;
623         while (div++)
624             slotNextPage();
625     }
626 }
627 
mousePressEvent(QMouseEvent * e)628 void PresentationWidget::mousePressEvent(QMouseEvent *e)
629 {
630     if (!m_isSetup)
631         return;
632 
633     if (m_drawingEngine) {
634         QRect r = routeMouseDrawingEvent(e);
635         if (r.isValid()) {
636             m_drawingRect |= r.translated(m_frames[m_frameIndex]->geometry.topLeft());
637             update(m_drawingRect);
638         }
639         return;
640     }
641 
642     // pressing left button
643     if (e->button() == Qt::LeftButton) {
644         // if pressing on a link, skip other checks
645         if ((m_pressedLink = getLink(e->x(), e->y())))
646             return;
647 
648         const Okular::Annotation *annotation = getAnnotation(e->x(), e->y());
649         if (annotation) {
650             if (annotation->subType() == Okular::Annotation::AMovie) {
651                 const Okular::MovieAnnotation *movieAnnotation = static_cast<const Okular::MovieAnnotation *>(annotation);
652 
653                 VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movieAnnotation->movie());
654                 vw->show();
655                 vw->play();
656                 return;
657             } else if (annotation->subType() == Okular::Annotation::ARichMedia) {
658                 const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast<const Okular::RichMediaAnnotation *>(annotation);
659 
660                 VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(richMediaAnnotation->movie());
661                 vw->show();
662                 vw->play();
663                 return;
664             } else if (annotation->subType() == Okular::Annotation::AScreen) {
665                 m_document->processAction(static_cast<const Okular::ScreenAnnotation *>(annotation)->action());
666                 return;
667             }
668         }
669 
670         // handle clicking on top-right overlay
671         if (!(Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) && m_overlayGeometry.contains(e->pos())) {
672             overlayClick(e->pos());
673             return;
674         }
675 
676         // Actual mouse press events always lead to the next page page
677         if (e->source() == Qt::MouseEventNotSynthesized) {
678             m_goToNextPageOnRelease = true;
679         }
680         // Touch events may lead to the previous or next page
681         else if (Okular::Settings::slidesTapNavigation() != Okular::Settings::EnumSlidesTapNavigation::Disabled) {
682             switch (Okular::Settings::slidesTapNavigation()) {
683             case Okular::Settings::EnumSlidesTapNavigation::ForwardBackward: {
684                 if (e->x() < (geometry().width() / 2)) {
685                     m_goToPreviousPageOnRelease = true;
686                 } else {
687                     m_goToNextPageOnRelease = true;
688                 }
689                 break;
690             }
691             case Okular::Settings::EnumSlidesTapNavigation::Forward: {
692                 m_goToNextPageOnRelease = true;
693                 break;
694             }
695             case Okular::Settings::EnumSlidesTapNavigation::Disabled: {
696                 // Do Nothing
697             }
698             }
699         }
700     }
701     // pressing forward button
702     else if (e->button() == Qt::ForwardButton) {
703         m_goToNextPageOnRelease = true;
704     }
705     // pressing right or backward button
706     else if (e->button() == Qt::RightButton || e->button() == Qt::BackButton)
707         m_goToPreviousPageOnRelease = true;
708 }
709 
mouseReleaseEvent(QMouseEvent * e)710 void PresentationWidget::mouseReleaseEvent(QMouseEvent *e)
711 {
712     if (m_drawingEngine) {
713         routeMouseDrawingEvent(e);
714         return;
715     }
716 
717     // if releasing on the same link we pressed over, execute it
718     if (m_pressedLink && e->button() == Qt::LeftButton) {
719         const Okular::Action *link = getLink(e->x(), e->y());
720         if (link == m_pressedLink)
721             m_document->processAction(link);
722         m_pressedLink = nullptr;
723     }
724 
725     if (m_goToPreviousPageOnRelease) {
726         slotPrevPage();
727         m_goToPreviousPageOnRelease = false;
728     }
729 
730     if (m_goToNextPageOnRelease) {
731         slotNextPage();
732         m_goToNextPageOnRelease = false;
733     }
734 }
735 
mouseMoveEvent(QMouseEvent * e)736 void PresentationWidget::mouseMoveEvent(QMouseEvent *e)
737 {
738     // safety check
739     if (!m_isSetup)
740         return;
741 
742     // update cursor and tooltip if hovering a link
743     if (!m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden)
744         testCursorOnLink(e->x(), e->y());
745 
746     if (!m_topBar->isHidden()) {
747         // hide a shown bar when exiting the area
748         if (e->y() > (m_topBar->height() + 1)) {
749             showTopBar(false);
750             setFocus(Qt::OtherFocusReason);
751         }
752     } else {
753         if (m_drawingEngine && e->buttons() != Qt::NoButton) {
754             QRect r = routeMouseDrawingEvent(e);
755             if (r.isValid()) {
756                 m_drawingRect |= r.translated(m_frames[m_frameIndex]->geometry.topLeft());
757                 update(m_drawingRect);
758             }
759         } else {
760             // show the bar if reaching top 2 pixels
761             if (e->y() <= 1)
762                 showTopBar(true);
763             // handle "dragging the wheel" if clicking on its geometry
764             else if ((QApplication::mouseButtons() & Qt::LeftButton) && m_overlayGeometry.contains(e->pos()))
765                 overlayClick(e->pos());
766         }
767     }
768 }
769 
paintEvent(QPaintEvent * pe)770 void PresentationWidget::paintEvent(QPaintEvent *pe)
771 {
772     qreal dpr = devicePixelRatioF();
773 
774     if (m_inBlackScreenMode) {
775         QPainter painter(this);
776         painter.fillRect(pe->rect(), Qt::black);
777         return;
778     }
779 
780     if (!m_isSetup) {
781         m_width = width();
782         m_height = height();
783 
784         connect(m_document, &Okular::Document::linkFind, this, &PresentationWidget::slotFind);
785 
786         // register this observer in document. events will come immediately
787         m_document->addObserver(this);
788 
789         // show summary if requested
790         if (Okular::Settings::slidesShowSummary())
791             generatePage();
792     }
793 
794     // check painting rect consistency
795     QRect r = pe->rect().intersected(QRect(QPoint(0, 0), geometry().size()));
796     if (r.isNull())
797         return;
798 
799     if (m_lastRenderedPixmap.isNull()) {
800         QPainter painter(this);
801         painter.fillRect(pe->rect(), Okular::Settings::slidesBackgroundColor());
802         return;
803     }
804 
805     // blit the pixmap to the screen
806     QPainter painter(this);
807     for (const QRect &r : pe->region()) {
808         if (!r.isValid())
809             continue;
810 #ifdef ENABLE_PROGRESS_OVERLAY
811         const QRect dR(QRectF(r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr).toAlignedRect());
812         if (Okular::Settings::slidesShowProgress() && r.intersects(m_overlayGeometry)) {
813             // backbuffer the overlay operation
814             QPixmap backPixmap(dR.size());
815             backPixmap.setDevicePixelRatio(dpr);
816             QPainter pixPainter(&backPixmap);
817 
818             // first draw the background on the backbuffer
819             pixPainter.drawPixmap(QPoint(0, 0), m_lastRenderedPixmap, dR);
820 
821             // then blend the overlay (a piece of) over the background
822             QRect ovr = m_overlayGeometry.intersected(r);
823             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);
824 
825             // finally blit the pixmap to the screen
826             pixPainter.end();
827             const QRect backPixmapRect = backPixmap.rect();
828             const QRect dBackPixmapRect(QRectF(backPixmapRect.x() * dpr, backPixmapRect.y() * dpr, backPixmapRect.width() * dpr, backPixmapRect.height() * dpr).toAlignedRect());
829             painter.drawPixmap(r.topLeft(), backPixmap, dBackPixmapRect);
830         } else
831 #endif
832             // copy the rendered pixmap to the screen
833             painter.drawPixmap(r.topLeft(), m_lastRenderedPixmap, dR);
834     }
835 
836     // paint drawings
837     if (m_frameIndex != -1) {
838         painter.save();
839 
840         const QRect &geom = m_frames[m_frameIndex]->geometry;
841 
842         const QSize pmSize(geom.width() * dpr, geom.height() * dpr);
843         QPixmap pm(pmSize);
844         pm.fill(Qt::transparent);
845         QPainter pmPainter(&pm);
846 
847         pm.setDevicePixelRatio(dpr);
848         pmPainter.setRenderHints(QPainter::Antialiasing);
849 
850         // Paint old paths
851         for (const SmoothPath &drawing : qAsConst(m_frames[m_frameIndex]->drawings)) {
852             drawing.paint(&pmPainter, pmSize.width(), pmSize.height());
853         }
854 
855         // Paint the path that is currently being drawn by the user
856         if (m_drawingEngine && m_drawingRect.intersects(pe->rect()))
857             m_drawingEngine->paint(&pmPainter, pmSize.width(), pmSize.height(), m_drawingRect.intersected(pe->rect()));
858 
859         painter.setRenderHints(QPainter::Antialiasing);
860         painter.drawPixmap(geom.topLeft(), pm);
861 
862         painter.restore();
863     }
864     painter.end();
865 }
866 
resizeEvent(QResizeEvent * re)867 void PresentationWidget::resizeEvent(QResizeEvent *re)
868 {
869     m_width = width();
870     m_height = height();
871 
872     // if by chance the new size equals the old, do not invalidate pixmaps and such..
873     if (size() == re->oldSize())
874         return;
875 
876     // BEGIN Top toolbar
877     // tool bar height in pixels, make it large enough to hold the text fields with the page numbers
878     const int toolBarHeight = m_pagesEdit->height() * 1.5;
879 
880     m_topBar->setGeometry(0, 0, width(), toolBarHeight);
881     m_topBar->setIconSize(QSize(toolBarHeight * 0.75, toolBarHeight * 0.75));
882     // END Top toolbar
883 
884     // BEGIN Content area
885     // update the frames
886     const float screenRatio = (float)m_height / (float)m_width;
887     for (PresentationFrame *frame : qAsConst(m_frames)) {
888         frame->recalcGeometry(m_width, m_height, screenRatio);
889     }
890 
891     if (m_frameIndex != -1) {
892         // ugliness alarm!
893         const_cast<Okular::Page *>(m_frames[m_frameIndex]->page)->deletePixmap(this);
894         // force the regeneration of the pixmap
895         m_lastRenderedPixmap = QPixmap();
896         m_blockNotifications = true;
897         requestPixmaps();
898         m_blockNotifications = false;
899     }
900 
901     if (m_transitionTimer->isActive()) {
902         m_transitionTimer->stop();
903     }
904 
905     generatePage(true /* no transitions */);
906     // END Content area
907 }
908 
enterEvent(QEvent * e)909 void PresentationWidget::enterEvent(QEvent *e)
910 {
911     if (!m_topBar->isHidden()) {
912         QEnterEvent *ee = static_cast<QEnterEvent *>(e);
913         // This can happen when we exited the widget via a "tooltip" and the tooltip disappears
914         if (ee->y() > (m_topBar->height() + 1)) {
915             showTopBar(false);
916         }
917     }
918     QWidget::enterEvent(e);
919 }
920 
leaveEvent(QEvent * e)921 void PresentationWidget::leaveEvent(QEvent *e)
922 {
923     Q_UNUSED(e)
924 
925     if (!m_topBar->isHidden()) {
926         if (QToolTip::isVisible()) {
927             // make sure we're not hovering over the tooltip
928             // because the world is sad, this works differently on Wayland and X11
929             // on X11 the widget under the cursor is the tooltip window
930             // on wayland it's the button generating the tooltip (why? no idea)
931             const QWidget *widgetUnderCursor = qApp->widgetAt(QCursor::pos());
932             if (widgetUnderCursor) {
933                 const QWidget *widgetUnderCursorWindow = widgetUnderCursor->window();
934                 if (widgetUnderCursorWindow == this) {
935                     qDebug() << "Wayland";
936                     return;
937                 } else {
938                     const QWidget *widgetUnderCursorParentWindow = widgetUnderCursorWindow->parentWidget() ? widgetUnderCursorWindow->parentWidget()->window() : nullptr;
939                     if (widgetUnderCursorParentWindow == this) {
940                         qDebug() << "X11";
941                         return;
942                     }
943                 }
944             }
945         }
946         showTopBar(false);
947     }
948 }
949 // </widget events>
950 
getObjectRect(Okular::ObjectRect::ObjectType type,int x,int y,QRect * geometry) const951 const void *PresentationWidget::getObjectRect(Okular::ObjectRect::ObjectType type, int x, int y, QRect *geometry) const
952 {
953     // no links on invalid pages
954     if (geometry && !geometry->isNull())
955         geometry->setRect(0, 0, 0, 0);
956     if (m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size())
957         return nullptr;
958 
959     // get frame, page and geometry
960     const PresentationFrame *frame = m_frames[m_frameIndex];
961     const Okular::Page *page = frame->page;
962     const QRect &frameGeometry = frame->geometry;
963 
964     // compute normalized x and y
965     double nx = (double)(x - frameGeometry.left()) / (double)frameGeometry.width();
966     double ny = (double)(y - frameGeometry.top()) / (double)frameGeometry.height();
967 
968     // no links outside the pages
969     if (nx < 0 || nx > 1 || ny < 0 || ny > 1)
970         return nullptr;
971 
972     // check if 1) there is an object and 2) it's a link
973     const QRect screenRect = oldQt_screenOf(this)->geometry();
974     const Okular::ObjectRect *object = page->objectRect(type, nx, ny, screenRect.width(), screenRect.height());
975     if (!object)
976         return nullptr;
977 
978     // compute link geometry if destination rect present
979     if (geometry) {
980         *geometry = object->boundingRect(frameGeometry.width(), frameGeometry.height());
981         geometry->translate(frameGeometry.left(), frameGeometry.top());
982     }
983 
984     // return the link pointer
985     return object->object();
986 }
987 
getLink(int x,int y,QRect * geometry) const988 const Okular::Action *PresentationWidget::getLink(int x, int y, QRect *geometry) const
989 {
990     return reinterpret_cast<const Okular::Action *>(getObjectRect(Okular::ObjectRect::Action, x, y, geometry));
991 }
992 
getAnnotation(int x,int y,QRect * geometry) const993 const Okular::Annotation *PresentationWidget::getAnnotation(int x, int y, QRect *geometry) const
994 {
995     return reinterpret_cast<const Okular::Annotation *>(getObjectRect(Okular::ObjectRect::OAnnotation, x, y, geometry));
996 }
997 
testCursorOnLink(int x,int y)998 void PresentationWidget::testCursorOnLink(int x, int y)
999 {
1000     const Okular::Action *link = getLink(x, y, nullptr);
1001     const Okular::Annotation *annotation = getAnnotation(x, y, nullptr);
1002 
1003     const bool needsHandCursor = ((link != nullptr) || ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::AMovie)) || ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::ARichMedia)) ||
1004                                   ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::AScreen) && (GuiUtils::renditionMovieFromScreenAnnotation(static_cast<const Okular::ScreenAnnotation *>(annotation)) != nullptr)));
1005 
1006     // only react on changes (in/out from a link)
1007     if ((needsHandCursor && !m_handCursor) || (!needsHandCursor && m_handCursor)) {
1008         // change cursor shape
1009         m_handCursor = needsHandCursor;
1010         setCursor(QCursor(m_handCursor ? Qt::PointingHandCursor : Qt::ArrowCursor));
1011     }
1012 }
1013 
overlayClick(const QPoint position)1014 void PresentationWidget::overlayClick(const QPoint position)
1015 {
1016     // clicking the progress indicator
1017     int xPos = position.x() - m_overlayGeometry.x() - m_overlayGeometry.width() / 2, yPos = m_overlayGeometry.height() / 2 - position.y();
1018     if (!xPos && !yPos)
1019         return;
1020 
1021     // compute angle relative to indicator (note coord transformation)
1022     float angle = 0.5 + 0.5 * atan2((double)-xPos, (double)-yPos) / M_PI;
1023     int pageIndex = (int)(angle * (m_frames.count() - 1) + 0.5);
1024 
1025     // go to selected page
1026     changePage(pageIndex);
1027 }
1028 
changePage(int newPage)1029 void PresentationWidget::changePage(int newPage)
1030 {
1031     if (m_showSummaryView) {
1032         m_showSummaryView = false;
1033         m_frameIndex = -1;
1034         return;
1035     }
1036 
1037     if (m_frameIndex == newPage)
1038         return;
1039 
1040     // switch to newPage
1041     m_document->setViewportPage(newPage, this);
1042 
1043     if ((Okular::Settings::slidesShowSummary() && !m_showSummaryView) || m_frameIndex == -1)
1044         notifyCurrentPageChanged(-1, newPage);
1045 }
1046 
generatePage(bool disableTransition)1047 void PresentationWidget::generatePage(bool disableTransition)
1048 {
1049     if (m_lastRenderedPixmap.isNull()) {
1050         qreal dpr = devicePixelRatioF();
1051         m_lastRenderedPixmap = QPixmap(m_width * dpr, m_height * dpr);
1052         m_lastRenderedPixmap.setDevicePixelRatio(dpr);
1053 
1054         m_previousPagePixmap = QPixmap();
1055     } else {
1056         m_previousPagePixmap = m_lastRenderedPixmap;
1057     }
1058 
1059     // opens the painter over the pixmap
1060     QPainter pixmapPainter;
1061     pixmapPainter.begin(&m_lastRenderedPixmap);
1062     // generate welcome page
1063     if (m_frameIndex == -1)
1064         generateIntroPage(pixmapPainter);
1065     // generate a normal pixmap with extended margin filling
1066     if (m_frameIndex >= 0 && m_frameIndex < (int)m_document->pages())
1067         generateContentsPage(m_frameIndex, pixmapPainter);
1068     pixmapPainter.end();
1069 
1070     // generate the top-right corner overlay
1071 #ifdef ENABLE_PROGRESS_OVERLAY
1072     if (Okular::Settings::slidesShowProgress() && m_frameIndex != -1)
1073         generateOverlay();
1074 #endif
1075 
1076     // start transition on pages that have one
1077     disableTransition |= (Okular::Settings::slidesTransition() == Okular::Settings::EnumSlidesTransition::NoTransitions);
1078     if (!disableTransition) {
1079         const Okular::PageTransition *transition = m_frameIndex != -1 ? m_frames[m_frameIndex]->page->transition() : nullptr;
1080         if (transition)
1081             initTransition(transition);
1082         else {
1083             Okular::PageTransition trans = defaultTransition();
1084             initTransition(&trans);
1085         }
1086     } else {
1087         Okular::PageTransition trans = defaultTransition(Okular::Settings::EnumSlidesTransition::Replace);
1088         initTransition(&trans);
1089     }
1090 
1091     // update cursor + tooltip
1092     if (!m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden) {
1093         QPoint p = mapFromGlobal(QCursor::pos());
1094         testCursorOnLink(p.x(), p.y());
1095     }
1096 }
1097 
generateIntroPage(QPainter & p)1098 void PresentationWidget::generateIntroPage(QPainter &p)
1099 {
1100     qreal dpr = devicePixelRatioF();
1101 
1102     // use a vertical gray gradient background
1103     int blend1 = m_height / 10, blend2 = 9 * m_height / 10;
1104     int baseTint = QColor(Qt::gray).red();
1105     for (int i = 0; i < m_height; i++) {
1106         int k = baseTint;
1107         if (i < blend1)
1108             k -= (int)(baseTint * (i - blend1) * (i - blend1) / (float)(blend1 * blend1));
1109         if (i > blend2)
1110             k += (int)((255 - baseTint) * (i - blend2) * (i - blend2) / (float)(blend1 * blend1));
1111         p.fillRect(0, i, m_width, 1, QColor(k, k, k));
1112     }
1113 
1114     // draw okular logo in the four corners
1115     QPixmap logo = QIcon::fromTheme(QStringLiteral("okular")).pixmap(64 * dpr);
1116     logo.setDevicePixelRatio(dpr);
1117     if (!logo.isNull()) {
1118         p.drawPixmap(5, 5, logo);
1119         p.drawPixmap(m_width - 5 - logo.width(), 5, logo);
1120         p.drawPixmap(m_width - 5 - logo.width(), m_height - 5 - logo.height(), logo);
1121         p.drawPixmap(5, m_height - 5 - logo.height(), logo);
1122     }
1123 
1124     // draw metadata text (the last line is 'click to begin')
1125     int strNum = m_metaStrings.count(), strHeight = m_height / (strNum + 4), fontHeight = 2 * strHeight / 3;
1126     QFont font(p.font());
1127     font.setPixelSize(fontHeight);
1128     QFontMetrics metrics(font);
1129     for (int i = 0; i < strNum; i++) {
1130         // set a font to fit text width
1131         float wScale = (float)metrics.boundingRect(m_metaStrings[i]).width() / (float)m_width;
1132         QFont f(font);
1133         if (wScale > 1.0)
1134             f.setPixelSize((int)((float)fontHeight / (float)wScale));
1135         p.setFont(f);
1136 
1137         // text shadow
1138         p.setPen(Qt::darkGray);
1139         p.drawText(2, m_height / 4 + strHeight * i + 2, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i]);
1140         // text body
1141         p.setPen(128 + (127 * i) / strNum);
1142         p.drawText(0, m_height / 4 + strHeight * i, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i]);
1143     }
1144 }
1145 
generateContentsPage(int pageNum,QPainter & p)1146 void PresentationWidget::generateContentsPage(int pageNum, QPainter &p)
1147 {
1148     PresentationFrame *frame = m_frames[pageNum];
1149 
1150     // translate painter and contents rect
1151     QRect geom(frame->geometry);
1152     p.translate(geom.left(), geom.top());
1153     geom.translate(-geom.left(), -geom.top());
1154 
1155     // draw the page using the shared PagePainter class
1156     int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations;
1157 
1158     PagePainter::paintPageOnPainter(&p, frame->page, this, flags, geom.width(), geom.height(), geom);
1159 
1160     // restore painter
1161     p.translate(-frame->geometry.left(), -frame->geometry.top());
1162 
1163     // fill unpainted areas with background color
1164     const QRegion unpainted(QRect(0, 0, m_width, m_height));
1165     const QRegion rgn = unpainted.subtracted(frame->geometry);
1166     for (const QRect &r : rgn) {
1167         p.fillRect(r, Okular::Settings::slidesBackgroundColor());
1168     }
1169 }
1170 
1171 // from Arthur - Qt4 - (is defined elsewhere as 'qt_div_255' to not break final compilation)
qt_div255(int x)1172 inline int qt_div255(int x)
1173 {
1174     return (x + (x >> 8) + 0x80) >> 8;
1175 }
generateOverlay()1176 void PresentationWidget::generateOverlay()
1177 {
1178 #ifdef ENABLE_PROGRESS_OVERLAY
1179     qreal dpr = devicePixelRatioF();
1180 
1181     // calculate overlay geometry and resize pixmap if needed
1182     double side = m_width / 16.0;
1183     m_overlayGeometry.setRect(m_width - side - 4, 4, side, side);
1184 
1185     // note: to get a sort of antialiasing, we render the pixmap double sized
1186     // and the resulting image is smoothly scaled down. So here we open a
1187     // painter on the double sized pixmap.
1188     side *= 2;
1189 
1190     QPixmap doublePixmap(side * dpr, side * dpr);
1191     doublePixmap.setDevicePixelRatio(dpr);
1192     doublePixmap.fill(Qt::black);
1193     QPainter pixmapPainter(&doublePixmap);
1194     pixmapPainter.setRenderHints(QPainter::Antialiasing);
1195 
1196     // draw PIE SLICES in blue levels (the levels will then be the alpha component)
1197     int pages = m_document->pages();
1198     if (pages > 28) { // draw continuous slices
1199         int degrees = (int)(360 * (float)(m_frameIndex + 1) / (float)pages);
1200         pixmapPainter.setPen(0x05);
1201         pixmapPainter.setBrush(QColor(0x40));
1202         pixmapPainter.drawPie(2, 2, side - 4, side - 4, 90 * 16, (360 - degrees) * 16);
1203         pixmapPainter.setPen(0x40);
1204         pixmapPainter.setBrush(QColor(0xF0));
1205         pixmapPainter.drawPie(2, 2, side - 4, side - 4, 90 * 16, -degrees * 16);
1206     } else { // draw discrete slices
1207         float oldCoord = -90;
1208         for (int i = 0; i < pages; i++) {
1209             float newCoord = -90 + 360 * (float)(i + 1) / (float)pages;
1210             pixmapPainter.setPen(i <= m_frameIndex ? 0x40 : 0x05);
1211             pixmapPainter.setBrush(QColor(i <= m_frameIndex ? 0xF0 : 0x40));
1212             pixmapPainter.drawPie(2, 2, side - 4, side - 4, (int)(-16 * (oldCoord + 1)), (int)(-16 * (newCoord - (oldCoord + 2))));
1213             oldCoord = newCoord;
1214         }
1215     }
1216     int circleOut = side / 4;
1217     pixmapPainter.setPen(Qt::black);
1218     pixmapPainter.setBrush(Qt::black);
1219     pixmapPainter.drawEllipse(circleOut, circleOut, side - 2 * circleOut, side - 2 * circleOut);
1220 
1221     // draw TEXT using maximum opacity
1222     QFont f(pixmapPainter.font());
1223     f.setPixelSize(side / 4);
1224     pixmapPainter.setFont(f);
1225     pixmapPainter.setPen(0xFF);
1226     // use a little offset to prettify output
1227     pixmapPainter.drawText(2, 2, side, side, Qt::AlignCenter, QString::number(m_frameIndex + 1));
1228 
1229     // end drawing pixmap and halve image
1230     pixmapPainter.end();
1231     QImage image(doublePixmap.toImage().scaled((side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
1232     image.setDevicePixelRatio(dpr);
1233     image = image.convertToFormat(QImage::Format_ARGB32);
1234     image.setDevicePixelRatio(dpr);
1235 
1236     // draw circular shadow using the same technique
1237     doublePixmap.fill(Qt::black);
1238     pixmapPainter.begin(&doublePixmap);
1239     pixmapPainter.setPen(0x40);
1240     pixmapPainter.setBrush(QColor(0x80));
1241     pixmapPainter.drawEllipse(0, 0, side, side);
1242     pixmapPainter.end();
1243     QImage shadow(doublePixmap.toImage().scaled((side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
1244     shadow.setDevicePixelRatio(dpr);
1245 
1246     // generate a 2 colors pixmap using mixing shadow (made with highlight color)
1247     // and image (made with highlightedText color)
1248     QPalette pal = palette();
1249     QColor color = pal.color(QPalette::Active, QPalette::HighlightedText);
1250     int red = color.red(), green = color.green(), blue = color.blue();
1251     color = pal.color(QPalette::Active, QPalette::Highlight);
1252     int sRed = color.red(), sGreen = color.green(), sBlue = color.blue();
1253     // pointers
1254     unsigned int *data = reinterpret_cast<unsigned int *>(image.bits()), *shadowData = reinterpret_cast<unsigned int *>(shadow.bits()), pixels = image.width() * image.height();
1255     // cache data (reduce computation time to 26%!)
1256     int c1 = -1, c2 = -1, cR = 0, cG = 0, cB = 0, cA = 0;
1257     // foreach pixel
1258     for (unsigned int i = 0; i < pixels; ++i) {
1259         // alpha for shadow and image
1260         int shadowAlpha = shadowData[i] & 0xFF, srcAlpha = data[i] & 0xFF;
1261         // cache values
1262         if (srcAlpha != c1 || shadowAlpha != c2) {
1263             c1 = srcAlpha;
1264             c2 = shadowAlpha;
1265             // fuse color components and alpha value of image over shadow
1266             data[i] = qRgba(cR = qt_div255(srcAlpha * red + (255 - srcAlpha) * sRed),
1267                             cG = qt_div255(srcAlpha * green + (255 - srcAlpha) * sGreen),
1268                             cB = qt_div255(srcAlpha * blue + (255 - srcAlpha) * sBlue),
1269                             cA = qt_div255(srcAlpha * srcAlpha + (255 - srcAlpha) * shadowAlpha));
1270         } else
1271             data[i] = qRgba(cR, cG, cB, cA);
1272     }
1273     m_lastRenderedOverlay = QPixmap::fromImage(image);
1274     m_lastRenderedOverlay.setDevicePixelRatio(dpr);
1275 
1276     // start the autohide timer
1277     // repaint( m_overlayGeometry ); // toggle with next line
1278     update(m_overlayGeometry);
1279     m_overlayHideTimer->start(2500);
1280 #endif
1281 }
1282 
routeMouseDrawingEvent(QMouseEvent * e)1283 QRect PresentationWidget::routeMouseDrawingEvent(QMouseEvent *e)
1284 {
1285     if (m_frameIndex == -1) // Can't draw on the summary page
1286         return QRect();
1287 
1288     const QRect &geom = m_frames[m_frameIndex]->geometry;
1289     const Okular::Page *page = m_frames[m_frameIndex]->page;
1290 
1291     AnnotatorEngine::EventType eventType;
1292     AnnotatorEngine::Button button;
1293     AnnotatorEngine::Modifiers modifiers;
1294 
1295     // figure out the event type and button
1296     AnnotatorEngine::decodeEvent(e, &eventType, &button);
1297 
1298     static bool hasclicked = false;
1299     if (eventType == AnnotatorEngine::Press)
1300         hasclicked = true;
1301 
1302     QPointF mousePos = e->localPos();
1303     double nX = (mousePos.x() - (double)geom.left()) / (double)geom.width();
1304     double nY = (mousePos.y() - (double)geom.top()) / (double)geom.height();
1305     QRect ret;
1306     bool isInside = nX >= 0 && nX < 1 && nY >= 0 && nY < 1;
1307 
1308     if (hasclicked && !isInside) {
1309         // Fake a move to the last border pos
1310         nX = qBound(0., nX, 1.);
1311         nY = qBound(0., nY, 1.);
1312         m_drawingEngine->event(AnnotatorEngine::Move, button, modifiers, nX, nY, geom.width(), geom.height(), page);
1313 
1314         // Fake a release in the following lines
1315         eventType = AnnotatorEngine::Release;
1316         isInside = true;
1317     } else if (!hasclicked && isInside) {
1318         // we're coming from the outside, pretend we started clicking at the closest border
1319         if (nX < (1 - nX) && nX < nY && nX < (1 - nY))
1320             nX = 0;
1321         else if (nY < (1 - nY) && nY < nX && nY < (1 - nX))
1322             nY = 0;
1323         else if ((1 - nX) < nX && (1 - nX) < nY && (1 - nX) < (1 - nY))
1324             nX = 1;
1325         else
1326             nY = 1;
1327 
1328         hasclicked = true;
1329         eventType = AnnotatorEngine::Press;
1330     }
1331 
1332     if (hasclicked && isInside) {
1333         ret = m_drawingEngine->event(eventType, button, modifiers, nX, nY, geom.width(), geom.height(), page);
1334     }
1335 
1336     if (eventType == AnnotatorEngine::Release) {
1337         hasclicked = false;
1338     }
1339 
1340     if (m_drawingEngine->creationCompleted()) {
1341         // add drawing to current page
1342         m_frames[m_frameIndex]->drawings << m_drawingEngine->endSmoothPath();
1343 
1344         // remove the actual drawer and create a new one just after
1345         // that - that gives continuous drawing
1346         delete m_drawingEngine;
1347         m_drawingRect = QRect();
1348         m_drawingEngine = new SmoothPathEngine(m_currentDrawingToolElement);
1349 
1350         // schedule repaint
1351         update();
1352     }
1353 
1354     return ret;
1355 }
1356 
startAutoChangeTimer()1357 void PresentationWidget::startAutoChangeTimer()
1358 {
1359     double pageDuration = m_frameIndex >= 0 && m_frameIndex < (int)m_frames.count() ? m_frames[m_frameIndex]->page->duration() : -1;
1360     if (m_advanceSlides || pageDuration >= 0.0) {
1361         double secs;
1362         if (pageDuration < 0.0)
1363             secs = Okular::SettingsCore::slidesAdvanceTime();
1364         else if (m_advanceSlides)
1365             secs = qMin<double>(pageDuration, Okular::SettingsCore::slidesAdvanceTime());
1366         else
1367             secs = pageDuration;
1368 
1369         m_nextPageTimer->start((int)(secs * 1000));
1370     }
1371     setPlayPauseIcon();
1372 }
1373 
defaultScreen() const1374 QScreen *PresentationWidget::defaultScreen() const
1375 {
1376     const int preferenceScreen = Okular::Settings::slidesScreen();
1377 
1378     if (preferenceScreen == -2) {
1379         return oldQt_screenOf(m_parentWidget);
1380     } else if (preferenceScreen == -1) {
1381         return QApplication::primaryScreen();
1382     } else if (preferenceScreen >= 0 && preferenceScreen < QApplication::screens().count()) {
1383         return QApplication::screens().at(preferenceScreen);
1384     } else {
1385         return oldQt_screenOf(m_parentWidget);
1386     }
1387 }
1388 
requestPixmaps()1389 void PresentationWidget::requestPixmaps()
1390 {
1391     const qreal dpr = devicePixelRatioF();
1392     PresentationFrame *frame = m_frames[m_frameIndex];
1393     int pixW = frame->geometry.width();
1394     int pixH = frame->geometry.height();
1395 
1396     // operation will take long: set busy cursor
1397     QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
1398     // request the pixmap
1399     QLinkedList<Okular::PixmapRequest *> requests;
1400     requests.push_back(new Okular::PixmapRequest(this, m_frameIndex, pixW, pixH, dpr, PRESENTATION_PRIO, Okular::PixmapRequest::NoFeature));
1401     // restore cursor
1402     QApplication::restoreOverrideCursor();
1403     // ask for next and previous page if not in low memory usage setting
1404     if (Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low) {
1405         int pagesToPreload = 1;
1406 
1407         // If greedy, preload everything
1408         if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy)
1409             pagesToPreload = (int)m_document->pages();
1410 
1411         Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload;
1412         requestFeatures |= Okular::PixmapRequest::Asynchronous;
1413 
1414         for (int j = 1; j <= pagesToPreload; j++) {
1415             int tailRequest = m_frameIndex + j;
1416             if (tailRequest < (int)m_document->pages()) {
1417                 PresentationFrame *nextFrame = m_frames[tailRequest];
1418                 pixW = nextFrame->geometry.width();
1419                 pixH = nextFrame->geometry.height();
1420                 if (!nextFrame->page->hasPixmap(this, pixW, pixH))
1421                     requests.push_back(new Okular::PixmapRequest(this, tailRequest, pixW, pixH, dpr, PRESENTATION_PRELOAD_PRIO, requestFeatures));
1422             }
1423 
1424             int headRequest = m_frameIndex - j;
1425             if (headRequest >= 0) {
1426                 PresentationFrame *prevFrame = m_frames[headRequest];
1427                 pixW = prevFrame->geometry.width();
1428                 pixH = prevFrame->geometry.height();
1429                 if (!prevFrame->page->hasPixmap(this, pixW, pixH))
1430                     requests.push_back(new Okular::PixmapRequest(this, headRequest, pixW, pixH, dpr, PRESENTATION_PRELOAD_PRIO, requestFeatures));
1431             }
1432 
1433             // stop if we've already reached both ends of the document
1434             if (headRequest < 0 && tailRequest >= (int)m_document->pages())
1435                 break;
1436         }
1437     }
1438     m_document->requestPixmaps(requests);
1439 }
1440 
slotNextPage()1441 void PresentationWidget::slotNextPage()
1442 {
1443     int nextIndex = m_frameIndex + 1;
1444 
1445     // loop when configured
1446     if (nextIndex == m_frames.count() && Okular::Settings::slidesLoop())
1447         nextIndex = 0;
1448 
1449     if (nextIndex < m_frames.count()) {
1450         // go to next page
1451         changePage(nextIndex);
1452         // auto advance to the next page if set
1453         startAutoChangeTimer();
1454     } else {
1455 #ifdef ENABLE_PROGRESS_OVERLAY
1456         if (Okular::Settings::slidesShowProgress())
1457             generateOverlay();
1458 #endif
1459         if (m_transitionTimer->isActive()) {
1460             m_transitionTimer->stop();
1461             m_lastRenderedPixmap = m_currentPagePixmap;
1462             update();
1463         }
1464     }
1465     // we need the setFocus() call here to let KCursor::autoHide() work correctly
1466     setFocus();
1467 }
1468 
slotPrevPage()1469 void PresentationWidget::slotPrevPage()
1470 {
1471     if (m_frameIndex > 0) {
1472         // go to previous page
1473         changePage(m_frameIndex - 1);
1474 
1475         // auto advance to the next page if set
1476         startAutoChangeTimer();
1477     } else {
1478 #ifdef ENABLE_PROGRESS_OVERLAY
1479         if (Okular::Settings::slidesShowProgress())
1480             generateOverlay();
1481 #endif
1482         if (m_transitionTimer->isActive()) {
1483             m_transitionTimer->stop();
1484             m_lastRenderedPixmap = m_currentPagePixmap;
1485             update();
1486         }
1487     }
1488 }
1489 
slotFirstPage()1490 void PresentationWidget::slotFirstPage()
1491 {
1492     changePage(0);
1493 }
1494 
slotLastPage()1495 void PresentationWidget::slotLastPage()
1496 {
1497     changePage((int)m_frames.count() - 1);
1498 }
1499 
slotHideOverlay()1500 void PresentationWidget::slotHideOverlay()
1501 {
1502     QRect geom(m_overlayGeometry);
1503     m_overlayGeometry.setCoords(0, 0, -1, -1);
1504     update(geom);
1505 }
1506 
slotTransitionStep()1507 void PresentationWidget::slotTransitionStep()
1508 {
1509     switch (m_currentTransition.type()) {
1510     case Okular::PageTransition::Fade: {
1511         QPainter pixmapPainter;
1512         m_currentPixmapOpacity += 1.0 / m_transitionSteps;
1513         m_lastRenderedPixmap = QPixmap(m_lastRenderedPixmap.size());
1514         m_lastRenderedPixmap.setDevicePixelRatio(devicePixelRatioF());
1515         m_lastRenderedPixmap.fill(Qt::transparent);
1516         pixmapPainter.begin(&m_lastRenderedPixmap);
1517         pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
1518         pixmapPainter.setOpacity(1 - m_currentPixmapOpacity);
1519         pixmapPainter.drawPixmap(0, 0, m_previousPagePixmap);
1520         pixmapPainter.setOpacity(m_currentPixmapOpacity);
1521         pixmapPainter.drawPixmap(0, 0, m_currentPagePixmap);
1522         update();
1523         if (m_currentPixmapOpacity >= 1)
1524             return;
1525     } break;
1526     default: {
1527         if (m_transitionRects.empty()) {
1528             // it's better to fix the transition to cover the whole screen than
1529             // enabling the following line that wastes cpu for nothing
1530             // update();
1531             return;
1532         }
1533 
1534         for (int i = 0; i < m_transitionMul && !m_transitionRects.empty(); i++) {
1535             update(m_transitionRects.first());
1536             m_transitionRects.pop_front();
1537         }
1538     } break;
1539     }
1540     m_transitionTimer->start(m_transitionDelay);
1541 }
1542 
slotDelayedEvents()1543 void PresentationWidget::slotDelayedEvents()
1544 {
1545     setScreen(defaultScreen());
1546     show();
1547 
1548     if (m_screenSelect) {
1549         m_screenSelect->setCurrentItem(QApplication::screens().indexOf(oldQt_screenOf(this)));
1550         connect(m_screenSelect->selectableActionGroup(), &QActionGroup::triggered, this, &PresentationWidget::chooseScreen);
1551     }
1552 
1553     // inform user on how to exit from presentation mode
1554     KMessageBox::information(
1555         this,
1556         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)"),
1557         QString(),
1558         QStringLiteral("presentationInfo"));
1559 }
1560 
slotPageChanged()1561 void PresentationWidget::slotPageChanged()
1562 {
1563     bool ok = true;
1564     int p = m_pagesEdit->text().toInt(&ok);
1565     if (!ok)
1566         return;
1567 
1568     changePage(p - 1);
1569 }
1570 
slotChangeDrawingToolEngine(const QDomElement & element)1571 void PresentationWidget::slotChangeDrawingToolEngine(const QDomElement &element)
1572 {
1573     if (element.isNull()) {
1574         delete m_drawingEngine;
1575         m_drawingEngine = nullptr;
1576         m_drawingRect = QRect();
1577         setCursor(Qt::ArrowCursor);
1578     } else {
1579         m_drawingEngine = new SmoothPathEngine(element);
1580         setCursor(QCursor(QPixmap(QStringLiteral("pencil")), Qt::ArrowCursor));
1581         m_currentDrawingToolElement = element;
1582     }
1583 }
1584 
slotAddDrawingToolActions()1585 void PresentationWidget::slotAddDrawingToolActions()
1586 {
1587     DrawingToolActions *drawingToolActions = qobject_cast<DrawingToolActions *>(sender());
1588 
1589     const QList<QAction *> actionsList = drawingToolActions->actions();
1590     for (QAction *action : actionsList) {
1591         action->setEnabled(true);
1592         m_topBar->addAction(action);
1593         addAction(action);
1594     }
1595 }
1596 
clearDrawings()1597 void PresentationWidget::clearDrawings()
1598 {
1599     if (m_frameIndex != -1)
1600         m_frames[m_frameIndex]->drawings.clear();
1601     update();
1602 }
1603 
chooseScreen(QAction * act)1604 void PresentationWidget::chooseScreen(QAction *act)
1605 {
1606     if (!act || act->data().type() != QVariant::Int)
1607         return;
1608 
1609     const int newScreen = act->data().toInt();
1610     if (newScreen < QApplication::screens().count()) {
1611         setScreen(QApplication::screens().at(newScreen));
1612     }
1613 }
1614 
toggleBlackScreenMode(bool)1615 void PresentationWidget::toggleBlackScreenMode(bool)
1616 {
1617     m_inBlackScreenMode = !m_inBlackScreenMode;
1618 
1619     update();
1620 }
1621 
setScreen(const QScreen * newScreen)1622 void PresentationWidget::setScreen(const QScreen *newScreen)
1623 {
1624     // To move to a new screen, need to disable fullscreen first:
1625 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
1626     if (newScreen != screen()) {
1627 #else
1628     if (true) { // TODO Qt6 (oldQt_screenOf() doesn’t help here, because it has a default value.)
1629 #endif
1630         setWindowState(windowState() & ~Qt::WindowFullScreen);
1631     }
1632     setGeometry(newScreen->geometry());
1633     setWindowState(windowState() | Qt::WindowFullScreen);
1634 }
1635 
1636 void PresentationWidget::inhibitPowerManagement()
1637 {
1638 #ifdef Q_OS_LINUX
1639     QString reason = i18nc("Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a presentation");
1640 
1641     if (!m_screenInhibitCookie) {
1642         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("Inhibit"));
1643         message << QCoreApplication::applicationName();
1644         message << reason;
1645 
1646         QDBusPendingReply<uint> reply = QDBusConnection::sessionBus().asyncCall(message);
1647         reply.waitForFinished();
1648         if (reply.isValid()) {
1649             m_screenInhibitCookie = reply.value();
1650             qCDebug(OkularUiDebug) << "Screen inhibition cookie" << m_screenInhibitCookie;
1651         } else {
1652             qCWarning(OkularUiDebug) << "Unable to inhibit screensaver" << reply.error();
1653         }
1654     }
1655 
1656     if (m_sleepInhibitFd != -1) {
1657         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("Inhibit"));
1658         message << QStringLiteral("sleep");
1659         message << QCoreApplication::applicationName();
1660         message << reason;
1661         message << QStringLiteral("block");
1662 
1663         QDBusPendingReply<QDBusUnixFileDescriptor> reply = QDBusConnection::systemBus().asyncCall(message);
1664         reply.waitForFinished();
1665         if (reply.isValid()) {
1666             m_sleepInhibitFd = dup(reply.value().fileDescriptor());
1667         } else {
1668             qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error();
1669         }
1670     }
1671 #endif
1672 }
1673 
1674 void PresentationWidget::allowPowerManagement()
1675 {
1676 #ifdef Q_OS_LINUX
1677     if (m_sleepInhibitFd != -1) {
1678         ::close(m_sleepInhibitFd);
1679         m_sleepInhibitFd = -1;
1680     }
1681 
1682     if (m_screenInhibitCookie) {
1683         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("UnInhibit"));
1684         message << m_screenInhibitCookie;
1685 
1686         QDBusPendingReply<uint> reply = QDBusConnection::sessionBus().asyncCall(message);
1687         reply.waitForFinished();
1688 
1689         m_screenInhibitCookie = 0;
1690     }
1691 #endif
1692 }
1693 
1694 void PresentationWidget::showTopBar(bool show)
1695 {
1696     if (show) {
1697         m_topBar->show();
1698 
1699         // Don't autohide the mouse cursor if it's over the toolbar
1700         if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
1701             KCursor::setAutoHideCursor(this, false);
1702         }
1703 
1704         // Always show a cursor when topBar is visible
1705         if (!m_drawingEngine) {
1706             setCursor(QCursor(Qt::ArrowCursor));
1707         }
1708     } else {
1709         m_topBar->hide();
1710 
1711         // Reenable autohide if need be when leaving the toolbar
1712         if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
1713             KCursor::setAutoHideCursor(this, true);
1714         }
1715 
1716         // Or hide the cursor again if hidden cursor is enabled
1717         else if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) {
1718             // Don't hide the cursor if drawing mode is on
1719             if (!m_drawingEngine) {
1720                 setCursor(QCursor(Qt::BlankCursor));
1721             }
1722         }
1723     }
1724 
1725     // Make sure mouse tracking isn't off after the KCursor::setAutoHideCursor() calls
1726     setMouseTracking(true);
1727 }
1728 
1729 void PresentationWidget::slotFind()
1730 {
1731     if (!m_searchBar) {
1732         m_searchBar = new PresentationSearchBar(m_document, this, this);
1733         m_searchBar->forceSnap();
1734     }
1735     m_searchBar->focusOnSearchEdit();
1736     m_searchBar->show();
1737 }
1738 
1739 const Okular::PageTransition PresentationWidget::defaultTransition() const
1740 {
1741     return defaultTransition(Okular::Settings::slidesTransition());
1742 }
1743 
1744 const Okular::PageTransition PresentationWidget::defaultTransition(int type) const
1745 {
1746     switch (type) {
1747     case Okular::Settings::EnumSlidesTransition::BlindsHorizontal: {
1748         Okular::PageTransition transition(Okular::PageTransition::Blinds);
1749         transition.setAlignment(Okular::PageTransition::Horizontal);
1750         return transition;
1751         break;
1752     }
1753     case Okular::Settings::EnumSlidesTransition::BlindsVertical: {
1754         Okular::PageTransition transition(Okular::PageTransition::Blinds);
1755         transition.setAlignment(Okular::PageTransition::Vertical);
1756         return transition;
1757         break;
1758     }
1759     case Okular::Settings::EnumSlidesTransition::BoxIn: {
1760         Okular::PageTransition transition(Okular::PageTransition::Box);
1761         transition.setDirection(Okular::PageTransition::Inward);
1762         return transition;
1763         break;
1764     }
1765     case Okular::Settings::EnumSlidesTransition::BoxOut: {
1766         Okular::PageTransition transition(Okular::PageTransition::Box);
1767         transition.setDirection(Okular::PageTransition::Outward);
1768         return transition;
1769         break;
1770     }
1771     case Okular::Settings::EnumSlidesTransition::Dissolve: {
1772         return Okular::PageTransition(Okular::PageTransition::Dissolve);
1773         break;
1774     }
1775     case Okular::Settings::EnumSlidesTransition::GlitterDown: {
1776         Okular::PageTransition transition(Okular::PageTransition::Glitter);
1777         transition.setAngle(270);
1778         return transition;
1779         break;
1780     }
1781     case Okular::Settings::EnumSlidesTransition::GlitterRight: {
1782         Okular::PageTransition transition(Okular::PageTransition::Glitter);
1783         transition.setAngle(0);
1784         return transition;
1785         break;
1786     }
1787     case Okular::Settings::EnumSlidesTransition::GlitterRightDown: {
1788         Okular::PageTransition transition(Okular::PageTransition::Glitter);
1789         transition.setAngle(315);
1790         return transition;
1791         break;
1792     }
1793     case Okular::Settings::EnumSlidesTransition::Random: {
1794         return defaultTransition(KRandom::random() % 18);
1795         break;
1796     }
1797     case Okular::Settings::EnumSlidesTransition::SplitHorizontalIn: {
1798         Okular::PageTransition transition(Okular::PageTransition::Split);
1799         transition.setAlignment(Okular::PageTransition::Horizontal);
1800         transition.setDirection(Okular::PageTransition::Inward);
1801         return transition;
1802         break;
1803     }
1804     case Okular::Settings::EnumSlidesTransition::SplitHorizontalOut: {
1805         Okular::PageTransition transition(Okular::PageTransition::Split);
1806         transition.setAlignment(Okular::PageTransition::Horizontal);
1807         transition.setDirection(Okular::PageTransition::Outward);
1808         return transition;
1809         break;
1810     }
1811     case Okular::Settings::EnumSlidesTransition::SplitVerticalIn: {
1812         Okular::PageTransition transition(Okular::PageTransition::Split);
1813         transition.setAlignment(Okular::PageTransition::Vertical);
1814         transition.setDirection(Okular::PageTransition::Inward);
1815         return transition;
1816         break;
1817     }
1818     case Okular::Settings::EnumSlidesTransition::SplitVerticalOut: {
1819         Okular::PageTransition transition(Okular::PageTransition::Split);
1820         transition.setAlignment(Okular::PageTransition::Vertical);
1821         transition.setDirection(Okular::PageTransition::Outward);
1822         return transition;
1823         break;
1824     }
1825     case Okular::Settings::EnumSlidesTransition::WipeDown: {
1826         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1827         transition.setAngle(270);
1828         return transition;
1829         break;
1830     }
1831     case Okular::Settings::EnumSlidesTransition::WipeRight: {
1832         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1833         transition.setAngle(0);
1834         return transition;
1835         break;
1836     }
1837     case Okular::Settings::EnumSlidesTransition::WipeLeft: {
1838         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1839         transition.setAngle(180);
1840         return transition;
1841         break;
1842     }
1843     case Okular::Settings::EnumSlidesTransition::WipeUp: {
1844         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1845         transition.setAngle(90);
1846         return transition;
1847         break;
1848     }
1849     case Okular::Settings::EnumSlidesTransition::Fade: {
1850         return Okular::PageTransition(Okular::PageTransition::Fade);
1851         break;
1852     }
1853     case Okular::Settings::EnumSlidesTransition::NoTransitions:
1854     case Okular::Settings::EnumSlidesTransition::Replace:
1855     default:
1856         return Okular::PageTransition(Okular::PageTransition::Replace);
1857         break;
1858     }
1859     // should not happen, just make gcc happy
1860     return Okular::PageTransition();
1861 }
1862 
1863 /** ONLY the TRANSITIONS GENERATION function from here on **/
1864 void PresentationWidget::initTransition(const Okular::PageTransition *transition)
1865 {
1866     // if it's just a 'replace' transition, repaint the screen
1867     if (transition->type() == Okular::PageTransition::Replace) {
1868         update();
1869         return;
1870     }
1871 
1872     const bool isInward = transition->direction() == Okular::PageTransition::Inward;
1873     const bool isHorizontal = transition->alignment() == Okular::PageTransition::Horizontal;
1874     const float totalTime = transition->duration();
1875 
1876     m_transitionRects.clear();
1877     m_currentTransition = *transition;
1878     m_currentPagePixmap = m_lastRenderedPixmap;
1879 
1880     switch (transition->type()) {
1881         // split: horizontal / vertical and inward / outward
1882     case Okular::PageTransition::Split: {
1883         const int steps = isHorizontal ? 100 : 75;
1884         if (isHorizontal) {
1885             if (isInward) {
1886                 int xPosition = 0;
1887                 for (int i = 0; i < steps; i++) {
1888                     int xNext = ((i + 1) * m_width) / (2 * steps);
1889                     m_transitionRects.push_back(QRect(xPosition, 0, xNext - xPosition, m_height));
1890                     m_transitionRects.push_back(QRect(m_width - xNext, 0, xNext - xPosition, m_height));
1891                     xPosition = xNext;
1892                 }
1893             } else {
1894                 int xPosition = m_width / 2;
1895                 for (int i = 0; i < steps; i++) {
1896                     int xNext = ((steps - (i + 1)) * m_width) / (2 * steps);
1897                     m_transitionRects.push_back(QRect(xNext, 0, xPosition - xNext, m_height));
1898                     m_transitionRects.push_back(QRect(m_width - xPosition, 0, xPosition - xNext, m_height));
1899                     xPosition = xNext;
1900                 }
1901             }
1902         } else {
1903             if (isInward) {
1904                 int yPosition = 0;
1905                 for (int i = 0; i < steps; i++) {
1906                     int yNext = ((i + 1) * m_height) / (2 * steps);
1907                     m_transitionRects.push_back(QRect(0, yPosition, m_width, yNext - yPosition));
1908                     m_transitionRects.push_back(QRect(0, m_height - yNext, m_width, yNext - yPosition));
1909                     yPosition = yNext;
1910                 }
1911             } else {
1912                 int yPosition = m_height / 2;
1913                 for (int i = 0; i < steps; i++) {
1914                     int yNext = ((steps - (i + 1)) * m_height) / (2 * steps);
1915                     m_transitionRects.push_back(QRect(0, yNext, m_width, yPosition - yNext));
1916                     m_transitionRects.push_back(QRect(0, m_height - yPosition, m_width, yPosition - yNext));
1917                     yPosition = yNext;
1918                 }
1919             }
1920         }
1921         m_transitionMul = 2;
1922         m_transitionDelay = (int)((totalTime * 1000) / steps);
1923     } break;
1924 
1925         // blinds: horizontal(l-to-r) / vertical(t-to-b)
1926     case Okular::PageTransition::Blinds: {
1927         const int blinds = isHorizontal ? 8 : 6;
1928         const int steps = m_width / (4 * blinds);
1929         if (isHorizontal) {
1930             int xPosition[8];
1931             for (int b = 0; b < blinds; b++)
1932                 xPosition[b] = (b * m_width) / blinds;
1933 
1934             for (int i = 0; i < steps; i++) {
1935                 int stepOffset = (int)(((float)i * (float)m_width) / ((float)blinds * (float)steps));
1936                 for (int b = 0; b < blinds; b++) {
1937                     m_transitionRects.push_back(QRect(xPosition[b], 0, stepOffset, m_height));
1938                     xPosition[b] = stepOffset + (b * m_width) / blinds;
1939                 }
1940             }
1941         } else {
1942             int yPosition[6];
1943             for (int b = 0; b < blinds; b++)
1944                 yPosition[b] = (b * m_height) / blinds;
1945 
1946             for (int i = 0; i < steps; i++) {
1947                 int stepOffset = (int)(((float)i * (float)m_height) / ((float)blinds * (float)steps));
1948                 for (int b = 0; b < blinds; b++) {
1949                     m_transitionRects.push_back(QRect(0, yPosition[b], m_width, stepOffset));
1950                     yPosition[b] = stepOffset + (b * m_height) / blinds;
1951                 }
1952             }
1953         }
1954         m_transitionMul = blinds;
1955         m_transitionDelay = (int)((totalTime * 1000) / steps);
1956     } break;
1957 
1958         // box: inward / outward
1959     case Okular::PageTransition::Box: {
1960         const int steps = m_width / 10;
1961         if (isInward) {
1962             int L = 0, T = 0, R = m_width, B = m_height;
1963             for (int i = 0; i < steps; i++) {
1964                 // compute shrunk box coords
1965                 int newL = ((i + 1) * m_width) / (2 * steps);
1966                 int newT = ((i + 1) * m_height) / (2 * steps);
1967                 int newR = m_width - newL;
1968                 int newB = m_height - newT;
1969                 // add left, right, topcenter, bottomcenter rects
1970                 m_transitionRects.push_back(QRect(L, T, newL - L, B - T));
1971                 m_transitionRects.push_back(QRect(newR, T, R - newR, B - T));
1972                 m_transitionRects.push_back(QRect(newL, T, newR - newL, newT - T));
1973                 m_transitionRects.push_back(QRect(newL, newB, newR - newL, B - newB));
1974                 L = newL;
1975                 T = newT;
1976                 R = newR, B = newB;
1977             }
1978         } else {
1979             int L = m_width / 2, T = m_height / 2, R = L, B = T;
1980             for (int i = 0; i < steps; i++) {
1981                 // compute shrunk box coords
1982                 int newL = ((steps - (i + 1)) * m_width) / (2 * steps);
1983                 int newT = ((steps - (i + 1)) * m_height) / (2 * steps);
1984                 int newR = m_width - newL;
1985                 int newB = m_height - newT;
1986                 // add left, right, topcenter, bottomcenter rects
1987                 m_transitionRects.push_back(QRect(newL, newT, L - newL, newB - newT));
1988                 m_transitionRects.push_back(QRect(R, newT, newR - R, newB - newT));
1989                 m_transitionRects.push_back(QRect(L, newT, R - L, T - newT));
1990                 m_transitionRects.push_back(QRect(L, B, R - L, newB - B));
1991                 L = newL;
1992                 T = newT;
1993                 R = newR, B = newB;
1994             }
1995         }
1996         m_transitionMul = 4;
1997         m_transitionDelay = (int)((totalTime * 1000) / steps);
1998     } break;
1999 
2000         // wipe: implemented for 4 canonical angles
2001     case Okular::PageTransition::Wipe: {
2002         const int angle = transition->angle();
2003         const int steps = (angle == 0) || (angle == 180) ? m_width / 8 : m_height / 8;
2004         if (angle == 0) {
2005             int xPosition = 0;
2006             for (int i = 0; i < steps; i++) {
2007                 int xNext = ((i + 1) * m_width) / steps;
2008                 m_transitionRects.push_back(QRect(xPosition, 0, xNext - xPosition, m_height));
2009                 xPosition = xNext;
2010             }
2011         } else if (angle == 90) {
2012             int yPosition = m_height;
2013             for (int i = 0; i < steps; i++) {
2014                 int yNext = ((steps - (i + 1)) * m_height) / steps;
2015                 m_transitionRects.push_back(QRect(0, yNext, m_width, yPosition - yNext));
2016                 yPosition = yNext;
2017             }
2018         } else if (angle == 180) {
2019             int xPosition = m_width;
2020             for (int i = 0; i < steps; i++) {
2021                 int xNext = ((steps - (i + 1)) * m_width) / steps;
2022                 m_transitionRects.push_back(QRect(xNext, 0, xPosition - xNext, m_height));
2023                 xPosition = xNext;
2024             }
2025         } else if (angle == 270) {
2026             int yPosition = 0;
2027             for (int i = 0; i < steps; i++) {
2028                 int yNext = ((i + 1) * m_height) / steps;
2029                 m_transitionRects.push_back(QRect(0, yPosition, m_width, yNext - yPosition));
2030                 yPosition = yNext;
2031             }
2032         } else {
2033             update();
2034             return;
2035         }
2036         m_transitionMul = 1;
2037         m_transitionDelay = (int)((totalTime * 1000) / steps);
2038     } break;
2039 
2040         // dissolve: replace 'random' rects
2041     case Okular::PageTransition::Dissolve: {
2042         const int gridXsteps = 50;
2043         const int gridYsteps = 38;
2044         const int steps = gridXsteps * gridYsteps;
2045         int oldX = 0;
2046         int oldY = 0;
2047         // create a grid of gridXstep by gridYstep QRects
2048         for (int y = 0; y < gridYsteps; y++) {
2049             int newY = (int)(m_height * ((float)(y + 1) / (float)gridYsteps));
2050             for (int x = 0; x < gridXsteps; x++) {
2051                 int newX = (int)(m_width * ((float)(x + 1) / (float)gridXsteps));
2052                 m_transitionRects.push_back(QRect(oldX, oldY, newX - oldX, newY - oldY));
2053                 oldX = newX;
2054             }
2055             oldX = 0;
2056             oldY = newY;
2057         }
2058         // randomize the grid
2059         for (int i = 0; i < steps; i++) {
2060 #ifndef Q_OS_WIN
2061             int n1 = (int)(steps * drand48());
2062             int n2 = (int)(steps * drand48());
2063 #else
2064             int n1 = (int)(steps * (std::rand() / RAND_MAX));
2065             int n2 = (int)(steps * (std::rand() / RAND_MAX));
2066 #endif
2067             // swap items if index differs
2068             if (n1 != n2) {
2069                 QRect r = m_transitionRects[n2];
2070                 m_transitionRects[n2] = m_transitionRects[n1];
2071                 m_transitionRects[n1] = r;
2072             }
2073         }
2074         // set global transition parameters
2075         m_transitionMul = 40;
2076         m_transitionDelay = (int)((m_transitionMul * 1000 * totalTime) / steps);
2077     } break;
2078 
2079         // glitter: similar to dissolve but has a direction
2080     case Okular::PageTransition::Glitter: {
2081         const int gridXsteps = 50;
2082         const int gridYsteps = 38;
2083         const int steps = gridXsteps * gridYsteps;
2084         const int angle = transition->angle();
2085         // generate boxes using a given direction
2086         if (angle == 90) {
2087             int yPosition = m_height;
2088             for (int i = 0; i < gridYsteps; i++) {
2089                 int yNext = ((gridYsteps - (i + 1)) * m_height) / gridYsteps;
2090                 int xPosition = 0;
2091                 for (int j = 0; j < gridXsteps; j++) {
2092                     int xNext = ((j + 1) * m_width) / gridXsteps;
2093                     m_transitionRects.push_back(QRect(xPosition, yNext, xNext - xPosition, yPosition - yNext));
2094                     xPosition = xNext;
2095                 }
2096                 yPosition = yNext;
2097             }
2098         } else if (angle == 180) {
2099             int xPosition = m_width;
2100             for (int i = 0; i < gridXsteps; i++) {
2101                 int xNext = ((gridXsteps - (i + 1)) * m_width) / gridXsteps;
2102                 int yPosition = 0;
2103                 for (int j = 0; j < gridYsteps; j++) {
2104                     int yNext = ((j + 1) * m_height) / gridYsteps;
2105                     m_transitionRects.push_back(QRect(xNext, yPosition, xPosition - xNext, yNext - yPosition));
2106                     yPosition = yNext;
2107                 }
2108                 xPosition = xNext;
2109             }
2110         } else if (angle == 270) {
2111             int yPosition = 0;
2112             for (int i = 0; i < gridYsteps; i++) {
2113                 int yNext = ((i + 1) * m_height) / gridYsteps;
2114                 int xPosition = 0;
2115                 for (int j = 0; j < gridXsteps; j++) {
2116                     int xNext = ((j + 1) * m_width) / gridXsteps;
2117                     m_transitionRects.push_back(QRect(xPosition, yPosition, xNext - xPosition, yNext - yPosition));
2118                     xPosition = xNext;
2119                 }
2120                 yPosition = yNext;
2121             }
2122         } else // if angle is 0 or 315
2123         {
2124             int xPosition = 0;
2125             for (int i = 0; i < gridXsteps; i++) {
2126                 int xNext = ((i + 1) * m_width) / gridXsteps;
2127                 int yPosition = 0;
2128                 for (int j = 0; j < gridYsteps; j++) {
2129                     int yNext = ((j + 1) * m_height) / gridYsteps;
2130                     m_transitionRects.push_back(QRect(xPosition, yPosition, xNext - xPosition, yNext - yPosition));
2131                     yPosition = yNext;
2132                 }
2133                 xPosition = xNext;
2134             }
2135         }
2136         // add a 'glitter' (1 over 10 pieces is randomized)
2137         int randomSteps = steps / 20;
2138         for (int i = 0; i < randomSteps; i++) {
2139 #ifndef Q_OS_WIN
2140             int n1 = (int)(steps * drand48());
2141             int n2 = (int)(steps * drand48());
2142 #else
2143             int n1 = (int)(steps * (std::rand() / RAND_MAX));
2144             int n2 = (int)(steps * (std::rand() / RAND_MAX));
2145 #endif
2146             // swap items if index differs
2147             if (n1 != n2) {
2148                 QRect r = m_transitionRects[n2];
2149                 m_transitionRects[n2] = m_transitionRects[n1];
2150                 m_transitionRects[n1] = r;
2151             }
2152         }
2153         // set global transition parameters
2154         m_transitionMul = (angle == 90) || (angle == 270) ? gridYsteps : gridXsteps;
2155         m_transitionMul /= 2;
2156         m_transitionDelay = (int)((m_transitionMul * 1000 * totalTime) / steps);
2157     } break;
2158 
2159     case Okular::PageTransition::Fade: {
2160         enum { FADE_TRANSITION_FPS = 20 };
2161         const int steps = totalTime * FADE_TRANSITION_FPS;
2162         m_transitionSteps = steps;
2163         QPainter pixmapPainter;
2164         m_currentPixmapOpacity = (double)1 / steps;
2165         m_transitionDelay = (int)(totalTime * 1000) / steps;
2166         m_lastRenderedPixmap = QPixmap(m_lastRenderedPixmap.size());
2167         m_lastRenderedPixmap.fill(Qt::transparent);
2168         pixmapPainter.begin(&m_lastRenderedPixmap);
2169         pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
2170         pixmapPainter.setOpacity(1 - m_currentPixmapOpacity);
2171         pixmapPainter.drawPixmap(0, 0, m_previousPagePixmap);
2172         pixmapPainter.setOpacity(m_currentPixmapOpacity);
2173         pixmapPainter.drawPixmap(0, 0, m_currentPagePixmap);
2174         pixmapPainter.end();
2175         update();
2176     } break;
2177     // implement missing transitions (a binary raster engine needed here)
2178     case Okular::PageTransition::Fly:
2179 
2180     case Okular::PageTransition::Push:
2181 
2182     case Okular::PageTransition::Cover:
2183 
2184     case Okular::PageTransition::Uncover:
2185 
2186     default:
2187         update();
2188         return;
2189     }
2190 
2191     // send the first start to the timer
2192     m_transitionTimer->start(0);
2193 }
2194 
2195 void PresentationWidget::slotProcessMovieAction(const Okular::MovieAction *action)
2196 {
2197     const Okular::MovieAnnotation *movieAnnotation = action->annotation();
2198     if (!movieAnnotation)
2199         return;
2200 
2201     Okular::Movie *movie = movieAnnotation->movie();
2202     if (!movie)
2203         return;
2204 
2205     VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movieAnnotation->movie());
2206     if (!vw)
2207         return;
2208 
2209     vw->show();
2210 
2211     switch (action->operation()) {
2212     case Okular::MovieAction::Play:
2213         vw->stop();
2214         vw->play();
2215         break;
2216     case Okular::MovieAction::Stop:
2217         vw->stop();
2218         break;
2219     case Okular::MovieAction::Pause:
2220         vw->pause();
2221         break;
2222     case Okular::MovieAction::Resume:
2223         vw->play();
2224         break;
2225     };
2226 }
2227 
2228 void PresentationWidget::slotProcessRenditionAction(const Okular::RenditionAction *action)
2229 {
2230     Okular::Movie *movie = action->movie();
2231     if (!movie)
2232         return;
2233 
2234     VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movie);
2235     if (!vw)
2236         return;
2237 
2238     if (action->operation() == Okular::RenditionAction::None)
2239         return;
2240 
2241     vw->show();
2242 
2243     switch (action->operation()) {
2244     case Okular::RenditionAction::Play:
2245         vw->stop();
2246         vw->play();
2247         break;
2248     case Okular::RenditionAction::Stop:
2249         vw->stop();
2250         break;
2251     case Okular::RenditionAction::Pause:
2252         vw->pause();
2253         break;
2254     case Okular::RenditionAction::Resume:
2255         vw->play();
2256         break;
2257     default:
2258         return;
2259     };
2260 }
2261 
2262 void PresentationWidget::slotTogglePlayPause()
2263 {
2264     if (!m_nextPageTimer->isActive()) {
2265         m_advanceSlides = true;
2266         startAutoChangeTimer();
2267     } else {
2268         m_nextPageTimer->stop();
2269         m_advanceSlides = false;
2270         setPlayPauseIcon();
2271     }
2272 }
2273 
2274 #include "presentationwidget.moc"
2275