File indexing completed on 2024-04-28 15:51:56

0001 /*
0002     SPDX-FileCopyrightText: 2004 Enrico Ros <eros.kde@email.it>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "presentationwidget.h"
0008 #include "config-okular.h"
0009 
0010 // qt/kde includes
0011 #if HAVE_DBUS
0012 #include <QDBusConnection>
0013 #include <QDBusMessage>
0014 #include <QDBusReply>
0015 #endif
0016 #include <QLoggingCategory>
0017 
0018 #include <KActionCollection>
0019 #include <KCursor>
0020 #include <KLineEdit>
0021 #include <KLocalizedString>
0022 #include <KMessageBox>
0023 #include <KSelectAction>
0024 #include <QAction>
0025 #include <QActionGroup>
0026 #include <QApplication>
0027 #include <QDialog>
0028 #include <QEvent>
0029 #include <QFontMetrics>
0030 #include <QGestureEvent>
0031 #include <QIcon>
0032 #include <QImage>
0033 #include <QLabel>
0034 #include <QLayout>
0035 #include <QPainter>
0036 #include <QRandomGenerator>
0037 #include <QScreen>
0038 #include <QStyle>
0039 #include <QStyleOption>
0040 #include <QTimer>
0041 #include <QToolBar>
0042 #include <QToolTip>
0043 #include <QValidator>
0044 
0045 #ifdef Q_OS_LINUX
0046 #if HAVE_DBUS
0047 #include <QDBusUnixFileDescriptor>
0048 #endif
0049 #include <unistd.h> // For ::close() for sleep inhibition
0050 #endif
0051 
0052 // system includes
0053 #include <math.h>
0054 #include <stdlib.h>
0055 
0056 // local includes
0057 #include "annotationtools.h"
0058 #include "core/action.h"
0059 #include "core/annotations.h"
0060 #include "core/audioplayer.h"
0061 #include "core/document.h"
0062 #include "core/generator.h"
0063 #include "core/movie.h"
0064 #include "core/page.h"
0065 #include "drawingtoolactions.h"
0066 #include "gui/debug_ui.h"
0067 #include "gui/guiutils.h"
0068 #include "gui/pagepainter.h"
0069 #include "gui/priorities.h"
0070 #include "presentationsearchbar.h"
0071 #include "settings.h"
0072 #include "settings_core.h"
0073 #include "videowidget.h"
0074 
0075 // comment this to disable the top-right progress indicator
0076 #define ENABLE_PROGRESS_OVERLAY
0077 
0078 // a frame contains a pointer to the page object, its geometry and the
0079 // transition effect to the next frame
0080 struct PresentationFrame {
0081     PresentationFrame() = default;
0082 
0083     ~PresentationFrame()
0084     {
0085         qDeleteAll(videoWidgets);
0086     }
0087 
0088     PresentationFrame(const PresentationFrame &) = delete;
0089     PresentationFrame &operator=(const PresentationFrame &) = delete;
0090 
0091     void recalcGeometry(int width, int height, float screenRatio)
0092     {
0093         // calculate frame geometry keeping constant aspect ratio
0094         float pageRatio = page->ratio();
0095         int pageWidth = width, pageHeight = height;
0096         if (pageRatio > screenRatio) {
0097             pageWidth = (int)((float)pageHeight / pageRatio);
0098         } else {
0099             pageHeight = (int)((float)pageWidth * pageRatio);
0100         }
0101         geometry.setRect((width - pageWidth) / 2, (height - pageHeight) / 2, pageWidth, pageHeight);
0102 
0103         for (VideoWidget *vw : std::as_const(videoWidgets)) {
0104             const Okular::NormalizedRect r = vw->normGeometry();
0105             QRect vwgeom = r.geometry(geometry.width(), geometry.height());
0106             vw->resize(vwgeom.size());
0107             vw->move(geometry.topLeft() + vwgeom.topLeft());
0108         }
0109     }
0110 
0111     const Okular::Page *page;
0112     QRect geometry;
0113     QHash<Okular::Movie *, VideoWidget *> videoWidgets;
0114     std::vector<SmoothPath> drawings;
0115 };
0116 
0117 // a custom QToolBar that basically does not propagate the event if the widget
0118 // background is not automatically filled
0119 class PresentationToolBar : public QToolBar
0120 {
0121     Q_OBJECT
0122 
0123 public:
0124     explicit PresentationToolBar(QWidget *parent = Q_NULLPTR)
0125         : QToolBar(parent)
0126     {
0127     }
0128 
0129 protected:
0130     void mousePressEvent(QMouseEvent *e) override
0131     {
0132         QToolBar::mousePressEvent(e);
0133         e->accept();
0134     }
0135 
0136     void mouseReleaseEvent(QMouseEvent *e) override
0137     {
0138         QToolBar::mouseReleaseEvent(e);
0139         e->accept();
0140     }
0141 };
0142 
0143 PresentationWidget::PresentationWidget(QWidget *parent, Okular::Document *doc, DrawingToolActions *drawingToolActions, KActionCollection *collection)
0144     : QWidget(nullptr /* must be null, to have an independent widget */, Qt::FramelessWindowHint)
0145     , m_pressedLink(nullptr)
0146     , m_handCursor(false)
0147     , m_drawingEngine(nullptr)
0148     , m_screenInhibitCookie(0)
0149     , m_sleepInhibitFd(-1)
0150     , m_parentWidget(parent)
0151     , m_document(doc)
0152     , m_frameIndex(-1)
0153     , m_topBar(nullptr)
0154     , m_pagesEdit(nullptr)
0155     , m_searchBar(nullptr)
0156     , m_ac(collection)
0157     , m_screenSelect(nullptr)
0158     , m_isSetup(false)
0159     , m_blockNotifications(false)
0160     , m_inBlackScreenMode(false)
0161     , m_showSummaryView(Okular::Settings::slidesShowSummary())
0162     , m_advanceSlides(Okular::SettingsCore::slidesAdvance())
0163     , m_goToPreviousPageOnRelease(false)
0164     , m_goToNextPageOnRelease(false)
0165 {
0166     setAttribute(Qt::WA_DeleteOnClose);
0167     setAttribute(Qt::WA_OpaquePaintEvent);
0168     setObjectName(QStringLiteral("presentationWidget"));
0169     QString caption = doc->metaData(QStringLiteral("DocumentTitle")).toString();
0170     if (caption.trimmed().isEmpty()) {
0171         caption = doc->currentDocument().fileName();
0172     }
0173     caption = i18nc("[document title/filename] – Presentation", "%1 – Presentation", caption);
0174     setWindowTitle(caption);
0175 
0176     m_width = -1;
0177 
0178     // create top toolbar
0179     m_topBar = new PresentationToolBar(this);
0180     m_topBar->setObjectName(QStringLiteral("presentationBar"));
0181     m_topBar->setMovable(false);
0182     m_topBar->layout()->setContentsMargins(0, 0, 0, 0);
0183     m_topBar->addAction(QIcon::fromTheme(layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-next") : QStringLiteral("go-previous")), i18n("Previous Page"), this, SLOT(slotPrevPage()));
0184     m_pagesEdit = new KLineEdit(m_topBar);
0185     QSizePolicy sp = m_pagesEdit->sizePolicy();
0186     sp.setHorizontalPolicy(QSizePolicy::Minimum);
0187     m_pagesEdit->setSizePolicy(sp);
0188     QFontMetrics fm(m_pagesEdit->font());
0189     QStyleOptionFrame option;
0190     option.initFrom(m_pagesEdit);
0191     m_pagesEdit->setMaximumWidth(fm.horizontalAdvance(QString::number(m_document->pages())) + 2 * style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, m_pagesEdit) +
0192                                  4); // the 4 comes from 2*horizontalMargin, horizontalMargin being a define in qlineedit.cpp
0193     QIntValidator *validator = new QIntValidator(1, m_document->pages(), m_pagesEdit);
0194     m_pagesEdit->setValidator(validator);
0195     m_topBar->addWidget(m_pagesEdit);
0196     QLabel *pagesLabel = new QLabel(m_topBar);
0197     pagesLabel->setText(QLatin1String(" / ") + QString::number(m_document->pages()) + QLatin1String(" "));
0198     m_topBar->addWidget(pagesLabel);
0199     connect(m_pagesEdit, &QLineEdit::returnPressed, this, &PresentationWidget::slotPageChanged);
0200     m_topBar->addAction(QIcon::fromTheme(layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-previous") : QStringLiteral("go-next")), i18n("Next Page"), this, SLOT(slotNextPage()));
0201     m_topBar->addSeparator();
0202     QAction *playPauseAct = collection->action(QStringLiteral("presentation_play_pause"));
0203     playPauseAct->setEnabled(true);
0204     connect(playPauseAct, &QAction::triggered, this, &PresentationWidget::slotTogglePlayPause);
0205     m_topBar->addAction(playPauseAct);
0206     addAction(playPauseAct);
0207     m_topBar->addSeparator();
0208 
0209     const QList<QAction *> actionsList = drawingToolActions->actions();
0210     for (QAction *action : actionsList) {
0211         action->setEnabled(true);
0212         m_topBar->addAction(action);
0213         addAction(action);
0214     }
0215     connect(drawingToolActions, &DrawingToolActions::changeEngine, this, &PresentationWidget::slotChangeDrawingToolEngine);
0216     connect(drawingToolActions, &DrawingToolActions::actionsRecreated, this, &PresentationWidget::slotAddDrawingToolActions);
0217 
0218     QAction *eraseDrawingAct = collection->action(QStringLiteral("presentation_erase_drawings"));
0219     eraseDrawingAct->setEnabled(true);
0220     connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings);
0221     m_topBar->addAction(eraseDrawingAct);
0222     addAction(eraseDrawingAct);
0223 
0224     const int screenCount = QApplication::screens().count();
0225     if (screenCount > 1) {
0226         m_topBar->addSeparator();
0227         m_screenSelect = new KSelectAction(QIcon::fromTheme(QStringLiteral("video-display")), i18n("Switch Screen"), m_topBar);
0228         m_screenSelect->setToolBarMode(KSelectAction::MenuMode);
0229         m_screenSelect->setToolButtonPopupMode(QToolButton::InstantPopup);
0230         m_topBar->addAction(m_screenSelect);
0231         for (int i = 0; i < screenCount; ++i) {
0232             QAction *act = m_screenSelect->addAction(i18nc("%1 is the screen number (0, 1, ...)", "Screen %1", i));
0233             act->setData(QVariant::fromValue(i));
0234         }
0235     }
0236     QWidget *spacer = new QWidget(m_topBar);
0237     spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
0238     m_topBar->addWidget(spacer);
0239     m_topBar->addAction(QIcon::fromTheme(QStringLiteral("application-exit")), i18n("Exit Presentation Mode"), this, SLOT(close()));
0240     m_topBar->setAutoFillBackground(true);
0241     showTopBar(false);
0242     // change topbar background color
0243     QPalette p = m_topBar->palette();
0244     p.setColor(QPalette::Active, QPalette::Button, Qt::gray);
0245     p.setColor(QPalette::Active, QPalette::Window, Qt::darkGray);
0246     m_topBar->setPalette(p);
0247 
0248     // Grab swipe gestures to change pages
0249     grabGesture(Qt::SwipeGesture);
0250 
0251     // misc stuff
0252     setMouseTracking(true);
0253     setContextMenuPolicy(Qt::PreventContextMenu);
0254     m_transitionTimer = new QTimer(this);
0255     m_transitionTimer->setSingleShot(true);
0256     connect(m_transitionTimer, &QTimer::timeout, this, &PresentationWidget::slotTransitionStep);
0257     m_overlayHideTimer = new QTimer(this);
0258     m_overlayHideTimer->setSingleShot(true);
0259     connect(m_overlayHideTimer, &QTimer::timeout, this, &PresentationWidget::slotHideOverlay);
0260     m_nextPageTimer = new QTimer(this);
0261     m_nextPageTimer->setSingleShot(true);
0262     connect(m_nextPageTimer, &QTimer::timeout, this, &PresentationWidget::slotNextPage);
0263     setPlayPauseIcon();
0264 
0265     connect(m_document, &Okular::Document::processMovieAction, this, &PresentationWidget::slotProcessMovieAction);
0266     connect(m_document, &Okular::Document::processRenditionAction, this, &PresentationWidget::slotProcessRenditionAction);
0267 
0268     // handle cursor appearance as specified in configuration
0269     if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
0270         KCursor::setAutoHideCursor(this, true);
0271         KCursor::setHideCursorDelay(3000);
0272     } else if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) {
0273         setCursor(QCursor(Qt::BlankCursor));
0274     }
0275 
0276     setupActions();
0277 
0278     // inhibit power management
0279     inhibitPowerManagement();
0280 
0281     QTimer::singleShot(0, this, &PresentationWidget::slotDelayedEvents);
0282 
0283     // setFocus() so KCursor::setAutoHideCursor() goes into effect if it's enabled
0284     setFocus(Qt::OtherFocusReason);
0285 
0286     // Catch TabletEnterProximity and TabletLeaveProximity events from the QApplication
0287     qApp->installEventFilter(this);
0288 }
0289 
0290 PresentationWidget::~PresentationWidget()
0291 {
0292     // allow power management saver again
0293     allowPowerManagement();
0294 
0295     // stop the audio playbacks
0296     Okular::AudioPlayer::instance()->stopPlaybacks();
0297 
0298     // remove our highlights
0299     if (m_searchBar) {
0300         m_document->resetSearch(PRESENTATION_SEARCH_ID);
0301     }
0302 
0303     // remove this widget from document observer
0304     m_document->removeObserver(this);
0305 
0306     const QList<QAction *> actionsList = actions();
0307     for (QAction *action : actionsList) {
0308         action->setChecked(false);
0309         action->setEnabled(false);
0310     }
0311 
0312     delete m_drawingEngine;
0313 
0314     // delete frames
0315     qDeleteAll(m_frames);
0316 
0317     qApp->removeEventFilter(this);
0318 }
0319 
0320 void PresentationWidget::notifySetup(const QVector<Okular::Page *> &pageSet, int setupFlags)
0321 {
0322     // same document, nothing to change - here we assume the document sets up
0323     // us with the whole document set as first notifySetup()
0324     if (!(setupFlags & Okular::DocumentObserver::DocumentChanged)) {
0325         return;
0326     }
0327 
0328     // delete previous frames (if any (shouldn't be))
0329     qDeleteAll(m_frames);
0330     if (!m_frames.isEmpty()) {
0331         qCWarning(OkularUiDebug) << "Frames setup changed while a Presentation is in progress.";
0332     }
0333     m_frames.clear();
0334 
0335     // create the new frames
0336     float screenRatio = (float)m_height / (float)m_width;
0337     for (const Okular::Page *page : pageSet) {
0338         PresentationFrame *frame = new PresentationFrame();
0339         frame->page = page;
0340         const QList<Okular::Annotation *> annotations = page->annotations();
0341         for (Okular::Annotation *a : annotations) {
0342             if (a->subType() == Okular::Annotation::AMovie) {
0343                 Okular::MovieAnnotation *movieAnn = static_cast<Okular::MovieAnnotation *>(a);
0344                 VideoWidget *vw = new VideoWidget(movieAnn, movieAnn->movie(), m_document, this);
0345                 frame->videoWidgets.insert(movieAnn->movie(), vw);
0346                 vw->pageInitialized();
0347             } else if (a->subType() == Okular::Annotation::ARichMedia) {
0348                 Okular::RichMediaAnnotation *richMediaAnn = static_cast<Okular::RichMediaAnnotation *>(a);
0349                 if (richMediaAnn->movie()) {
0350                     VideoWidget *vw = new VideoWidget(richMediaAnn, richMediaAnn->movie(), m_document, this);
0351                     frame->videoWidgets.insert(richMediaAnn->movie(), vw);
0352                     vw->pageInitialized();
0353                 }
0354             } else if (a->subType() == Okular::Annotation::AScreen) {
0355                 const Okular::ScreenAnnotation *screenAnn = static_cast<Okular::ScreenAnnotation *>(a);
0356                 Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation(screenAnn);
0357                 if (movie) {
0358                     VideoWidget *vw = new VideoWidget(screenAnn, movie, m_document, this);
0359                     frame->videoWidgets.insert(movie, vw);
0360                     vw->pageInitialized();
0361                 }
0362             }
0363         }
0364         frame->recalcGeometry(m_width, m_height, screenRatio);
0365         // add the frame to the vector
0366         m_frames.push_back(frame);
0367     }
0368 
0369     // get metadata from the document
0370     m_metaStrings.clear();
0371     const Okular::DocumentInfo info = m_document->documentInfo(QSet<Okular::DocumentInfo::Key>() << Okular::DocumentInfo::Title << Okular::DocumentInfo::Author);
0372     if (!info.get(Okular::DocumentInfo::Title).isNull()) {
0373         m_metaStrings += i18n("Title: %1", info.get(Okular::DocumentInfo::Title));
0374     }
0375     if (!info.get(Okular::DocumentInfo::Author).isNull()) {
0376         m_metaStrings += i18n("Author: %1", info.get(Okular::DocumentInfo::Author));
0377     }
0378     m_metaStrings += i18n("Pages: %1", m_document->pages());
0379     m_metaStrings += i18n("Click to begin");
0380 
0381     m_isSetup = true;
0382 }
0383 
0384 void PresentationWidget::notifyViewportChanged(bool /*smoothMove*/)
0385 {
0386     // display the current page
0387     changePage(m_document->viewport().pageNumber);
0388 
0389     // auto advance to the next page if set
0390     startAutoChangeTimer();
0391 }
0392 
0393 void PresentationWidget::notifyPageChanged(int pageNumber, int changedFlags)
0394 {
0395     // if we are blocking the notifications, do nothing
0396     if (m_blockNotifications) {
0397         return;
0398     }
0399 
0400     // check if it's the last requested pixmap. if so update the widget.
0401     if ((changedFlags & (DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights)) && pageNumber == m_frameIndex) {
0402         generatePage(changedFlags & (DocumentObserver::Annotations | DocumentObserver::Highlights));
0403     }
0404 }
0405 
0406 void PresentationWidget::notifyCurrentPageChanged(int previousPage, int currentPage)
0407 {
0408     if (previousPage != -1) {
0409         // stop video playback
0410         for (VideoWidget *vw : std::as_const(m_frames[previousPage]->videoWidgets)) {
0411             vw->stop();
0412             vw->pageLeft();
0413         }
0414 
0415         // stop audio playback, if any
0416         Okular::AudioPlayer::instance()->stopPlaybacks();
0417 
0418         // perform the page closing action, if any
0419         if (m_document->page(previousPage)->pageAction(Okular::Page::Closing)) {
0420             m_document->processAction(m_document->page(previousPage)->pageAction(Okular::Page::Closing));
0421         }
0422 
0423         // perform the additional actions of the page's annotations, if any
0424         const QList<Okular::Annotation *> annotationsList = m_document->page(previousPage)->annotations();
0425         for (const Okular::Annotation *annotation : annotationsList) {
0426             Okular::Action *action = nullptr;
0427 
0428             if (annotation->subType() == Okular::Annotation::AScreen) {
0429                 action = static_cast<const Okular::ScreenAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageClosing);
0430             } else if (annotation->subType() == Okular::Annotation::AWidget) {
0431                 action = static_cast<const Okular::WidgetAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageClosing);
0432             }
0433 
0434             if (action) {
0435                 m_document->processAction(action);
0436             }
0437         }
0438     }
0439 
0440     if (currentPage != -1) {
0441         m_frameIndex = currentPage;
0442 
0443         // check if pixmap exists or else request it
0444         PresentationFrame *frame = m_frames[m_frameIndex];
0445         int pixW = frame->geometry.width();
0446         int pixH = frame->geometry.height();
0447 
0448         bool signalsBlocked = m_pagesEdit->signalsBlocked();
0449         m_pagesEdit->blockSignals(true);
0450         m_pagesEdit->setText(QString::number(m_frameIndex + 1));
0451         m_pagesEdit->blockSignals(signalsBlocked);
0452 
0453         // if pixmap not inside the Okular::Page we request it and wait for
0454         // notifyPixmapChanged call or else we can proceed to pixmap generation
0455         if (!frame->page->hasPixmap(this, ceil(pixW * devicePixelRatioF()), ceil(pixH * devicePixelRatioF()))) {
0456             requestPixmaps();
0457         } else {
0458             // make the background pixmap
0459             generatePage();
0460         }
0461 
0462         // perform the page opening action, if any
0463         if (m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening)) {
0464             m_document->processAction(m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening));
0465         }
0466 
0467         // perform the additional actions of the page's annotations, if any
0468         const QList<Okular::Annotation *> annotationsList = m_document->page(m_frameIndex)->annotations();
0469         for (const Okular::Annotation *annotation : annotationsList) {
0470             Okular::Action *action = nullptr;
0471 
0472             if (annotation->subType() == Okular::Annotation::AScreen) {
0473                 action = static_cast<const Okular::ScreenAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageOpening);
0474             } else if (annotation->subType() == Okular::Annotation::AWidget) {
0475                 action = static_cast<const Okular::WidgetAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageOpening);
0476             }
0477 
0478             if (action) {
0479                 m_document->processAction(action);
0480             }
0481         }
0482 
0483         // start autoplay video playback
0484         for (VideoWidget *vw : std::as_const(m_frames[m_frameIndex]->videoWidgets)) {
0485             vw->pageEntered();
0486         }
0487     }
0488 }
0489 
0490 bool PresentationWidget::canUnloadPixmap(int pageNumber) const
0491 {
0492     if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal) {
0493         // can unload all pixmaps except for the currently visible one
0494         return pageNumber != m_frameIndex;
0495     } else {
0496         // can unload all pixmaps except for the currently visible one, previous and next
0497         return qAbs(pageNumber - m_frameIndex) <= 1;
0498     }
0499 }
0500 
0501 void PresentationWidget::setupActions()
0502 {
0503     addAction(m_ac->action(QStringLiteral("first_page")));
0504     addAction(m_ac->action(QStringLiteral("last_page")));
0505     addAction(m_ac->action(KStandardAction::name(KStandardAction::Prior)));
0506     addAction(m_ac->action(KStandardAction::name(KStandardAction::Next)));
0507     addAction(m_ac->action(KStandardAction::name(KStandardAction::DocumentBack)));
0508     addAction(m_ac->action(KStandardAction::name(KStandardAction::DocumentForward)));
0509 
0510     QAction *action = m_ac->action(QStringLiteral("switch_blackscreen_mode"));
0511     connect(action, &QAction::toggled, this, &PresentationWidget::toggleBlackScreenMode);
0512     action->setEnabled(true);
0513     addAction(action);
0514 }
0515 
0516 void PresentationWidget::setPlayPauseIcon()
0517 {
0518     QAction *playPauseAction = m_ac->action(QStringLiteral("presentation_play_pause"));
0519     if (m_nextPageTimer->isActive()) {
0520         playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
0521         playPauseAction->setToolTip(i18nc("For Presentation", "Pause"));
0522     } else {
0523         playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
0524         playPauseAction->setToolTip(i18nc("For Presentation", "Play"));
0525     }
0526 }
0527 
0528 bool PresentationWidget::eventFilter(QObject *o, QEvent *e)
0529 {
0530     if (o == qApp) {
0531         if (e->type() == QTabletEvent::TabletEnterProximity) {
0532             setCursor(QCursor(Qt::CrossCursor));
0533         } else if (e->type() == QTabletEvent::TabletLeaveProximity) {
0534             setCursor(QCursor(Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ? Qt::BlankCursor : Qt::ArrowCursor));
0535             if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
0536                 // Trick KCursor to hide the cursor if needed by sending an "unknown" key press event
0537                 // Send also the key release to make the world happy even it's probably not needed
0538                 QKeyEvent kp(QEvent::KeyPress, 0, Qt::NoModifier);
0539                 qApp->sendEvent(this, &kp);
0540                 QKeyEvent kr(QEvent::KeyRelease, 0, Qt::NoModifier);
0541                 qApp->sendEvent(this, &kr);
0542             }
0543         }
0544     }
0545     return false;
0546 }
0547 
0548 // <widget events>
0549 bool PresentationWidget::event(QEvent *e)
0550 {
0551     if (e->type() == QEvent::Gesture) {
0552         return gestureEvent(static_cast<QGestureEvent *>(e));
0553     }
0554 
0555     if (e->type() == QEvent::ToolTip) {
0556         QHelpEvent *he = (QHelpEvent *)e;
0557 
0558         QRect r;
0559         const Okular::Action *link = getLink(he->pos(), &r);
0560 
0561         if (link) {
0562             QString tip = link->actionTip();
0563             if (!tip.isEmpty()) {
0564                 QToolTip::showText(he->globalPos(), tip, this, r);
0565             }
0566         }
0567         e->accept();
0568         return true;
0569     } else {
0570         // do not stop the event
0571         return QWidget::event(e);
0572     }
0573 }
0574 
0575 bool PresentationWidget::gestureEvent(QGestureEvent *event)
0576 {
0577     // Swiping left or right on a touch screen will go to the previous or next slide, respectively.
0578     // The precise gesture is the standard Qt swipe: with three(!) fingers.
0579     if (QGesture *swipe = event->gesture(Qt::SwipeGesture)) {
0580         QSwipeGesture *swipeEvent = static_cast<QSwipeGesture *>(swipe);
0581 
0582         if (swipeEvent->state() == Qt::GestureFinished) {
0583             if (swipeEvent->horizontalDirection() == QSwipeGesture::Left) {
0584                 slotPrevPage();
0585                 event->accept();
0586                 return true;
0587             }
0588             if (swipeEvent->horizontalDirection() == QSwipeGesture::Right) {
0589                 slotNextPage();
0590                 event->accept();
0591                 return true;
0592             }
0593         }
0594     }
0595 
0596     return false;
0597 }
0598 void PresentationWidget::keyPressEvent(QKeyEvent *e)
0599 {
0600     if (!m_isSetup) {
0601         return;
0602     }
0603 
0604     switch (e->key()) {
0605     case Qt::Key_Left:
0606     case Qt::Key_Backspace:
0607     case Qt::Key_PageUp:
0608     case Qt::Key_Up:
0609         slotPrevPage();
0610         break;
0611     case Qt::Key_Right:
0612     case Qt::Key_Space:
0613     case Qt::Key_PageDown:
0614     case Qt::Key_Down:
0615         slotNextPage();
0616         break;
0617     case Qt::Key_Home:
0618         slotFirstPage();
0619         break;
0620     case Qt::Key_End:
0621         slotLastPage();
0622         break;
0623     case Qt::Key_Escape:
0624         if (!m_topBar->isHidden()) {
0625             showTopBar(false);
0626         } else {
0627             close();
0628         }
0629         break;
0630     }
0631 }
0632 
0633 void PresentationWidget::wheelEvent(QWheelEvent *e)
0634 {
0635     if (!m_isSetup) {
0636         return;
0637     }
0638 
0639     // performance note: don't remove the clipping
0640     int div = e->angleDelta().y() / 120;
0641     if (div > 0) {
0642         if (div > 3) {
0643             div = 3;
0644         }
0645         while (div--) {
0646             slotPrevPage();
0647         }
0648     } else if (div < 0) {
0649         if (div < -3) {
0650             div = -3;
0651         }
0652         while (div++) {
0653             slotNextPage();
0654         }
0655     }
0656 }
0657 
0658 void PresentationWidget::mousePressEvent(QMouseEvent *e)
0659 {
0660     if (!m_isSetup) {
0661         return;
0662     }
0663 
0664     if (m_drawingEngine) {
0665         QRect r = routeMouseDrawingEvent(e);
0666         if (r.isValid()) {
0667             m_drawingRect |= r.translated(m_frames[m_frameIndex]->geometry.topLeft());
0668             update(m_drawingRect);
0669         }
0670         return;
0671     }
0672 
0673     // pressing left button
0674     if (e->button() == Qt::LeftButton) {
0675         // if pressing on a link, skip other checks
0676         if ((m_pressedLink = getLink(e->position()))) {
0677             return;
0678         }
0679 
0680         const Okular::Annotation *annotation = getAnnotation(e->position());
0681         if (annotation) {
0682             if (annotation->subType() == Okular::Annotation::AMovie) {
0683                 const Okular::MovieAnnotation *movieAnnotation = static_cast<const Okular::MovieAnnotation *>(annotation);
0684 
0685                 VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movieAnnotation->movie());
0686                 vw->show();
0687                 vw->play();
0688                 return;
0689             } else if (annotation->subType() == Okular::Annotation::ARichMedia) {
0690                 const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast<const Okular::RichMediaAnnotation *>(annotation);
0691 
0692                 VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(richMediaAnnotation->movie());
0693                 vw->show();
0694                 vw->play();
0695                 return;
0696             } else if (annotation->subType() == Okular::Annotation::AScreen) {
0697                 m_document->processAction(static_cast<const Okular::ScreenAnnotation *>(annotation)->action());
0698                 return;
0699             }
0700         }
0701 
0702         // handle clicking on top-right overlay
0703         if (!(Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) && m_overlayGeometry.contains(e->pos())) {
0704             overlayClick(e->pos());
0705             return;
0706         }
0707 
0708         // Actual mouse press events always lead to the next page page
0709         if (e->source() == Qt::MouseEventNotSynthesized) {
0710             m_goToNextPageOnRelease = true;
0711         }
0712         // Touch events may lead to the previous or next page
0713         else if (Okular::Settings::slidesTapNavigation() != Okular::Settings::EnumSlidesTapNavigation::Disabled) {
0714             switch (Okular::Settings::slidesTapNavigation()) {
0715             case Okular::Settings::EnumSlidesTapNavigation::ForwardBackward: {
0716                 if (e->position().x() < (qreal(geometry().width()) / 2)) {
0717                     m_goToPreviousPageOnRelease = true;
0718                 } else {
0719                     m_goToNextPageOnRelease = true;
0720                 }
0721                 break;
0722             }
0723             case Okular::Settings::EnumSlidesTapNavigation::Forward: {
0724                 m_goToNextPageOnRelease = true;
0725                 break;
0726             }
0727             case Okular::Settings::EnumSlidesTapNavigation::Disabled: {
0728                 // Do Nothing
0729             }
0730             }
0731         }
0732     }
0733     // pressing forward button
0734     else if (e->button() == Qt::ForwardButton) {
0735         m_goToNextPageOnRelease = true;
0736     }
0737     // pressing right or backward button
0738     else if (e->button() == Qt::RightButton || e->button() == Qt::BackButton) {
0739         m_goToPreviousPageOnRelease = true;
0740     }
0741 }
0742 
0743 void PresentationWidget::mouseReleaseEvent(QMouseEvent *e)
0744 {
0745     if (m_drawingEngine) {
0746         routeMouseDrawingEvent(e);
0747         return;
0748     }
0749 
0750     // if releasing on the same link we pressed over, execute it
0751     if (m_pressedLink && e->button() == Qt::LeftButton) {
0752         const Okular::Action *link = getLink(e->position());
0753         if (link == m_pressedLink) {
0754             m_document->processAction(link);
0755         }
0756         m_pressedLink = nullptr;
0757     }
0758 
0759     if (m_goToPreviousPageOnRelease) {
0760         slotPrevPage();
0761         m_goToPreviousPageOnRelease = false;
0762     }
0763 
0764     if (m_goToNextPageOnRelease) {
0765         slotNextPage();
0766         m_goToNextPageOnRelease = false;
0767     }
0768 }
0769 
0770 void PresentationWidget::mouseMoveEvent(QMouseEvent *e)
0771 {
0772     // safety check
0773     if (!m_isSetup) {
0774         return;
0775     }
0776 
0777     // update cursor and tooltip if hovering a link
0778     if (!m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden) {
0779         testCursorOnLink(e->position());
0780     }
0781 
0782     if (!m_topBar->isHidden()) {
0783         // hide a shown bar when exiting the area
0784         if (e->position().y() > (m_topBar->height() + 1)) {
0785             showTopBar(false);
0786             setFocus(Qt::OtherFocusReason);
0787         }
0788     } else {
0789         if (m_drawingEngine && e->buttons() != Qt::NoButton) {
0790             QRect r = routeMouseDrawingEvent(e);
0791             if (r.isValid()) {
0792                 m_drawingRect |= r.translated(m_frames[m_frameIndex]->geometry.topLeft());
0793                 update(m_drawingRect);
0794             }
0795         } else {
0796             // show the bar if reaching top 2 pixels
0797             if (e->position().y() <= 1) {
0798                 showTopBar(true);
0799             } else if ((QApplication::mouseButtons() & Qt::LeftButton) && m_overlayGeometry.contains(e->pos())) {
0800                 // handle "dragging the wheel" if clicking on its geometry
0801                 overlayClick(e->pos());
0802             }
0803         }
0804     }
0805 }
0806 
0807 void PresentationWidget::paintEvent(QPaintEvent *pe)
0808 {
0809     qreal dpr = devicePixelRatioF();
0810 
0811     if (m_inBlackScreenMode) {
0812         QPainter painter(this);
0813         painter.fillRect(pe->rect(), Qt::black);
0814         return;
0815     }
0816 
0817     if (!m_isSetup) {
0818         m_width = width();
0819         m_height = height();
0820 
0821         connect(m_document, &Okular::Document::linkFind, this, &PresentationWidget::slotFind);
0822 
0823         // register this observer in document. events will come immediately
0824         m_document->addObserver(this);
0825 
0826         // show summary if requested
0827         if (Okular::Settings::slidesShowSummary()) {
0828             generatePage();
0829         }
0830     }
0831 
0832     // check painting rect consistency
0833     QRect paintRect = pe->rect().intersected(QRect(QPoint(0, 0), geometry().size()));
0834     if (paintRect.isNull()) {
0835         return;
0836     }
0837 
0838     if (m_lastRenderedPixmap.isNull()) {
0839         QPainter painter(this);
0840         painter.fillRect(pe->rect(), Okular::Settings::slidesBackgroundColor());
0841         return;
0842     }
0843 
0844     // blit the pixmap to the screen
0845     QPainter painter(this);
0846     for (const QRect &r : pe->region()) {
0847         if (!r.isValid()) {
0848             continue;
0849         }
0850 #ifdef ENABLE_PROGRESS_OVERLAY
0851         const QRect dR(QRectF(r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr).toAlignedRect());
0852         if (Okular::Settings::slidesShowProgress() && r.intersects(m_overlayGeometry)) {
0853             // backbuffer the overlay operation
0854             QPixmap backPixmap(dR.size());
0855             backPixmap.setDevicePixelRatio(dpr);
0856             QPainter pixPainter(&backPixmap);
0857 
0858             // first draw the background on the backbuffer
0859             pixPainter.drawPixmap(QPoint(0, 0), m_lastRenderedPixmap, dR);
0860 
0861             // then blend the overlay (a piece of) over the background
0862             QRect ovr = m_overlayGeometry.intersected(r);
0863             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);
0864 
0865             // finally blit the pixmap to the screen
0866             pixPainter.end();
0867             const QRect backPixmapRect = backPixmap.rect();
0868             const QRect dBackPixmapRect(QRectF(backPixmapRect.x() * dpr, backPixmapRect.y() * dpr, backPixmapRect.width() * dpr, backPixmapRect.height() * dpr).toAlignedRect());
0869             painter.drawPixmap(r.topLeft(), backPixmap, dBackPixmapRect);
0870         } else {
0871 #endif
0872             // copy the rendered pixmap to the screen
0873             painter.drawPixmap(r.topLeft(), m_lastRenderedPixmap, dR);
0874         }
0875     }
0876 
0877     // paint drawings
0878     if (m_frameIndex != -1) {
0879         painter.save();
0880 
0881         const QRect &geom = m_frames[m_frameIndex]->geometry;
0882 
0883         const QSize pmSize(geom.width() * dpr, geom.height() * dpr);
0884         QPixmap pm(pmSize);
0885         pm.fill(Qt::transparent);
0886         QPainter pmPainter(&pm);
0887 
0888         pm.setDevicePixelRatio(dpr);
0889         pmPainter.setRenderHints(QPainter::Antialiasing);
0890 
0891         // Paint old paths
0892         for (const SmoothPath &drawing : m_frames[m_frameIndex]->drawings) {
0893             drawing.paint(&pmPainter, pmSize.width(), pmSize.height());
0894         }
0895 
0896         // Paint the path that is currently being drawn by the user
0897         if (m_drawingEngine && m_drawingRect.intersects(pe->rect())) {
0898             m_drawingEngine->paint(&pmPainter, pmSize.width(), pmSize.height(), m_drawingRect.intersected(pe->rect()));
0899         }
0900 
0901         painter.setRenderHints(QPainter::Antialiasing);
0902         painter.drawPixmap(geom.topLeft(), pm);
0903 
0904         painter.restore();
0905     }
0906     painter.end();
0907 }
0908 
0909 void PresentationWidget::resizeEvent(QResizeEvent *re)
0910 {
0911     m_width = width();
0912     m_height = height();
0913 
0914     // if by chance the new size equals the old, do not invalidate pixmaps and such..
0915     if (size() == re->oldSize()) {
0916         return;
0917     }
0918 
0919     // BEGIN Top toolbar
0920     // tool bar height in pixels, make it large enough to hold the text fields with the page numbers
0921     const int toolBarHeight = m_pagesEdit->height() * 1.5;
0922 
0923     m_topBar->setGeometry(0, 0, width(), toolBarHeight);
0924     m_topBar->setIconSize(QSize(toolBarHeight * 0.75, toolBarHeight * 0.75));
0925     // END Top toolbar
0926 
0927     // BEGIN Content area
0928     // update the frames
0929     const float screenRatio = (float)m_height / (float)m_width;
0930     for (PresentationFrame *frame : std::as_const(m_frames)) {
0931         frame->recalcGeometry(m_width, m_height, screenRatio);
0932     }
0933 
0934     if (m_frameIndex != -1) {
0935         // ugliness alarm!
0936         const_cast<Okular::Page *>(m_frames[m_frameIndex]->page)->deletePixmap(this);
0937         // force the regeneration of the pixmap
0938         m_lastRenderedPixmap = QPixmap();
0939         m_blockNotifications = true;
0940         requestPixmaps();
0941         m_blockNotifications = false;
0942     }
0943 
0944     if (m_transitionTimer->isActive()) {
0945         m_transitionTimer->stop();
0946     }
0947 
0948     generatePage(true /* no transitions */);
0949     // END Content area
0950 }
0951 
0952 void PresentationWidget::enterEvent(QEnterEvent *e)
0953 {
0954     if (!m_topBar->isHidden()) {
0955         // This can happen when we exited the widget via a "tooltip" and the tooltip disappears
0956         if (e->position().y() > (m_topBar->height() + 1)) {
0957             showTopBar(false);
0958         }
0959     }
0960     QWidget::enterEvent(e);
0961 }
0962 
0963 void PresentationWidget::leaveEvent(QEvent *e)
0964 {
0965     Q_UNUSED(e)
0966 
0967     if (!m_topBar->isHidden()) {
0968         if (QToolTip::isVisible()) {
0969             // make sure we're not hovering over the tooltip
0970             // because the world is sad, this works differently on Wayland and X11
0971             // on X11 the widget under the cursor is the tooltip window
0972             // on wayland it's the button generating the tooltip (why? no idea)
0973             const QWidget *widgetUnderCursor = qApp->widgetAt(QCursor::pos());
0974             if (widgetUnderCursor) {
0975                 const QWidget *widgetUnderCursorWindow = widgetUnderCursor->window();
0976                 if (widgetUnderCursorWindow == this) {
0977                     qDebug() << "Wayland";
0978                     return;
0979                 } else {
0980                     const QWidget *widgetUnderCursorParentWindow = widgetUnderCursorWindow->parentWidget() ? widgetUnderCursorWindow->parentWidget()->window() : nullptr;
0981                     if (widgetUnderCursorParentWindow == this) {
0982                         qDebug() << "X11";
0983                         return;
0984                     }
0985                 }
0986             }
0987         }
0988         showTopBar(false);
0989     }
0990 }
0991 // </widget events>
0992 
0993 const void *PresentationWidget::getObjectRect(Okular::ObjectRect::ObjectType type, QPointF point, QRect *geometry) const
0994 {
0995     // no links on invalid pages
0996     if (geometry && !geometry->isNull()) {
0997         geometry->setRect(0, 0, 0, 0);
0998     }
0999     if (m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size()) {
1000         return nullptr;
1001     }
1002 
1003     // get frame, page and geometry
1004     const PresentationFrame *frame = m_frames[m_frameIndex];
1005     const Okular::Page *page = frame->page;
1006     const QRect &frameGeometry = frame->geometry;
1007 
1008     // compute normalized x and y
1009     double nx = (double)(point.x() - frameGeometry.left()) / (double)frameGeometry.width();
1010     double ny = (double)(point.y() - frameGeometry.top()) / (double)frameGeometry.height();
1011 
1012     // no links outside the pages
1013     if (nx < 0 || nx > 1 || ny < 0 || ny > 1) {
1014         return nullptr;
1015     }
1016 
1017     // check if 1) there is an object and 2) it's a link
1018     const QRect screenRect = oldQt_screenOf(this)->geometry();
1019     const Okular::ObjectRect *object = page->objectRect(type, nx, ny, screenRect.width(), screenRect.height());
1020     if (!object) {
1021         return nullptr;
1022     }
1023 
1024     // compute link geometry if destination rect present
1025     if (geometry) {
1026         *geometry = object->boundingRect(frameGeometry.width(), frameGeometry.height());
1027         geometry->translate(frameGeometry.left(), frameGeometry.top());
1028     }
1029 
1030     // return the link pointer
1031     return object->object();
1032 }
1033 
1034 const Okular::Action *PresentationWidget::getLink(QPointF point, QRect *geometry) const
1035 {
1036     return reinterpret_cast<const Okular::Action *>(getObjectRect(Okular::ObjectRect::Action, point, geometry));
1037 }
1038 
1039 const Okular::Annotation *PresentationWidget::getAnnotation(QPointF point, QRect *geometry) const
1040 {
1041     return reinterpret_cast<const Okular::Annotation *>(getObjectRect(Okular::ObjectRect::OAnnotation, point, geometry));
1042 }
1043 
1044 void PresentationWidget::testCursorOnLink(QPointF point)
1045 {
1046     const Okular::Action *link = getLink(point, nullptr);
1047     const Okular::Annotation *annotation = getAnnotation(point, nullptr);
1048 
1049     const bool needsHandCursor = ((link != nullptr) || ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::AMovie)) || ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::ARichMedia)) ||
1050                                   ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::AScreen) && (GuiUtils::renditionMovieFromScreenAnnotation(static_cast<const Okular::ScreenAnnotation *>(annotation)) != nullptr)));
1051 
1052     // only react on changes (in/out from a link)
1053     if ((needsHandCursor && !m_handCursor) || (!needsHandCursor && m_handCursor)) {
1054         // change cursor shape
1055         m_handCursor = needsHandCursor;
1056         setCursor(QCursor(m_handCursor ? Qt::PointingHandCursor : Qt::ArrowCursor));
1057     }
1058 }
1059 
1060 void PresentationWidget::overlayClick(const QPoint position)
1061 {
1062     // clicking the progress indicator
1063     int xPos = position.x() - m_overlayGeometry.x() - m_overlayGeometry.width() / 2, yPos = m_overlayGeometry.height() / 2 - position.y();
1064     if (!xPos && !yPos) {
1065         return;
1066     }
1067 
1068     // compute angle relative to indicator (note coord transformation)
1069     float angle = 0.5 + 0.5 * atan2((double)-xPos, (double)-yPos) / M_PI;
1070     int pageIndex = (int)(angle * (m_frames.count() - 1) + 0.5);
1071 
1072     // go to selected page
1073     changePage(pageIndex);
1074 }
1075 
1076 void PresentationWidget::changePage(int newPage)
1077 {
1078     if (m_showSummaryView) {
1079         m_showSummaryView = false;
1080         m_frameIndex = -1;
1081         return;
1082     }
1083 
1084     if (m_frameIndex == newPage) {
1085         return;
1086     }
1087 
1088     // switch to newPage
1089     m_document->setViewportPage(newPage, this);
1090 
1091     if ((Okular::Settings::slidesShowSummary() && !m_showSummaryView) || m_frameIndex == -1) {
1092         notifyCurrentPageChanged(-1, newPage);
1093     }
1094 }
1095 
1096 void PresentationWidget::generatePage(bool disableTransition)
1097 {
1098     if (m_lastRenderedPixmap.isNull()) {
1099         qreal dpr = devicePixelRatioF();
1100         m_lastRenderedPixmap = QPixmap(m_width * dpr, m_height * dpr);
1101         m_lastRenderedPixmap.setDevicePixelRatio(dpr);
1102 
1103         m_previousPagePixmap = QPixmap();
1104     } else {
1105         m_previousPagePixmap = m_lastRenderedPixmap;
1106     }
1107 
1108     // opens the painter over the pixmap
1109     QPainter pixmapPainter;
1110     pixmapPainter.begin(&m_lastRenderedPixmap);
1111     // generate welcome page
1112     if (m_frameIndex == -1) {
1113         generateIntroPage(pixmapPainter);
1114     }
1115     // generate a normal pixmap with extended margin filling
1116     if (m_frameIndex >= 0 && m_frameIndex < (int)m_document->pages()) {
1117         generateContentsPage(m_frameIndex, pixmapPainter);
1118     }
1119     pixmapPainter.end();
1120 
1121     // generate the top-right corner overlay
1122 #ifdef ENABLE_PROGRESS_OVERLAY
1123     if (Okular::Settings::slidesShowProgress() && m_frameIndex != -1) {
1124         generateOverlay();
1125     }
1126 #endif
1127 
1128     // start transition on pages that have one
1129     disableTransition |= (Okular::Settings::slidesTransition() == Okular::Settings::EnumSlidesTransition::NoTransitions);
1130     if (!disableTransition) {
1131         const Okular::PageTransition *transition = m_frameIndex != -1 ? m_frames[m_frameIndex]->page->transition() : nullptr;
1132         if (transition) {
1133             initTransition(transition);
1134         } else {
1135             Okular::PageTransition trans = defaultTransition();
1136             initTransition(&trans);
1137         }
1138     } else {
1139         Okular::PageTransition trans = defaultTransition(Okular::Settings::EnumSlidesTransition::Replace);
1140         initTransition(&trans);
1141     }
1142 
1143     // update cursor + tooltip
1144     if (!m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden) {
1145         QPoint p = mapFromGlobal(QCursor::pos());
1146         testCursorOnLink(p);
1147     }
1148 }
1149 
1150 void PresentationWidget::generateIntroPage(QPainter &p)
1151 {
1152     qreal dpr = devicePixelRatioF();
1153 
1154     // use a vertical gray gradient background
1155     int blend1 = m_height / 10, blend2 = 9 * m_height / 10;
1156     int baseTint = QColor(Qt::gray).red();
1157     for (int i = 0; i < m_height; i++) {
1158         int k = baseTint;
1159         if (i < blend1) {
1160             k -= (int)(baseTint * (i - blend1) * (i - blend1) / (float)(blend1 * blend1));
1161         }
1162         if (i > blend2) {
1163             k += (int)((255 - baseTint) * (i - blend2) * (i - blend2) / (float)(blend1 * blend1));
1164         }
1165         p.fillRect(0, i, m_width, 1, QColor(k, k, k));
1166     }
1167 
1168     // draw okular logo in the four corners
1169     QPixmap logo = QIcon::fromTheme(QStringLiteral("okular")).pixmap(64 * dpr);
1170     logo.setDevicePixelRatio(dpr);
1171     if (!logo.isNull()) {
1172         p.drawPixmap(5, 5, logo);
1173         p.drawPixmap(m_width - 5 - logo.width(), 5, logo);
1174         p.drawPixmap(m_width - 5 - logo.width(), m_height - 5 - logo.height(), logo);
1175         p.drawPixmap(5, m_height - 5 - logo.height(), logo);
1176     }
1177 
1178     // draw metadata text (the last line is 'click to begin')
1179     int strNum = m_metaStrings.count(), strHeight = m_height / (strNum + 4), fontHeight = 2 * strHeight / 3;
1180     QFont font(p.font());
1181     font.setPixelSize(fontHeight);
1182     QFontMetrics metrics(font);
1183     for (int i = 0; i < strNum; i++) {
1184         // set a font to fit text width
1185         float wScale = (float)metrics.boundingRect(m_metaStrings[i]).width() / (float)m_width;
1186         QFont f(font);
1187         if (wScale > 1.0) {
1188             f.setPixelSize((int)((float)fontHeight / (float)wScale));
1189         }
1190         p.setFont(f);
1191 
1192         // text shadow
1193         p.setPen(Qt::darkGray);
1194         p.drawText(2, m_height / 4 + strHeight * i + 2, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i]);
1195         // text body
1196         p.setPen(128 + (127 * i) / strNum);
1197         p.drawText(0, m_height / 4 + strHeight * i, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i]);
1198     }
1199 }
1200 
1201 void PresentationWidget::generateContentsPage(int pageNum, QPainter &p)
1202 {
1203     PresentationFrame *frame = m_frames[pageNum];
1204 
1205     // translate painter and contents rect
1206     QRect geom(frame->geometry);
1207     p.translate(geom.left(), geom.top());
1208     geom.translate(-geom.left(), -geom.top());
1209 
1210     // draw the page using the shared PagePainter class
1211     int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations;
1212 
1213     PagePainter::paintPageOnPainter(&p, frame->page, this, flags, geom.width(), geom.height(), geom);
1214 
1215     // restore painter
1216     p.translate(-frame->geometry.left(), -frame->geometry.top());
1217 
1218     // fill unpainted areas with background color
1219     const QRegion unpainted(QRect(0, 0, m_width, m_height));
1220     const QRegion rgn = unpainted.subtracted(frame->geometry);
1221     for (const QRect &r : rgn) {
1222         p.fillRect(r, Okular::Settings::slidesBackgroundColor());
1223     }
1224 }
1225 
1226 // from Arthur - Qt4 - (is defined elsewhere as 'qt_div_255' to not break final compilation)
1227 inline int qt_div255(int x)
1228 {
1229     return (x + (x >> 8) + 0x80) >> 8;
1230 }
1231 void PresentationWidget::generateOverlay()
1232 {
1233 #ifdef ENABLE_PROGRESS_OVERLAY
1234     qreal dpr = devicePixelRatioF();
1235 
1236     // calculate overlay geometry and resize pixmap if needed
1237     double side = m_width / 16.0;
1238     m_overlayGeometry.setRect(m_width - side - 4, 4, side, side);
1239 
1240     // note: to get a sort of antialiasing, we render the pixmap double sized
1241     // and the resulting image is smoothly scaled down. So here we open a
1242     // painter on the double sized pixmap.
1243     side *= 2;
1244 
1245     QPixmap doublePixmap(side * dpr, side * dpr);
1246     doublePixmap.setDevicePixelRatio(dpr);
1247     doublePixmap.fill(Qt::black);
1248     QPainter pixmapPainter(&doublePixmap);
1249     pixmapPainter.setRenderHints(QPainter::Antialiasing);
1250 
1251     // draw PIE SLICES in blue levels (the levels will then be the alpha component)
1252     int pages = m_document->pages();
1253     if (pages > 28) { // draw continuous slices
1254         int degrees = (int)(360 * (float)(m_frameIndex + 1) / (float)pages);
1255         pixmapPainter.setPen(0x05);
1256         pixmapPainter.setBrush(QColor(0x40));
1257         pixmapPainter.drawPie(2, 2, side - 4, side - 4, 90 * 16, (360 - degrees) * 16);
1258         pixmapPainter.setPen(0x40);
1259         pixmapPainter.setBrush(QColor(0xF0));
1260         pixmapPainter.drawPie(2, 2, side - 4, side - 4, 90 * 16, -degrees * 16);
1261     } else { // draw discrete slices
1262         float oldCoord = -90;
1263         for (int i = 0; i < pages; i++) {
1264             float newCoord = -90 + 360 * (float)(i + 1) / (float)pages;
1265             pixmapPainter.setPen(i <= m_frameIndex ? 0x40 : 0x05);
1266             pixmapPainter.setBrush(QColor(i <= m_frameIndex ? 0xF0 : 0x40));
1267             pixmapPainter.drawPie(2, 2, side - 4, side - 4, (int)(-16 * (oldCoord + 1)), (int)(-16 * (newCoord - (oldCoord + 2))));
1268             oldCoord = newCoord;
1269         }
1270     }
1271     int circleOut = side / 4;
1272     pixmapPainter.setPen(Qt::black);
1273     pixmapPainter.setBrush(Qt::black);
1274     pixmapPainter.drawEllipse(circleOut, circleOut, side - 2 * circleOut, side - 2 * circleOut);
1275 
1276     // draw TEXT using maximum opacity
1277     QFont f(pixmapPainter.font());
1278     f.setPixelSize(side / 4);
1279     pixmapPainter.setFont(f);
1280     pixmapPainter.setPen(0xFF);
1281     // use a little offset to prettify output
1282     pixmapPainter.drawText(2, 2, side, side, Qt::AlignCenter, QString::number(m_frameIndex + 1));
1283 
1284     // end drawing pixmap and halve image
1285     pixmapPainter.end();
1286     QImage image(doublePixmap.toImage().scaled((side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
1287     image.setDevicePixelRatio(dpr);
1288     image = image.convertToFormat(QImage::Format_ARGB32);
1289     image.setDevicePixelRatio(dpr);
1290 
1291     // draw circular shadow using the same technique
1292     doublePixmap.fill(Qt::black);
1293     pixmapPainter.begin(&doublePixmap);
1294     pixmapPainter.setPen(0x40);
1295     pixmapPainter.setBrush(QColor(0x80));
1296     pixmapPainter.drawEllipse(0, 0, side, side);
1297     pixmapPainter.end();
1298     QImage shadow(doublePixmap.toImage().scaled((side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
1299     shadow.setDevicePixelRatio(dpr);
1300 
1301     // generate a 2 colors pixmap using mixing shadow (made with highlight color)
1302     // and image (made with highlightedText color)
1303     QPalette pal = palette();
1304     QColor color = pal.color(QPalette::Active, QPalette::HighlightedText);
1305     int red = color.red(), green = color.green(), blue = color.blue();
1306     color = pal.color(QPalette::Active, QPalette::Highlight);
1307     int sRed = color.red(), sGreen = color.green(), sBlue = color.blue();
1308     // pointers
1309     unsigned int *data = reinterpret_cast<unsigned int *>(image.bits()), *shadowData = reinterpret_cast<unsigned int *>(shadow.bits()), pixels = image.width() * image.height();
1310     // cache data (reduce computation time to 26%!)
1311     int c1 = -1, c2 = -1, cR = 0, cG = 0, cB = 0, cA = 0;
1312     // foreach pixel
1313     for (unsigned int i = 0; i < pixels; ++i) {
1314         // alpha for shadow and image
1315         int shadowAlpha = shadowData[i] & 0xFF, srcAlpha = data[i] & 0xFF;
1316         // cache values
1317         if (srcAlpha != c1 || shadowAlpha != c2) {
1318             c1 = srcAlpha;
1319             c2 = shadowAlpha;
1320             // fuse color components and alpha value of image over shadow
1321             data[i] = qRgba(cR = qt_div255(srcAlpha * red + (255 - srcAlpha) * sRed),
1322                             cG = qt_div255(srcAlpha * green + (255 - srcAlpha) * sGreen),
1323                             cB = qt_div255(srcAlpha * blue + (255 - srcAlpha) * sBlue),
1324                             cA = qt_div255(srcAlpha * srcAlpha + (255 - srcAlpha) * shadowAlpha));
1325         } else {
1326             data[i] = qRgba(cR, cG, cB, cA);
1327         }
1328     }
1329     m_lastRenderedOverlay = QPixmap::fromImage(image);
1330     m_lastRenderedOverlay.setDevicePixelRatio(dpr);
1331 
1332     // start the autohide timer
1333     // repaint( m_overlayGeometry ); // toggle with next line
1334     update(m_overlayGeometry);
1335     m_overlayHideTimer->start(2500);
1336 #endif
1337 }
1338 
1339 QRect PresentationWidget::routeMouseDrawingEvent(QMouseEvent *e)
1340 {
1341     if (m_frameIndex == -1) { // Can't draw on the summary page
1342         return QRect();
1343     }
1344 
1345     const QRect &geom = m_frames[m_frameIndex]->geometry;
1346     const Okular::Page *page = m_frames[m_frameIndex]->page;
1347 
1348     AnnotatorEngine::EventType eventType;
1349     AnnotatorEngine::Button button;
1350     AnnotatorEngine::Modifiers modifiers;
1351 
1352     // figure out the event type and button
1353     AnnotatorEngine::decodeEvent(e, &eventType, &button);
1354 
1355     static bool hasclicked = false;
1356     if (eventType == AnnotatorEngine::Press) {
1357         hasclicked = true;
1358     }
1359 
1360     QPointF mousePos = e->position();
1361     double nX = (mousePos.x() - (double)geom.left()) / (double)geom.width();
1362     double nY = (mousePos.y() - (double)geom.top()) / (double)geom.height();
1363     QRect ret;
1364     bool isInside = nX >= 0 && nX < 1 && nY >= 0 && nY < 1;
1365 
1366     if (hasclicked && !isInside) {
1367         // Fake a move to the last border pos
1368         nX = qBound(0., nX, 1.);
1369         nY = qBound(0., nY, 1.);
1370         m_drawingEngine->event(AnnotatorEngine::Move, button, modifiers, nX, nY, geom.width(), geom.height(), page);
1371 
1372         // Fake a release in the following lines
1373         eventType = AnnotatorEngine::Release;
1374         isInside = true;
1375     } else if (!hasclicked && isInside) {
1376         // we're coming from the outside, pretend we started clicking at the closest border
1377         if (nX < (1 - nX) && nX < nY && nX < (1 - nY)) {
1378             nX = 0;
1379         } else if (nY < (1 - nY) && nY < nX && nY < (1 - nX)) {
1380             nY = 0;
1381         } else if ((1 - nX) < nX && (1 - nX) < nY && (1 - nX) < (1 - nY)) {
1382             nX = 1;
1383         } else {
1384             nY = 1;
1385         }
1386 
1387         hasclicked = true;
1388         eventType = AnnotatorEngine::Press;
1389     }
1390 
1391     if (hasclicked && isInside) {
1392         ret = m_drawingEngine->event(eventType, button, modifiers, nX, nY, geom.width(), geom.height(), page);
1393     }
1394 
1395     if (eventType == AnnotatorEngine::Release) {
1396         hasclicked = false;
1397     }
1398 
1399     if (m_drawingEngine->creationCompleted()) {
1400         // add drawing to current page
1401         m_frames[m_frameIndex]->drawings.emplace_back(m_drawingEngine->endSmoothPath());
1402 
1403         // remove the actual drawer and create a new one just after
1404         // that - that gives continuous drawing
1405         delete m_drawingEngine;
1406         m_drawingRect = QRect();
1407         m_drawingEngine = new SmoothPathEngine(m_currentDrawingToolElement);
1408 
1409         // schedule repaint
1410         update();
1411     }
1412 
1413     return ret;
1414 }
1415 
1416 void PresentationWidget::startAutoChangeTimer()
1417 {
1418     double pageDuration = m_frameIndex >= 0 && m_frameIndex < (int)m_frames.count() ? m_frames[m_frameIndex]->page->duration() : -1;
1419     if (m_advanceSlides || pageDuration >= 0.0) {
1420         double secs;
1421         if (pageDuration < 0.0) {
1422             secs = Okular::SettingsCore::slidesAdvanceTime();
1423         } else if (m_advanceSlides) {
1424             secs = qMin<double>(pageDuration, Okular::SettingsCore::slidesAdvanceTime());
1425         } else {
1426             secs = pageDuration;
1427         }
1428 
1429         m_nextPageTimer->start((int)(secs * 1000));
1430     }
1431     setPlayPauseIcon();
1432 }
1433 
1434 QScreen *PresentationWidget::defaultScreen() const
1435 {
1436     const int preferenceScreen = Okular::Settings::slidesScreen();
1437 
1438     if (preferenceScreen == -2) {
1439         return oldQt_screenOf(m_parentWidget);
1440     } else if (preferenceScreen == -1) {
1441         return QApplication::primaryScreen();
1442     } else if (preferenceScreen >= 0 && preferenceScreen < QApplication::screens().count()) {
1443         return QApplication::screens().at(preferenceScreen);
1444     } else {
1445         return oldQt_screenOf(m_parentWidget);
1446     }
1447 }
1448 
1449 void PresentationWidget::requestPixmaps()
1450 {
1451     const qreal dpr = devicePixelRatioF();
1452     PresentationFrame *frame = m_frames[m_frameIndex];
1453     int pixW = frame->geometry.width();
1454     int pixH = frame->geometry.height();
1455 
1456     // operation will take long: set busy cursor
1457     QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
1458     // request the pixmap
1459     QList<Okular::PixmapRequest *> requests;
1460     requests.push_back(new Okular::PixmapRequest(this, m_frameIndex, pixW, pixH, dpr, PRESENTATION_PRIO, Okular::PixmapRequest::NoFeature));
1461     // restore cursor
1462     QApplication::restoreOverrideCursor();
1463     // ask for next and previous page if not in low memory usage setting
1464     if (Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low) {
1465         int pagesToPreload = 1;
1466 
1467         // If greedy, preload everything
1468         if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) {
1469             pagesToPreload = (int)m_document->pages();
1470         }
1471 
1472         Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload;
1473         requestFeatures |= Okular::PixmapRequest::Asynchronous;
1474 
1475         for (int j = 1; j <= pagesToPreload; j++) {
1476             int tailRequest = m_frameIndex + j;
1477             if (tailRequest < (int)m_document->pages()) {
1478                 PresentationFrame *nextFrame = m_frames[tailRequest];
1479                 pixW = nextFrame->geometry.width();
1480                 pixH = nextFrame->geometry.height();
1481                 if (!nextFrame->page->hasPixmap(this, pixW, pixH)) {
1482                     requests.push_back(new Okular::PixmapRequest(this, tailRequest, pixW, pixH, dpr, PRESENTATION_PRELOAD_PRIO, requestFeatures));
1483                 }
1484             }
1485 
1486             int headRequest = m_frameIndex - j;
1487             if (headRequest >= 0) {
1488                 PresentationFrame *prevFrame = m_frames[headRequest];
1489                 pixW = prevFrame->geometry.width();
1490                 pixH = prevFrame->geometry.height();
1491                 if (!prevFrame->page->hasPixmap(this, pixW, pixH)) {
1492                     requests.push_back(new Okular::PixmapRequest(this, headRequest, pixW, pixH, dpr, PRESENTATION_PRELOAD_PRIO, requestFeatures));
1493                 }
1494             }
1495 
1496             // stop if we've already reached both ends of the document
1497             if (headRequest < 0 && tailRequest >= (int)m_document->pages()) {
1498                 break;
1499             }
1500         }
1501     }
1502     m_document->requestPixmaps(requests);
1503 }
1504 
1505 void PresentationWidget::slotNextPage()
1506 {
1507     int nextIndex = m_frameIndex + 1;
1508 
1509     // loop when configured
1510     if (nextIndex == m_frames.count() && Okular::Settings::slidesLoop()) {
1511         nextIndex = 0;
1512     }
1513 
1514     if (nextIndex < m_frames.count()) {
1515         // go to next page
1516         changePage(nextIndex);
1517         // auto advance to the next page if set
1518         startAutoChangeTimer();
1519     } else {
1520 #ifdef ENABLE_PROGRESS_OVERLAY
1521         if (Okular::Settings::slidesShowProgress()) {
1522             generateOverlay();
1523         }
1524 #endif
1525         if (m_transitionTimer->isActive()) {
1526             m_transitionTimer->stop();
1527             m_lastRenderedPixmap = m_currentPagePixmap;
1528             update();
1529         }
1530     }
1531     // we need the setFocus() call here to let KCursor::autoHide() work correctly
1532     setFocus();
1533 }
1534 
1535 void PresentationWidget::slotPrevPage()
1536 {
1537     if (m_frameIndex > 0) {
1538         // go to previous page
1539         changePage(m_frameIndex - 1);
1540 
1541         // auto advance to the next page if set
1542         startAutoChangeTimer();
1543     } else {
1544 #ifdef ENABLE_PROGRESS_OVERLAY
1545         if (Okular::Settings::slidesShowProgress()) {
1546             generateOverlay();
1547         }
1548 #endif
1549         if (m_transitionTimer->isActive()) {
1550             m_transitionTimer->stop();
1551             m_lastRenderedPixmap = m_currentPagePixmap;
1552             update();
1553         }
1554     }
1555 }
1556 
1557 void PresentationWidget::slotFirstPage()
1558 {
1559     changePage(0);
1560 }
1561 
1562 void PresentationWidget::slotLastPage()
1563 {
1564     changePage((int)m_frames.count() - 1);
1565 }
1566 
1567 void PresentationWidget::slotHideOverlay()
1568 {
1569     QRect geom(m_overlayGeometry);
1570     m_overlayGeometry.setCoords(0, 0, -1, -1);
1571     update(geom);
1572 }
1573 
1574 void PresentationWidget::slotTransitionStep()
1575 {
1576     switch (m_currentTransition.type()) {
1577     case Okular::PageTransition::Fade: {
1578         QPainter pixmapPainter;
1579         m_currentPixmapOpacity += 1.0 / m_transitionSteps;
1580         m_lastRenderedPixmap = QPixmap(m_lastRenderedPixmap.size());
1581         m_lastRenderedPixmap.setDevicePixelRatio(devicePixelRatioF());
1582         m_lastRenderedPixmap.fill(Qt::transparent);
1583         pixmapPainter.begin(&m_lastRenderedPixmap);
1584         pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
1585         pixmapPainter.setOpacity(1 - m_currentPixmapOpacity);
1586         pixmapPainter.drawPixmap(0, 0, m_previousPagePixmap);
1587         pixmapPainter.setOpacity(m_currentPixmapOpacity);
1588         pixmapPainter.drawPixmap(0, 0, m_currentPagePixmap);
1589         update();
1590         if (m_currentPixmapOpacity >= 1) {
1591             return;
1592         }
1593     } break;
1594     default: {
1595         if (m_transitionRects.empty()) {
1596             // it's better to fix the transition to cover the whole screen than
1597             // enabling the following line that wastes cpu for nothing
1598             // update();
1599             return;
1600         }
1601 
1602         for (int i = 0; i < m_transitionMul && !m_transitionRects.empty(); i++) {
1603             update(m_transitionRects.first());
1604             m_transitionRects.pop_front();
1605         }
1606     } break;
1607     }
1608     m_transitionTimer->start(m_transitionDelay);
1609 }
1610 
1611 void PresentationWidget::slotDelayedEvents()
1612 {
1613     setScreen(defaultScreen());
1614     show();
1615 
1616     if (m_screenSelect) {
1617         m_screenSelect->setCurrentItem(QApplication::screens().indexOf(oldQt_screenOf(this)));
1618         connect(m_screenSelect->selectableActionGroup(), &QActionGroup::triggered, this, &PresentationWidget::chooseScreen);
1619     }
1620 
1621     // inform user on how to exit from presentation mode
1622     KMessageBox::information(
1623         this,
1624         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)"),
1625         QString(),
1626         QStringLiteral("presentationInfo"));
1627 }
1628 
1629 void PresentationWidget::slotPageChanged()
1630 {
1631     bool ok = true;
1632     int p = m_pagesEdit->text().toInt(&ok);
1633     if (!ok) {
1634         return;
1635     }
1636 
1637     changePage(p - 1);
1638 }
1639 
1640 void PresentationWidget::slotChangeDrawingToolEngine(const QDomElement &element)
1641 {
1642     if (element.isNull()) {
1643         delete m_drawingEngine;
1644         m_drawingEngine = nullptr;
1645         m_drawingRect = QRect();
1646         setCursor(Qt::ArrowCursor);
1647     } else {
1648         m_drawingEngine = new SmoothPathEngine(element);
1649         setCursor(QCursor(QPixmap(QStringLiteral("pencil")), Qt::ArrowCursor));
1650         m_currentDrawingToolElement = element;
1651     }
1652 }
1653 
1654 void PresentationWidget::slotAddDrawingToolActions()
1655 {
1656     DrawingToolActions *drawingToolActions = qobject_cast<DrawingToolActions *>(sender());
1657 
1658     const QList<QAction *> actionsList = drawingToolActions->actions();
1659     for (QAction *action : actionsList) {
1660         action->setEnabled(true);
1661         m_topBar->addAction(action);
1662         addAction(action);
1663     }
1664 }
1665 
1666 void PresentationWidget::clearDrawings()
1667 {
1668     if (m_frameIndex != -1) {
1669         m_frames[m_frameIndex]->drawings.clear();
1670     }
1671     update();
1672 }
1673 
1674 void PresentationWidget::chooseScreen(QAction *act)
1675 {
1676     if (!act || act->data().metaType().id() != QMetaType::Int) {
1677         return;
1678     }
1679 
1680     const int newScreen = act->data().toInt();
1681     if (newScreen < QApplication::screens().count()) {
1682         setScreen(QApplication::screens().at(newScreen));
1683     }
1684 }
1685 
1686 void PresentationWidget::toggleBlackScreenMode(bool)
1687 {
1688     m_inBlackScreenMode = !m_inBlackScreenMode;
1689 
1690     update();
1691 }
1692 
1693 void PresentationWidget::setScreen(const QScreen *newScreen)
1694 {
1695     // To move to a new screen, need to disable fullscreen first:
1696     if (newScreen != screen()) {
1697         setWindowState(windowState() & ~Qt::WindowFullScreen);
1698     }
1699     setGeometry(newScreen->geometry());
1700     setWindowState(windowState() | Qt::WindowFullScreen);
1701 }
1702 
1703 void PresentationWidget::inhibitPowerManagement()
1704 {
1705 #if HAVE_DBUS
1706 #ifdef Q_OS_LINUX
1707     QString reason = i18nc("Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a presentation");
1708 
1709     if (!m_screenInhibitCookie) {
1710         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("Inhibit"));
1711         message << QCoreApplication::applicationName();
1712         message << reason;
1713 
1714         QDBusPendingReply<uint> reply = QDBusConnection::sessionBus().asyncCall(message);
1715         reply.waitForFinished();
1716         if (reply.isValid()) {
1717             m_screenInhibitCookie = reply.value();
1718             qCDebug(OkularUiDebug) << "Screen inhibition cookie" << m_screenInhibitCookie;
1719         } else {
1720             qCWarning(OkularUiDebug) << "Unable to inhibit screensaver" << reply.error();
1721         }
1722     }
1723 
1724     if (m_sleepInhibitFd != -1) {
1725         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("Inhibit"));
1726         message << QStringLiteral("sleep");
1727         message << QCoreApplication::applicationName();
1728         message << reason;
1729         message << QStringLiteral("block");
1730 
1731         QDBusPendingReply<QDBusUnixFileDescriptor> reply = QDBusConnection::systemBus().asyncCall(message);
1732         reply.waitForFinished();
1733         if (reply.isValid()) {
1734             m_sleepInhibitFd = dup(reply.value().fileDescriptor());
1735         } else {
1736             qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error();
1737         }
1738     }
1739 #endif // Q_OS_LINUX
1740 #endif // HAVE_DBUS
1741 }
1742 
1743 void PresentationWidget::allowPowerManagement()
1744 {
1745 #if HAVE_DBUS
1746 #ifdef Q_OS_LINUX
1747     if (m_sleepInhibitFd != -1) {
1748         ::close(m_sleepInhibitFd);
1749         m_sleepInhibitFd = -1;
1750     }
1751 
1752     if (m_screenInhibitCookie) {
1753         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("UnInhibit"));
1754         message << m_screenInhibitCookie;
1755 
1756         QDBusPendingReply<uint> reply = QDBusConnection::sessionBus().asyncCall(message);
1757         reply.waitForFinished();
1758 
1759         m_screenInhibitCookie = 0;
1760     }
1761 #endif // Q_OS_LINUX
1762 #endif // HAVE_DBUS
1763 }
1764 
1765 void PresentationWidget::showTopBar(bool show)
1766 {
1767     if (show) {
1768         m_topBar->show();
1769 
1770         // Don't autohide the mouse cursor if it's over the toolbar
1771         if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
1772             KCursor::setAutoHideCursor(this, false);
1773         }
1774 
1775         // Always show a cursor when topBar is visible
1776         if (!m_drawingEngine) {
1777             setCursor(QCursor(Qt::ArrowCursor));
1778         }
1779     } else {
1780         m_topBar->hide();
1781 
1782         // Reenable autohide if need be when leaving the toolbar
1783         if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
1784             KCursor::setAutoHideCursor(this, true);
1785         }
1786 
1787         // Or hide the cursor again if hidden cursor is enabled
1788         else if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) {
1789             // Don't hide the cursor if drawing mode is on
1790             if (!m_drawingEngine) {
1791                 setCursor(QCursor(Qt::BlankCursor));
1792             }
1793         }
1794     }
1795 
1796     // Make sure mouse tracking isn't off after the KCursor::setAutoHideCursor() calls
1797     setMouseTracking(true);
1798 }
1799 
1800 void PresentationWidget::slotFind()
1801 {
1802     if (!m_searchBar) {
1803         m_searchBar = new PresentationSearchBar(m_document, this, this);
1804         m_searchBar->forceSnap();
1805     }
1806     m_searchBar->focusOnSearchEdit();
1807     m_searchBar->show();
1808 }
1809 
1810 const Okular::PageTransition PresentationWidget::defaultTransition() const
1811 {
1812     return defaultTransition(Okular::Settings::slidesTransition());
1813 }
1814 
1815 const Okular::PageTransition PresentationWidget::defaultTransition(int type) const
1816 {
1817     switch (type) {
1818     case Okular::Settings::EnumSlidesTransition::BlindsHorizontal: {
1819         Okular::PageTransition transition(Okular::PageTransition::Blinds);
1820         transition.setAlignment(Okular::PageTransition::Horizontal);
1821         return transition;
1822     }
1823     case Okular::Settings::EnumSlidesTransition::BlindsVertical: {
1824         Okular::PageTransition transition(Okular::PageTransition::Blinds);
1825         transition.setAlignment(Okular::PageTransition::Vertical);
1826         return transition;
1827     }
1828     case Okular::Settings::EnumSlidesTransition::BoxIn: {
1829         Okular::PageTransition transition(Okular::PageTransition::Box);
1830         transition.setDirection(Okular::PageTransition::Inward);
1831         return transition;
1832     }
1833     case Okular::Settings::EnumSlidesTransition::BoxOut: {
1834         Okular::PageTransition transition(Okular::PageTransition::Box);
1835         transition.setDirection(Okular::PageTransition::Outward);
1836         return transition;
1837     }
1838     case Okular::Settings::EnumSlidesTransition::Dissolve: {
1839         return Okular::PageTransition(Okular::PageTransition::Dissolve);
1840     }
1841     case Okular::Settings::EnumSlidesTransition::GlitterDown: {
1842         Okular::PageTransition transition(Okular::PageTransition::Glitter);
1843         transition.setAngle(270);
1844         return transition;
1845     }
1846     case Okular::Settings::EnumSlidesTransition::GlitterRight: {
1847         Okular::PageTransition transition(Okular::PageTransition::Glitter);
1848         transition.setAngle(0);
1849         return transition;
1850     }
1851     case Okular::Settings::EnumSlidesTransition::GlitterRightDown: {
1852         Okular::PageTransition transition(Okular::PageTransition::Glitter);
1853         transition.setAngle(315);
1854         return transition;
1855     }
1856     case Okular::Settings::EnumSlidesTransition::Random: {
1857         return defaultTransition(QRandomGenerator::global()->bounded(18));
1858     }
1859     case Okular::Settings::EnumSlidesTransition::SplitHorizontalIn: {
1860         Okular::PageTransition transition(Okular::PageTransition::Split);
1861         transition.setAlignment(Okular::PageTransition::Horizontal);
1862         transition.setDirection(Okular::PageTransition::Inward);
1863         return transition;
1864     }
1865     case Okular::Settings::EnumSlidesTransition::SplitHorizontalOut: {
1866         Okular::PageTransition transition(Okular::PageTransition::Split);
1867         transition.setAlignment(Okular::PageTransition::Horizontal);
1868         transition.setDirection(Okular::PageTransition::Outward);
1869         return transition;
1870     }
1871     case Okular::Settings::EnumSlidesTransition::SplitVerticalIn: {
1872         Okular::PageTransition transition(Okular::PageTransition::Split);
1873         transition.setAlignment(Okular::PageTransition::Vertical);
1874         transition.setDirection(Okular::PageTransition::Inward);
1875         return transition;
1876     }
1877     case Okular::Settings::EnumSlidesTransition::SplitVerticalOut: {
1878         Okular::PageTransition transition(Okular::PageTransition::Split);
1879         transition.setAlignment(Okular::PageTransition::Vertical);
1880         transition.setDirection(Okular::PageTransition::Outward);
1881         return transition;
1882     }
1883     case Okular::Settings::EnumSlidesTransition::WipeDown: {
1884         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1885         transition.setAngle(270);
1886         return transition;
1887     }
1888     case Okular::Settings::EnumSlidesTransition::WipeRight: {
1889         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1890         transition.setAngle(0);
1891         return transition;
1892     }
1893     case Okular::Settings::EnumSlidesTransition::WipeLeft: {
1894         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1895         transition.setAngle(180);
1896         return transition;
1897     }
1898     case Okular::Settings::EnumSlidesTransition::WipeUp: {
1899         Okular::PageTransition transition(Okular::PageTransition::Wipe);
1900         transition.setAngle(90);
1901         return transition;
1902     }
1903     case Okular::Settings::EnumSlidesTransition::Fade: {
1904         return Okular::PageTransition(Okular::PageTransition::Fade);
1905     }
1906     case Okular::Settings::EnumSlidesTransition::NoTransitions:
1907     case Okular::Settings::EnumSlidesTransition::Replace:
1908     default:
1909         return Okular::PageTransition(Okular::PageTransition::Replace);
1910     }
1911     // should not happen, just make gcc happy
1912     return Okular::PageTransition();
1913 }
1914 
1915 /** ONLY the TRANSITIONS GENERATION function from here on **/
1916 void PresentationWidget::initTransition(const Okular::PageTransition *transition)
1917 {
1918     // if it's just a 'replace' transition, repaint the screen
1919     if (transition->type() == Okular::PageTransition::Replace) {
1920         update();
1921         return;
1922     }
1923 
1924     const bool isInward = transition->direction() == Okular::PageTransition::Inward;
1925     const bool isHorizontal = transition->alignment() == Okular::PageTransition::Horizontal;
1926     const float totalTime = transition->duration();
1927 
1928     m_transitionRects.clear();
1929     m_currentTransition = *transition;
1930     m_currentPagePixmap = m_lastRenderedPixmap;
1931 
1932     switch (transition->type()) {
1933         // split: horizontal / vertical and inward / outward
1934     case Okular::PageTransition::Split: {
1935         const int steps = isHorizontal ? 100 : 75;
1936         if (isHorizontal) {
1937             if (isInward) {
1938                 int xPosition = 0;
1939                 for (int i = 0; i < steps; i++) {
1940                     int xNext = ((i + 1) * m_width) / (2 * steps);
1941                     m_transitionRects.push_back(QRect(xPosition, 0, xNext - xPosition, m_height));
1942                     m_transitionRects.push_back(QRect(m_width - xNext, 0, xNext - xPosition, m_height));
1943                     xPosition = xNext;
1944                 }
1945             } else {
1946                 int xPosition = m_width / 2;
1947                 for (int i = 0; i < steps; i++) {
1948                     int xNext = ((steps - (i + 1)) * m_width) / (2 * steps);
1949                     m_transitionRects.push_back(QRect(xNext, 0, xPosition - xNext, m_height));
1950                     m_transitionRects.push_back(QRect(m_width - xPosition, 0, xPosition - xNext, m_height));
1951                     xPosition = xNext;
1952                 }
1953             }
1954         } else {
1955             if (isInward) {
1956                 int yPosition = 0;
1957                 for (int i = 0; i < steps; i++) {
1958                     int yNext = ((i + 1) * m_height) / (2 * steps);
1959                     m_transitionRects.push_back(QRect(0, yPosition, m_width, yNext - yPosition));
1960                     m_transitionRects.push_back(QRect(0, m_height - yNext, m_width, yNext - yPosition));
1961                     yPosition = yNext;
1962                 }
1963             } else {
1964                 int yPosition = m_height / 2;
1965                 for (int i = 0; i < steps; i++) {
1966                     int yNext = ((steps - (i + 1)) * m_height) / (2 * steps);
1967                     m_transitionRects.push_back(QRect(0, yNext, m_width, yPosition - yNext));
1968                     m_transitionRects.push_back(QRect(0, m_height - yPosition, m_width, yPosition - yNext));
1969                     yPosition = yNext;
1970                 }
1971             }
1972         }
1973         m_transitionMul = 2;
1974         m_transitionDelay = (int)((totalTime * 1000) / steps);
1975     } break;
1976 
1977         // blinds: horizontal(l-to-r) / vertical(t-to-b)
1978     case Okular::PageTransition::Blinds: {
1979         const int blinds = isHorizontal ? 8 : 6;
1980         const int steps = m_width / (4 * blinds);
1981         if (isHorizontal) {
1982             int xPosition[8];
1983             for (int b = 0; b < blinds; b++) {
1984                 xPosition[b] = (b * m_width) / blinds;
1985             }
1986 
1987             for (int i = 0; i < steps; i++) {
1988                 int stepOffset = (int)(((float)i * (float)m_width) / ((float)blinds * (float)steps));
1989                 for (int b = 0; b < blinds; b++) {
1990                     m_transitionRects.push_back(QRect(xPosition[b], 0, stepOffset, m_height));
1991                     xPosition[b] = stepOffset + (b * m_width) / blinds;
1992                 }
1993             }
1994         } else {
1995             int yPosition[6];
1996             for (int b = 0; b < blinds; b++) {
1997                 yPosition[b] = (b * m_height) / blinds;
1998             }
1999 
2000             for (int i = 0; i < steps; i++) {
2001                 int stepOffset = (int)(((float)i * (float)m_height) / ((float)blinds * (float)steps));
2002                 for (int b = 0; b < blinds; b++) {
2003                     m_transitionRects.push_back(QRect(0, yPosition[b], m_width, stepOffset));
2004                     yPosition[b] = stepOffset + (b * m_height) / blinds;
2005                 }
2006             }
2007         }
2008         m_transitionMul = blinds;
2009         m_transitionDelay = (int)((totalTime * 1000) / steps);
2010     } break;
2011 
2012         // box: inward / outward
2013     case Okular::PageTransition::Box: {
2014         const int steps = m_width / 10;
2015         if (isInward) {
2016             int L = 0, T = 0, R = m_width, B = m_height;
2017             for (int i = 0; i < steps; i++) {
2018                 // compute shrunk box coords
2019                 int newL = ((i + 1) * m_width) / (2 * steps);
2020                 int newT = ((i + 1) * m_height) / (2 * steps);
2021                 int newR = m_width - newL;
2022                 int newB = m_height - newT;
2023                 // add left, right, topcenter, bottomcenter rects
2024                 m_transitionRects.push_back(QRect(L, T, newL - L, B - T));
2025                 m_transitionRects.push_back(QRect(newR, T, R - newR, B - T));
2026                 m_transitionRects.push_back(QRect(newL, T, newR - newL, newT - T));
2027                 m_transitionRects.push_back(QRect(newL, newB, newR - newL, B - newB));
2028                 L = newL;
2029                 T = newT;
2030                 R = newR, B = newB;
2031             }
2032         } else {
2033             int L = m_width / 2, T = m_height / 2, R = L, B = T;
2034             for (int i = 0; i < steps; i++) {
2035                 // compute shrunk box coords
2036                 int newL = ((steps - (i + 1)) * m_width) / (2 * steps);
2037                 int newT = ((steps - (i + 1)) * m_height) / (2 * steps);
2038                 int newR = m_width - newL;
2039                 int newB = m_height - newT;
2040                 // add left, right, topcenter, bottomcenter rects
2041                 m_transitionRects.push_back(QRect(newL, newT, L - newL, newB - newT));
2042                 m_transitionRects.push_back(QRect(R, newT, newR - R, newB - newT));
2043                 m_transitionRects.push_back(QRect(L, newT, R - L, T - newT));
2044                 m_transitionRects.push_back(QRect(L, B, R - L, newB - B));
2045                 L = newL;
2046                 T = newT;
2047                 R = newR, B = newB;
2048             }
2049         }
2050         m_transitionMul = 4;
2051         m_transitionDelay = (int)((totalTime * 1000) / steps);
2052     } break;
2053 
2054         // wipe: implemented for 4 canonical angles
2055     case Okular::PageTransition::Wipe: {
2056         const int angle = transition->angle();
2057         const int steps = (angle == 0) || (angle == 180) ? m_width / 8 : m_height / 8;
2058         if (angle == 0) {
2059             int xPosition = 0;
2060             for (int i = 0; i < steps; i++) {
2061                 int xNext = ((i + 1) * m_width) / steps;
2062                 m_transitionRects.push_back(QRect(xPosition, 0, xNext - xPosition, m_height));
2063                 xPosition = xNext;
2064             }
2065         } else if (angle == 90) {
2066             int yPosition = m_height;
2067             for (int i = 0; i < steps; i++) {
2068                 int yNext = ((steps - (i + 1)) * m_height) / steps;
2069                 m_transitionRects.push_back(QRect(0, yNext, m_width, yPosition - yNext));
2070                 yPosition = yNext;
2071             }
2072         } else if (angle == 180) {
2073             int xPosition = m_width;
2074             for (int i = 0; i < steps; i++) {
2075                 int xNext = ((steps - (i + 1)) * m_width) / steps;
2076                 m_transitionRects.push_back(QRect(xNext, 0, xPosition - xNext, m_height));
2077                 xPosition = xNext;
2078             }
2079         } else if (angle == 270) {
2080             int yPosition = 0;
2081             for (int i = 0; i < steps; i++) {
2082                 int yNext = ((i + 1) * m_height) / steps;
2083                 m_transitionRects.push_back(QRect(0, yPosition, m_width, yNext - yPosition));
2084                 yPosition = yNext;
2085             }
2086         } else {
2087             update();
2088             return;
2089         }
2090         m_transitionMul = 1;
2091         m_transitionDelay = (int)((totalTime * 1000) / steps);
2092     } break;
2093 
2094         // dissolve: replace 'random' rects
2095     case Okular::PageTransition::Dissolve: {
2096         const int gridXsteps = 50;
2097         const int gridYsteps = 38;
2098         const int steps = gridXsteps * gridYsteps;
2099         int oldX = 0;
2100         int oldY = 0;
2101         // create a grid of gridXstep by gridYstep QRects
2102         for (int y = 0; y < gridYsteps; y++) {
2103             int newY = (int)(m_height * ((float)(y + 1) / (float)gridYsteps));
2104             for (int x = 0; x < gridXsteps; x++) {
2105                 int newX = (int)(m_width * ((float)(x + 1) / (float)gridXsteps));
2106                 m_transitionRects.push_back(QRect(oldX, oldY, newX - oldX, newY - oldY));
2107                 oldX = newX;
2108             }
2109             oldX = 0;
2110             oldY = newY;
2111         }
2112         // randomize the grid
2113         for (int i = 0; i < steps; i++) {
2114 #ifndef Q_OS_WIN
2115             int n1 = (int)(steps * drand48());
2116             int n2 = (int)(steps * drand48());
2117 #else
2118             int n1 = (int)(steps * (std::rand() / RAND_MAX));
2119             int n2 = (int)(steps * (std::rand() / RAND_MAX));
2120 #endif
2121             // swap items if index differs
2122             if (n1 != n2) {
2123                 QRect r = m_transitionRects[n2];
2124                 m_transitionRects[n2] = m_transitionRects[n1];
2125                 m_transitionRects[n1] = r;
2126             }
2127         }
2128         // set global transition parameters
2129         m_transitionMul = 40;
2130         m_transitionDelay = (int)((m_transitionMul * 1000 * totalTime) / steps);
2131     } break;
2132 
2133         // glitter: similar to dissolve but has a direction
2134     case Okular::PageTransition::Glitter: {
2135         const int gridXsteps = 50;
2136         const int gridYsteps = 38;
2137         const int steps = gridXsteps * gridYsteps;
2138         const int angle = transition->angle();
2139         // generate boxes using a given direction
2140         if (angle == 90) {
2141             int yPosition = m_height;
2142             for (int i = 0; i < gridYsteps; i++) {
2143                 int yNext = ((gridYsteps - (i + 1)) * m_height) / gridYsteps;
2144                 int xPosition = 0;
2145                 for (int j = 0; j < gridXsteps; j++) {
2146                     int xNext = ((j + 1) * m_width) / gridXsteps;
2147                     m_transitionRects.push_back(QRect(xPosition, yNext, xNext - xPosition, yPosition - yNext));
2148                     xPosition = xNext;
2149                 }
2150                 yPosition = yNext;
2151             }
2152         } else if (angle == 180) {
2153             int xPosition = m_width;
2154             for (int i = 0; i < gridXsteps; i++) {
2155                 int xNext = ((gridXsteps - (i + 1)) * m_width) / gridXsteps;
2156                 int yPosition = 0;
2157                 for (int j = 0; j < gridYsteps; j++) {
2158                     int yNext = ((j + 1) * m_height) / gridYsteps;
2159                     m_transitionRects.push_back(QRect(xNext, yPosition, xPosition - xNext, yNext - yPosition));
2160                     yPosition = yNext;
2161                 }
2162                 xPosition = xNext;
2163             }
2164         } else if (angle == 270) {
2165             int yPosition = 0;
2166             for (int i = 0; i < gridYsteps; i++) {
2167                 int yNext = ((i + 1) * m_height) / gridYsteps;
2168                 int xPosition = 0;
2169                 for (int j = 0; j < gridXsteps; j++) {
2170                     int xNext = ((j + 1) * m_width) / gridXsteps;
2171                     m_transitionRects.push_back(QRect(xPosition, yPosition, xNext - xPosition, yNext - yPosition));
2172                     xPosition = xNext;
2173                 }
2174                 yPosition = yNext;
2175             }
2176         } else // if angle is 0 or 315
2177         {
2178             int xPosition = 0;
2179             for (int i = 0; i < gridXsteps; i++) {
2180                 int xNext = ((i + 1) * m_width) / gridXsteps;
2181                 int yPosition = 0;
2182                 for (int j = 0; j < gridYsteps; j++) {
2183                     int yNext = ((j + 1) * m_height) / gridYsteps;
2184                     m_transitionRects.push_back(QRect(xPosition, yPosition, xNext - xPosition, yNext - yPosition));
2185                     yPosition = yNext;
2186                 }
2187                 xPosition = xNext;
2188             }
2189         }
2190         // add a 'glitter' (1 over 10 pieces is randomized)
2191         int randomSteps = steps / 20;
2192         for (int i = 0; i < randomSteps; i++) {
2193 #ifndef Q_OS_WIN
2194             int n1 = (int)(steps * drand48());
2195             int n2 = (int)(steps * drand48());
2196 #else
2197             int n1 = (int)(steps * (std::rand() / RAND_MAX));
2198             int n2 = (int)(steps * (std::rand() / RAND_MAX));
2199 #endif
2200             // swap items if index differs
2201             if (n1 != n2) {
2202                 QRect r = m_transitionRects[n2];
2203                 m_transitionRects[n2] = m_transitionRects[n1];
2204                 m_transitionRects[n1] = r;
2205             }
2206         }
2207         // set global transition parameters
2208         m_transitionMul = (angle == 90) || (angle == 270) ? gridYsteps : gridXsteps;
2209         m_transitionMul /= 2;
2210         m_transitionDelay = (int)((m_transitionMul * 1000 * totalTime) / steps);
2211     } break;
2212 
2213     case Okular::PageTransition::Fade: {
2214         enum { FADE_TRANSITION_FPS = 20 };
2215         const int steps = totalTime * FADE_TRANSITION_FPS;
2216         m_transitionSteps = steps;
2217         QPainter pixmapPainter;
2218         m_currentPixmapOpacity = (double)1 / steps;
2219         m_transitionDelay = (int)(totalTime * 1000) / steps;
2220         m_lastRenderedPixmap = QPixmap(m_lastRenderedPixmap.size());
2221         m_lastRenderedPixmap.fill(Qt::transparent);
2222         pixmapPainter.begin(&m_lastRenderedPixmap);
2223         pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
2224         pixmapPainter.setOpacity(1 - m_currentPixmapOpacity);
2225         pixmapPainter.drawPixmap(0, 0, m_previousPagePixmap);
2226         pixmapPainter.setOpacity(m_currentPixmapOpacity);
2227         pixmapPainter.drawPixmap(0, 0, m_currentPagePixmap);
2228         pixmapPainter.end();
2229         update();
2230     } break;
2231     // implement missing transitions (a binary raster engine needed here)
2232     case Okular::PageTransition::Fly:
2233 
2234     case Okular::PageTransition::Push:
2235 
2236     case Okular::PageTransition::Cover:
2237 
2238     case Okular::PageTransition::Uncover:
2239 
2240     default:
2241         update();
2242         return;
2243     }
2244 
2245     // send the first start to the timer
2246     m_transitionTimer->start(0);
2247 }
2248 
2249 void PresentationWidget::slotProcessMovieAction(const Okular::MovieAction *action)
2250 {
2251     const Okular::MovieAnnotation *movieAnnotation = action->annotation();
2252     if (!movieAnnotation) {
2253         return;
2254     }
2255 
2256     Okular::Movie *movie = movieAnnotation->movie();
2257     if (!movie) {
2258         return;
2259     }
2260 
2261     VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movieAnnotation->movie());
2262     if (!vw) {
2263         return;
2264     }
2265 
2266     vw->show();
2267 
2268     switch (action->operation()) {
2269     case Okular::MovieAction::Play:
2270         vw->stop();
2271         vw->play();
2272         break;
2273     case Okular::MovieAction::Stop:
2274         vw->stop();
2275         break;
2276     case Okular::MovieAction::Pause:
2277         vw->pause();
2278         break;
2279     case Okular::MovieAction::Resume:
2280         vw->play();
2281         break;
2282     };
2283 }
2284 
2285 void PresentationWidget::slotProcessRenditionAction(const Okular::RenditionAction *action)
2286 {
2287     Okular::Movie *movie = action->movie();
2288     if (!movie) {
2289         return;
2290     }
2291 
2292     VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movie);
2293     if (!vw) {
2294         return;
2295     }
2296 
2297     if (action->operation() == Okular::RenditionAction::None) {
2298         return;
2299     }
2300 
2301     vw->show();
2302 
2303     switch (action->operation()) {
2304     case Okular::RenditionAction::Play:
2305         vw->stop();
2306         vw->play();
2307         break;
2308     case Okular::RenditionAction::Stop:
2309         vw->stop();
2310         break;
2311     case Okular::RenditionAction::Pause:
2312         vw->pause();
2313         break;
2314     case Okular::RenditionAction::Resume:
2315         vw->play();
2316         break;
2317     default:
2318         return;
2319     };
2320 }
2321 
2322 void PresentationWidget::slotTogglePlayPause()
2323 {
2324     if (!m_nextPageTimer->isActive()) {
2325         m_advanceSlides = true;
2326         startAutoChangeTimer();
2327     } else {
2328         m_nextPageTimer->stop();
2329         m_advanceSlides = false;
2330         setPlayPauseIcon();
2331     }
2332 }
2333 
2334 #include "presentationwidget.moc"