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"