okular/part/presentationwidget.cpp
Sune Vuorela 720e08220d Allow dbus to be disabled
In certain usecases, especially for non-linux desktop builds, the
advantage of of dbus is often limited. Provide a way to disable
building with dbus.

Note that this also disables the related functionalities of having a
second okular launch open the document in the first okular.

This also disables the unit tests that tests those features.
2023-07-18 07:53:42 +00:00

2354 lines
85 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

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

/*
SPDX-FileCopyrightText: 2004 Enrico Ros <eros.kde@email.it>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "presentationwidget.h"
#include "config-okular.h"
// qt/kde includes
#if HAVE_DBUS
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusReply>
#endif
#include <QLoggingCategory>
#include <KActionCollection>
#include <KCursor>
#include <KLineEdit>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSelectAction>
#include <QAction>
#include <QApplication>
#include <QDialog>
#include <QEvent>
#include <QFontMetrics>
#include <QGestureEvent>
#include <QIcon>
#include <QImage>
#include <QLabel>
#include <QLayout>
#include <QPainter>
#include <QRandomGenerator>
#include <QScreen>
#include <QStyle>
#include <QStyleOption>
#include <QTimer>
#include <QToolBar>
#include <QToolTip>
#include <QValidator>
#ifdef Q_OS_LINUX
#if HAVE_DBUS
#include <QDBusUnixFileDescriptor>
#endif
#include <unistd.h> // For ::close() for sleep inhibition
#endif
// system includes
#include <math.h>
#include <stdlib.h>
// local includes
#include "annotationtools.h"
#include "core/action.h"
#include "core/annotations.h"
#include "core/audioplayer.h"
#include "core/document.h"
#include "core/generator.h"
#include "core/movie.h"
#include "core/page.h"
#include "drawingtoolactions.h"
#include "gui/debug_ui.h"
#include "gui/guiutils.h"
#include "gui/pagepainter.h"
#include "gui/priorities.h"
#include "presentationsearchbar.h"
#include "settings.h"
#include "settings_core.h"
#include "videowidget.h"
// comment this to disable the top-right progress indicator
#define ENABLE_PROGRESS_OVERLAY
// a frame contains a pointer to the page object, its geometry and the
// transition effect to the next frame
struct PresentationFrame {
PresentationFrame() = default;
~PresentationFrame()
{
qDeleteAll(videoWidgets);
}
PresentationFrame(const PresentationFrame &) = delete;
PresentationFrame &operator=(const PresentationFrame &) = delete;
void recalcGeometry(int width, int height, float screenRatio)
{
// calculate frame geometry keeping constant aspect ratio
float pageRatio = page->ratio();
int pageWidth = width, pageHeight = height;
if (pageRatio > screenRatio) {
pageWidth = (int)((float)pageHeight / pageRatio);
} else {
pageHeight = (int)((float)pageWidth * pageRatio);
}
geometry.setRect((width - pageWidth) / 2, (height - pageHeight) / 2, pageWidth, pageHeight);
for (VideoWidget *vw : qAsConst(videoWidgets)) {
const Okular::NormalizedRect r = vw->normGeometry();
QRect vwgeom = r.geometry(geometry.width(), geometry.height());
vw->resize(vwgeom.size());
vw->move(geometry.topLeft() + vwgeom.topLeft());
}
}
const Okular::Page *page;
QRect geometry;
QHash<Okular::Movie *, VideoWidget *> videoWidgets;
std::vector<SmoothPath> drawings;
};
// a custom QToolBar that basically does not propagate the event if the widget
// background is not automatically filled
class PresentationToolBar : public QToolBar
{
Q_OBJECT
public:
explicit PresentationToolBar(QWidget *parent = Q_NULLPTR)
: QToolBar(parent)
{
}
protected:
void mousePressEvent(QMouseEvent *e) override
{
QToolBar::mousePressEvent(e);
e->accept();
}
void mouseReleaseEvent(QMouseEvent *e) override
{
QToolBar::mouseReleaseEvent(e);
e->accept();
}
};
PresentationWidget::PresentationWidget(QWidget *parent, Okular::Document *doc, DrawingToolActions *drawingToolActions, KActionCollection *collection)
: QWidget(nullptr /* must be null, to have an independent widget */, Qt::FramelessWindowHint)
, m_pressedLink(nullptr)
, m_handCursor(false)
, m_drawingEngine(nullptr)
, m_screenInhibitCookie(0)
, m_sleepInhibitFd(-1)
, m_parentWidget(parent)
, m_document(doc)
, m_frameIndex(-1)
, m_topBar(nullptr)
, m_pagesEdit(nullptr)
, m_searchBar(nullptr)
, m_ac(collection)
, m_screenSelect(nullptr)
, m_isSetup(false)
, m_blockNotifications(false)
, m_inBlackScreenMode(false)
, m_showSummaryView(Okular::Settings::slidesShowSummary())
, m_advanceSlides(Okular::SettingsCore::slidesAdvance())
, m_goToPreviousPageOnRelease(false)
, m_goToNextPageOnRelease(false)
{
setAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_OpaquePaintEvent);
setObjectName(QStringLiteral("presentationWidget"));
QString caption = doc->metaData(QStringLiteral("DocumentTitle")).toString();
if (caption.trimmed().isEmpty()) {
caption = doc->currentDocument().fileName();
}
caption = i18nc("[document title/filename] Presentation", "%1 Presentation", caption);
setWindowTitle(caption);
m_width = -1;
// create top toolbar
m_topBar = new PresentationToolBar(this);
m_topBar->setObjectName(QStringLiteral("presentationBar"));
m_topBar->setMovable(false);
m_topBar->layout()->setContentsMargins(0, 0, 0, 0);
m_topBar->addAction(QIcon::fromTheme(layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-next") : QStringLiteral("go-previous")), i18n("Previous Page"), this, SLOT(slotPrevPage()));
m_pagesEdit = new KLineEdit(m_topBar);
QSizePolicy sp = m_pagesEdit->sizePolicy();
sp.setHorizontalPolicy(QSizePolicy::Minimum);
m_pagesEdit->setSizePolicy(sp);
QFontMetrics fm(m_pagesEdit->font());
QStyleOptionFrame option;
option.initFrom(m_pagesEdit);
m_pagesEdit->setMaximumWidth(fm.horizontalAdvance(QString::number(m_document->pages())) + 2 * style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, m_pagesEdit) +
4); // the 4 comes from 2*horizontalMargin, horizontalMargin being a define in qlineedit.cpp
QIntValidator *validator = new QIntValidator(1, m_document->pages(), m_pagesEdit);
m_pagesEdit->setValidator(validator);
m_topBar->addWidget(m_pagesEdit);
QLabel *pagesLabel = new QLabel(m_topBar);
pagesLabel->setText(QLatin1String(" / ") + QString::number(m_document->pages()) + QLatin1String(" "));
m_topBar->addWidget(pagesLabel);
connect(m_pagesEdit, &QLineEdit::returnPressed, this, &PresentationWidget::slotPageChanged);
m_topBar->addAction(QIcon::fromTheme(layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-previous") : QStringLiteral("go-next")), i18n("Next Page"), this, SLOT(slotNextPage()));
m_topBar->addSeparator();
QAction *playPauseAct = collection->action(QStringLiteral("presentation_play_pause"));
playPauseAct->setEnabled(true);
connect(playPauseAct, &QAction::triggered, this, &PresentationWidget::slotTogglePlayPause);
m_topBar->addAction(playPauseAct);
addAction(playPauseAct);
m_topBar->addSeparator();
const QList<QAction *> actionsList = drawingToolActions->actions();
for (QAction *action : actionsList) {
action->setEnabled(true);
m_topBar->addAction(action);
addAction(action);
}
connect(drawingToolActions, &DrawingToolActions::changeEngine, this, &PresentationWidget::slotChangeDrawingToolEngine);
connect(drawingToolActions, &DrawingToolActions::actionsRecreated, this, &PresentationWidget::slotAddDrawingToolActions);
QAction *eraseDrawingAct = collection->action(QStringLiteral("presentation_erase_drawings"));
eraseDrawingAct->setEnabled(true);
connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings);
m_topBar->addAction(eraseDrawingAct);
addAction(eraseDrawingAct);
const int screenCount = QApplication::screens().count();
if (screenCount > 1) {
m_topBar->addSeparator();
m_screenSelect = new KSelectAction(QIcon::fromTheme(QStringLiteral("video-display")), i18n("Switch Screen"), m_topBar);
m_screenSelect->setToolBarMode(KSelectAction::MenuMode);
m_screenSelect->setToolButtonPopupMode(QToolButton::InstantPopup);
m_topBar->addAction(m_screenSelect);
for (int i = 0; i < screenCount; ++i) {
QAction *act = m_screenSelect->addAction(i18nc("%1 is the screen number (0, 1, ...)", "Screen %1", i));
act->setData(QVariant::fromValue(i));
}
}
QWidget *spacer = new QWidget(m_topBar);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
m_topBar->addWidget(spacer);
m_topBar->addAction(QIcon::fromTheme(QStringLiteral("application-exit")), i18n("Exit Presentation Mode"), this, SLOT(close()));
m_topBar->setAutoFillBackground(true);
showTopBar(false);
// change topbar background color
QPalette p = m_topBar->palette();
p.setColor(QPalette::Active, QPalette::Button, Qt::gray);
p.setColor(QPalette::Active, QPalette::Window, Qt::darkGray);
m_topBar->setPalette(p);
// Grab swipe gestures to change pages
grabGesture(Qt::SwipeGesture);
// misc stuff
setMouseTracking(true);
setContextMenuPolicy(Qt::PreventContextMenu);
m_transitionTimer = new QTimer(this);
m_transitionTimer->setSingleShot(true);
connect(m_transitionTimer, &QTimer::timeout, this, &PresentationWidget::slotTransitionStep);
m_overlayHideTimer = new QTimer(this);
m_overlayHideTimer->setSingleShot(true);
connect(m_overlayHideTimer, &QTimer::timeout, this, &PresentationWidget::slotHideOverlay);
m_nextPageTimer = new QTimer(this);
m_nextPageTimer->setSingleShot(true);
connect(m_nextPageTimer, &QTimer::timeout, this, &PresentationWidget::slotNextPage);
setPlayPauseIcon();
connect(m_document, &Okular::Document::processMovieAction, this, &PresentationWidget::slotProcessMovieAction);
connect(m_document, &Okular::Document::processRenditionAction, this, &PresentationWidget::slotProcessRenditionAction);
// handle cursor appearance as specified in configuration
if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
KCursor::setAutoHideCursor(this, true);
KCursor::setHideCursorDelay(3000);
} else if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) {
setCursor(QCursor(Qt::BlankCursor));
}
setupActions();
// inhibit power management
inhibitPowerManagement();
QTimer::singleShot(0, this, &PresentationWidget::slotDelayedEvents);
// setFocus() so KCursor::setAutoHideCursor() goes into effect if it's enabled
setFocus(Qt::OtherFocusReason);
// Catch TabletEnterProximity and TabletLeaveProximity events from the QApplication
qApp->installEventFilter(this);
}
PresentationWidget::~PresentationWidget()
{
// allow power management saver again
allowPowerManagement();
// stop the audio playbacks
Okular::AudioPlayer::instance()->stopPlaybacks();
// remove our highlights
if (m_searchBar) {
m_document->resetSearch(PRESENTATION_SEARCH_ID);
}
// remove this widget from document observer
m_document->removeObserver(this);
const QList<QAction *> actionsList = actions();
for (QAction *action : actionsList) {
action->setChecked(false);
action->setEnabled(false);
}
delete m_drawingEngine;
// delete frames
qDeleteAll(m_frames);
qApp->removeEventFilter(this);
}
void PresentationWidget::notifySetup(const QVector<Okular::Page *> &pageSet, int setupFlags)
{
// same document, nothing to change - here we assume the document sets up
// us with the whole document set as first notifySetup()
if (!(setupFlags & Okular::DocumentObserver::DocumentChanged)) {
return;
}
// delete previous frames (if any (shouldn't be))
qDeleteAll(m_frames);
if (!m_frames.isEmpty()) {
qCWarning(OkularUiDebug) << "Frames setup changed while a Presentation is in progress.";
}
m_frames.clear();
// create the new frames
float screenRatio = (float)m_height / (float)m_width;
for (const Okular::Page *page : pageSet) {
PresentationFrame *frame = new PresentationFrame();
frame->page = page;
const QList<Okular::Annotation *> annotations = page->annotations();
for (Okular::Annotation *a : annotations) {
if (a->subType() == Okular::Annotation::AMovie) {
Okular::MovieAnnotation *movieAnn = static_cast<Okular::MovieAnnotation *>(a);
VideoWidget *vw = new VideoWidget(movieAnn, movieAnn->movie(), m_document, this);
frame->videoWidgets.insert(movieAnn->movie(), vw);
vw->pageInitialized();
} else if (a->subType() == Okular::Annotation::ARichMedia) {
Okular::RichMediaAnnotation *richMediaAnn = static_cast<Okular::RichMediaAnnotation *>(a);
if (richMediaAnn->movie()) {
VideoWidget *vw = new VideoWidget(richMediaAnn, richMediaAnn->movie(), m_document, this);
frame->videoWidgets.insert(richMediaAnn->movie(), vw);
vw->pageInitialized();
}
} else if (a->subType() == Okular::Annotation::AScreen) {
const Okular::ScreenAnnotation *screenAnn = static_cast<Okular::ScreenAnnotation *>(a);
Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation(screenAnn);
if (movie) {
VideoWidget *vw = new VideoWidget(screenAnn, movie, m_document, this);
frame->videoWidgets.insert(movie, vw);
vw->pageInitialized();
}
}
}
frame->recalcGeometry(m_width, m_height, screenRatio);
// add the frame to the vector
m_frames.push_back(frame);
}
// get metadata from the document
m_metaStrings.clear();
const Okular::DocumentInfo info = m_document->documentInfo(QSet<Okular::DocumentInfo::Key>() << Okular::DocumentInfo::Title << Okular::DocumentInfo::Author);
if (!info.get(Okular::DocumentInfo::Title).isNull()) {
m_metaStrings += i18n("Title: %1", info.get(Okular::DocumentInfo::Title));
}
if (!info.get(Okular::DocumentInfo::Author).isNull()) {
m_metaStrings += i18n("Author: %1", info.get(Okular::DocumentInfo::Author));
}
m_metaStrings += i18n("Pages: %1", m_document->pages());
m_metaStrings += i18n("Click to begin");
m_isSetup = true;
}
void PresentationWidget::notifyViewportChanged(bool /*smoothMove*/)
{
// display the current page
changePage(m_document->viewport().pageNumber);
// auto advance to the next page if set
startAutoChangeTimer();
}
void PresentationWidget::notifyPageChanged(int pageNumber, int changedFlags)
{
// if we are blocking the notifications, do nothing
if (m_blockNotifications) {
return;
}
// check if it's the last requested pixmap. if so update the widget.
if ((changedFlags & (DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights)) && pageNumber == m_frameIndex) {
generatePage(changedFlags & (DocumentObserver::Annotations | DocumentObserver::Highlights));
}
}
void PresentationWidget::notifyCurrentPageChanged(int previousPage, int currentPage)
{
if (previousPage != -1) {
// stop video playback
for (VideoWidget *vw : qAsConst(m_frames[previousPage]->videoWidgets)) {
vw->stop();
vw->pageLeft();
}
// stop audio playback, if any
Okular::AudioPlayer::instance()->stopPlaybacks();
// perform the page closing action, if any
if (m_document->page(previousPage)->pageAction(Okular::Page::Closing)) {
m_document->processAction(m_document->page(previousPage)->pageAction(Okular::Page::Closing));
}
// perform the additional actions of the page's annotations, if any
const QList<Okular::Annotation *> annotationsList = m_document->page(previousPage)->annotations();
for (const Okular::Annotation *annotation : annotationsList) {
Okular::Action *action = nullptr;
if (annotation->subType() == Okular::Annotation::AScreen) {
action = static_cast<const Okular::ScreenAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageClosing);
} else if (annotation->subType() == Okular::Annotation::AWidget) {
action = static_cast<const Okular::WidgetAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageClosing);
}
if (action) {
m_document->processAction(action);
}
}
}
if (currentPage != -1) {
m_frameIndex = currentPage;
// check if pixmap exists or else request it
PresentationFrame *frame = m_frames[m_frameIndex];
int pixW = frame->geometry.width();
int pixH = frame->geometry.height();
bool signalsBlocked = m_pagesEdit->signalsBlocked();
m_pagesEdit->blockSignals(true);
m_pagesEdit->setText(QString::number(m_frameIndex + 1));
m_pagesEdit->blockSignals(signalsBlocked);
// if pixmap not inside the Okular::Page we request it and wait for
// notifyPixmapChanged call or else we can proceed to pixmap generation
if (!frame->page->hasPixmap(this, ceil(pixW * devicePixelRatioF()), ceil(pixH * devicePixelRatioF()))) {
requestPixmaps();
} else {
// make the background pixmap
generatePage();
}
// perform the page opening action, if any
if (m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening)) {
m_document->processAction(m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening));
}
// perform the additional actions of the page's annotations, if any
const QList<Okular::Annotation *> annotationsList = m_document->page(m_frameIndex)->annotations();
for (const Okular::Annotation *annotation : annotationsList) {
Okular::Action *action = nullptr;
if (annotation->subType() == Okular::Annotation::AScreen) {
action = static_cast<const Okular::ScreenAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageOpening);
} else if (annotation->subType() == Okular::Annotation::AWidget) {
action = static_cast<const Okular::WidgetAnnotation *>(annotation)->additionalAction(Okular::Annotation::PageOpening);
}
if (action) {
m_document->processAction(action);
}
}
// start autoplay video playback
for (VideoWidget *vw : qAsConst(m_frames[m_frameIndex]->videoWidgets)) {
vw->pageEntered();
}
}
}
bool PresentationWidget::canUnloadPixmap(int pageNumber) const
{
if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal) {
// can unload all pixmaps except for the currently visible one
return pageNumber != m_frameIndex;
} else {
// can unload all pixmaps except for the currently visible one, previous and next
return qAbs(pageNumber - m_frameIndex) <= 1;
}
}
void PresentationWidget::setupActions()
{
addAction(m_ac->action(QStringLiteral("first_page")));
addAction(m_ac->action(QStringLiteral("last_page")));
addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::Prior))));
addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::Next))));
addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::DocumentBack))));
addAction(m_ac->action(QString::fromLocal8Bit(KStandardAction::name(KStandardAction::DocumentForward))));
QAction *action = m_ac->action(QStringLiteral("switch_blackscreen_mode"));
connect(action, &QAction::toggled, this, &PresentationWidget::toggleBlackScreenMode);
action->setEnabled(true);
addAction(action);
}
void PresentationWidget::setPlayPauseIcon()
{
QAction *playPauseAction = m_ac->action(QStringLiteral("presentation_play_pause"));
if (m_nextPageTimer->isActive()) {
playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
playPauseAction->setToolTip(i18nc("For Presentation", "Pause"));
} else {
playPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
playPauseAction->setToolTip(i18nc("For Presentation", "Play"));
}
}
bool PresentationWidget::eventFilter(QObject *o, QEvent *e)
{
if (o == qApp) {
if (e->type() == QTabletEvent::TabletEnterProximity) {
setCursor(QCursor(Qt::CrossCursor));
} else if (e->type() == QTabletEvent::TabletLeaveProximity) {
setCursor(QCursor(Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ? Qt::BlankCursor : Qt::ArrowCursor));
if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
// Trick KCursor to hide the cursor if needed by sending an "unknown" key press event
// Send also the key release to make the world happy even it's probably not needed
QKeyEvent kp(QEvent::KeyPress, 0, Qt::NoModifier);
qApp->sendEvent(this, &kp);
QKeyEvent kr(QEvent::KeyRelease, 0, Qt::NoModifier);
qApp->sendEvent(this, &kr);
}
}
}
return false;
}
// <widget events>
bool PresentationWidget::event(QEvent *e)
{
if (e->type() == QEvent::Gesture) {
return gestureEvent(static_cast<QGestureEvent *>(e));
}
if (e->type() == QEvent::ToolTip) {
QHelpEvent *he = (QHelpEvent *)e;
QRect r;
const Okular::Action *link = getLink(he->x(), he->y(), &r);
if (link) {
QString tip = link->actionTip();
if (!tip.isEmpty()) {
QToolTip::showText(he->globalPos(), tip, this, r);
}
}
e->accept();
return true;
} else {
// do not stop the event
return QWidget::event(e);
}
}
bool PresentationWidget::gestureEvent(QGestureEvent *event)
{
// Swiping left or right on a touch screen will go to the previous or next slide, respectively.
// The precise gesture is the standard Qt swipe: with three(!) fingers.
if (QGesture *swipe = event->gesture(Qt::SwipeGesture)) {
QSwipeGesture *swipeEvent = static_cast<QSwipeGesture *>(swipe);
if (swipeEvent->state() == Qt::GestureFinished) {
if (swipeEvent->horizontalDirection() == QSwipeGesture::Left) {
slotPrevPage();
event->accept();
return true;
}
if (swipeEvent->horizontalDirection() == QSwipeGesture::Right) {
slotNextPage();
event->accept();
return true;
}
}
}
return false;
}
void PresentationWidget::keyPressEvent(QKeyEvent *e)
{
if (!m_isSetup) {
return;
}
switch (e->key()) {
case Qt::Key_Left:
case Qt::Key_Backspace:
case Qt::Key_PageUp:
case Qt::Key_Up:
slotPrevPage();
break;
case Qt::Key_Right:
case Qt::Key_Space:
case Qt::Key_PageDown:
case Qt::Key_Down:
slotNextPage();
break;
case Qt::Key_Home:
slotFirstPage();
break;
case Qt::Key_End:
slotLastPage();
break;
case Qt::Key_Escape:
if (!m_topBar->isHidden()) {
showTopBar(false);
} else {
close();
}
break;
}
}
void PresentationWidget::wheelEvent(QWheelEvent *e)
{
if (!m_isSetup) {
return;
}
// performance note: don't remove the clipping
int div = e->angleDelta().y() / 120;
if (div > 0) {
if (div > 3) {
div = 3;
}
while (div--) {
slotPrevPage();
}
} else if (div < 0) {
if (div < -3) {
div = -3;
}
while (div++) {
slotNextPage();
}
}
}
void PresentationWidget::mousePressEvent(QMouseEvent *e)
{
if (!m_isSetup) {
return;
}
if (m_drawingEngine) {
QRect r = routeMouseDrawingEvent(e);
if (r.isValid()) {
m_drawingRect |= r.translated(m_frames[m_frameIndex]->geometry.topLeft());
update(m_drawingRect);
}
return;
}
// pressing left button
if (e->button() == Qt::LeftButton) {
// if pressing on a link, skip other checks
if ((m_pressedLink = getLink(e->x(), e->y()))) {
return;
}
const Okular::Annotation *annotation = getAnnotation(e->x(), e->y());
if (annotation) {
if (annotation->subType() == Okular::Annotation::AMovie) {
const Okular::MovieAnnotation *movieAnnotation = static_cast<const Okular::MovieAnnotation *>(annotation);
VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movieAnnotation->movie());
vw->show();
vw->play();
return;
} else if (annotation->subType() == Okular::Annotation::ARichMedia) {
const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast<const Okular::RichMediaAnnotation *>(annotation);
VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(richMediaAnnotation->movie());
vw->show();
vw->play();
return;
} else if (annotation->subType() == Okular::Annotation::AScreen) {
m_document->processAction(static_cast<const Okular::ScreenAnnotation *>(annotation)->action());
return;
}
}
// handle clicking on top-right overlay
if (!(Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) && m_overlayGeometry.contains(e->pos())) {
overlayClick(e->pos());
return;
}
// Actual mouse press events always lead to the next page page
if (e->source() == Qt::MouseEventNotSynthesized) {
m_goToNextPageOnRelease = true;
}
// Touch events may lead to the previous or next page
else if (Okular::Settings::slidesTapNavigation() != Okular::Settings::EnumSlidesTapNavigation::Disabled) {
switch (Okular::Settings::slidesTapNavigation()) {
case Okular::Settings::EnumSlidesTapNavigation::ForwardBackward: {
if (e->x() < (geometry().width() / 2)) {
m_goToPreviousPageOnRelease = true;
} else {
m_goToNextPageOnRelease = true;
}
break;
}
case Okular::Settings::EnumSlidesTapNavigation::Forward: {
m_goToNextPageOnRelease = true;
break;
}
case Okular::Settings::EnumSlidesTapNavigation::Disabled: {
// Do Nothing
}
}
}
}
// pressing forward button
else if (e->button() == Qt::ForwardButton) {
m_goToNextPageOnRelease = true;
}
// pressing right or backward button
else if (e->button() == Qt::RightButton || e->button() == Qt::BackButton) {
m_goToPreviousPageOnRelease = true;
}
}
void PresentationWidget::mouseReleaseEvent(QMouseEvent *e)
{
if (m_drawingEngine) {
routeMouseDrawingEvent(e);
return;
}
// if releasing on the same link we pressed over, execute it
if (m_pressedLink && e->button() == Qt::LeftButton) {
const Okular::Action *link = getLink(e->x(), e->y());
if (link == m_pressedLink) {
m_document->processAction(link);
}
m_pressedLink = nullptr;
}
if (m_goToPreviousPageOnRelease) {
slotPrevPage();
m_goToPreviousPageOnRelease = false;
}
if (m_goToNextPageOnRelease) {
slotNextPage();
m_goToNextPageOnRelease = false;
}
}
void PresentationWidget::mouseMoveEvent(QMouseEvent *e)
{
// safety check
if (!m_isSetup) {
return;
}
// update cursor and tooltip if hovering a link
if (!m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden) {
testCursorOnLink(e->x(), e->y());
}
if (!m_topBar->isHidden()) {
// hide a shown bar when exiting the area
if (e->y() > (m_topBar->height() + 1)) {
showTopBar(false);
setFocus(Qt::OtherFocusReason);
}
} else {
if (m_drawingEngine && e->buttons() != Qt::NoButton) {
QRect r = routeMouseDrawingEvent(e);
if (r.isValid()) {
m_drawingRect |= r.translated(m_frames[m_frameIndex]->geometry.topLeft());
update(m_drawingRect);
}
} else {
// show the bar if reaching top 2 pixels
if (e->y() <= 1) {
showTopBar(true);
} else if ((QApplication::mouseButtons() & Qt::LeftButton) && m_overlayGeometry.contains(e->pos())) {
// handle "dragging the wheel" if clicking on its geometry
overlayClick(e->pos());
}
}
}
}
void PresentationWidget::paintEvent(QPaintEvent *pe)
{
qreal dpr = devicePixelRatioF();
if (m_inBlackScreenMode) {
QPainter painter(this);
painter.fillRect(pe->rect(), Qt::black);
return;
}
if (!m_isSetup) {
m_width = width();
m_height = height();
connect(m_document, &Okular::Document::linkFind, this, &PresentationWidget::slotFind);
// register this observer in document. events will come immediately
m_document->addObserver(this);
// show summary if requested
if (Okular::Settings::slidesShowSummary()) {
generatePage();
}
}
// check painting rect consistency
QRect r = pe->rect().intersected(QRect(QPoint(0, 0), geometry().size()));
if (r.isNull()) {
return;
}
if (m_lastRenderedPixmap.isNull()) {
QPainter painter(this);
painter.fillRect(pe->rect(), Okular::Settings::slidesBackgroundColor());
return;
}
// blit the pixmap to the screen
QPainter painter(this);
for (const QRect &r : pe->region()) {
if (!r.isValid()) {
continue;
}
#ifdef ENABLE_PROGRESS_OVERLAY
const QRect dR(QRectF(r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr).toAlignedRect());
if (Okular::Settings::slidesShowProgress() && r.intersects(m_overlayGeometry)) {
// backbuffer the overlay operation
QPixmap backPixmap(dR.size());
backPixmap.setDevicePixelRatio(dpr);
QPainter pixPainter(&backPixmap);
// first draw the background on the backbuffer
pixPainter.drawPixmap(QPoint(0, 0), m_lastRenderedPixmap, dR);
// then blend the overlay (a piece of) over the background
QRect ovr = m_overlayGeometry.intersected(r);
pixPainter.drawPixmap((ovr.left() - r.left()), (ovr.top() - r.top()), m_lastRenderedOverlay, (ovr.left() - m_overlayGeometry.left()) * dpr, (ovr.top() - m_overlayGeometry.top()) * dpr, ovr.width() * dpr, ovr.height() * dpr);
// finally blit the pixmap to the screen
pixPainter.end();
const QRect backPixmapRect = backPixmap.rect();
const QRect dBackPixmapRect(QRectF(backPixmapRect.x() * dpr, backPixmapRect.y() * dpr, backPixmapRect.width() * dpr, backPixmapRect.height() * dpr).toAlignedRect());
painter.drawPixmap(r.topLeft(), backPixmap, dBackPixmapRect);
} else {
#endif
// copy the rendered pixmap to the screen
painter.drawPixmap(r.topLeft(), m_lastRenderedPixmap, dR);
}
}
// paint drawings
if (m_frameIndex != -1) {
painter.save();
const QRect &geom = m_frames[m_frameIndex]->geometry;
const QSize pmSize(geom.width() * dpr, geom.height() * dpr);
QPixmap pm(pmSize);
pm.fill(Qt::transparent);
QPainter pmPainter(&pm);
pm.setDevicePixelRatio(dpr);
pmPainter.setRenderHints(QPainter::Antialiasing);
// Paint old paths
for (const SmoothPath &drawing : m_frames[m_frameIndex]->drawings) {
drawing.paint(&pmPainter, pmSize.width(), pmSize.height());
}
// Paint the path that is currently being drawn by the user
if (m_drawingEngine && m_drawingRect.intersects(pe->rect())) {
m_drawingEngine->paint(&pmPainter, pmSize.width(), pmSize.height(), m_drawingRect.intersected(pe->rect()));
}
painter.setRenderHints(QPainter::Antialiasing);
painter.drawPixmap(geom.topLeft(), pm);
painter.restore();
}
painter.end();
}
void PresentationWidget::resizeEvent(QResizeEvent *re)
{
m_width = width();
m_height = height();
// if by chance the new size equals the old, do not invalidate pixmaps and such..
if (size() == re->oldSize()) {
return;
}
// BEGIN Top toolbar
// tool bar height in pixels, make it large enough to hold the text fields with the page numbers
const int toolBarHeight = m_pagesEdit->height() * 1.5;
m_topBar->setGeometry(0, 0, width(), toolBarHeight);
m_topBar->setIconSize(QSize(toolBarHeight * 0.75, toolBarHeight * 0.75));
// END Top toolbar
// BEGIN Content area
// update the frames
const float screenRatio = (float)m_height / (float)m_width;
for (PresentationFrame *frame : qAsConst(m_frames)) {
frame->recalcGeometry(m_width, m_height, screenRatio);
}
if (m_frameIndex != -1) {
// ugliness alarm!
const_cast<Okular::Page *>(m_frames[m_frameIndex]->page)->deletePixmap(this);
// force the regeneration of the pixmap
m_lastRenderedPixmap = QPixmap();
m_blockNotifications = true;
requestPixmaps();
m_blockNotifications = false;
}
if (m_transitionTimer->isActive()) {
m_transitionTimer->stop();
}
generatePage(true /* no transitions */);
// END Content area
}
void PresentationWidget::enterEvent(QEvent *e)
{
if (!m_topBar->isHidden()) {
QEnterEvent *ee = static_cast<QEnterEvent *>(e);
// This can happen when we exited the widget via a "tooltip" and the tooltip disappears
if (ee->y() > (m_topBar->height() + 1)) {
showTopBar(false);
}
}
QWidget::enterEvent(e);
}
void PresentationWidget::leaveEvent(QEvent *e)
{
Q_UNUSED(e)
if (!m_topBar->isHidden()) {
if (QToolTip::isVisible()) {
// make sure we're not hovering over the tooltip
// because the world is sad, this works differently on Wayland and X11
// on X11 the widget under the cursor is the tooltip window
// on wayland it's the button generating the tooltip (why? no idea)
const QWidget *widgetUnderCursor = qApp->widgetAt(QCursor::pos());
if (widgetUnderCursor) {
const QWidget *widgetUnderCursorWindow = widgetUnderCursor->window();
if (widgetUnderCursorWindow == this) {
qDebug() << "Wayland";
return;
} else {
const QWidget *widgetUnderCursorParentWindow = widgetUnderCursorWindow->parentWidget() ? widgetUnderCursorWindow->parentWidget()->window() : nullptr;
if (widgetUnderCursorParentWindow == this) {
qDebug() << "X11";
return;
}
}
}
}
showTopBar(false);
}
}
// </widget events>
const void *PresentationWidget::getObjectRect(Okular::ObjectRect::ObjectType type, int x, int y, QRect *geometry) const
{
// no links on invalid pages
if (geometry && !geometry->isNull()) {
geometry->setRect(0, 0, 0, 0);
}
if (m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size()) {
return nullptr;
}
// get frame, page and geometry
const PresentationFrame *frame = m_frames[m_frameIndex];
const Okular::Page *page = frame->page;
const QRect &frameGeometry = frame->geometry;
// compute normalized x and y
double nx = (double)(x - frameGeometry.left()) / (double)frameGeometry.width();
double ny = (double)(y - frameGeometry.top()) / (double)frameGeometry.height();
// no links outside the pages
if (nx < 0 || nx > 1 || ny < 0 || ny > 1) {
return nullptr;
}
// check if 1) there is an object and 2) it's a link
const QRect screenRect = oldQt_screenOf(this)->geometry();
const Okular::ObjectRect *object = page->objectRect(type, nx, ny, screenRect.width(), screenRect.height());
if (!object) {
return nullptr;
}
// compute link geometry if destination rect present
if (geometry) {
*geometry = object->boundingRect(frameGeometry.width(), frameGeometry.height());
geometry->translate(frameGeometry.left(), frameGeometry.top());
}
// return the link pointer
return object->object();
}
const Okular::Action *PresentationWidget::getLink(int x, int y, QRect *geometry) const
{
return reinterpret_cast<const Okular::Action *>(getObjectRect(Okular::ObjectRect::Action, x, y, geometry));
}
const Okular::Annotation *PresentationWidget::getAnnotation(int x, int y, QRect *geometry) const
{
return reinterpret_cast<const Okular::Annotation *>(getObjectRect(Okular::ObjectRect::OAnnotation, x, y, geometry));
}
void PresentationWidget::testCursorOnLink(int x, int y)
{
const Okular::Action *link = getLink(x, y, nullptr);
const Okular::Annotation *annotation = getAnnotation(x, y, nullptr);
const bool needsHandCursor = ((link != nullptr) || ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::AMovie)) || ((annotation != nullptr) && (annotation->subType() == Okular::Annotation::ARichMedia)) ||
((annotation != nullptr) && (annotation->subType() == Okular::Annotation::AScreen) && (GuiUtils::renditionMovieFromScreenAnnotation(static_cast<const Okular::ScreenAnnotation *>(annotation)) != nullptr)));
// only react on changes (in/out from a link)
if ((needsHandCursor && !m_handCursor) || (!needsHandCursor && m_handCursor)) {
// change cursor shape
m_handCursor = needsHandCursor;
setCursor(QCursor(m_handCursor ? Qt::PointingHandCursor : Qt::ArrowCursor));
}
}
void PresentationWidget::overlayClick(const QPoint position)
{
// clicking the progress indicator
int xPos = position.x() - m_overlayGeometry.x() - m_overlayGeometry.width() / 2, yPos = m_overlayGeometry.height() / 2 - position.y();
if (!xPos && !yPos) {
return;
}
// compute angle relative to indicator (note coord transformation)
float angle = 0.5 + 0.5 * atan2((double)-xPos, (double)-yPos) / M_PI;
int pageIndex = (int)(angle * (m_frames.count() - 1) + 0.5);
// go to selected page
changePage(pageIndex);
}
void PresentationWidget::changePage(int newPage)
{
if (m_showSummaryView) {
m_showSummaryView = false;
m_frameIndex = -1;
return;
}
if (m_frameIndex == newPage) {
return;
}
// switch to newPage
m_document->setViewportPage(newPage, this);
if ((Okular::Settings::slidesShowSummary() && !m_showSummaryView) || m_frameIndex == -1) {
notifyCurrentPageChanged(-1, newPage);
}
}
void PresentationWidget::generatePage(bool disableTransition)
{
if (m_lastRenderedPixmap.isNull()) {
qreal dpr = devicePixelRatioF();
m_lastRenderedPixmap = QPixmap(m_width * dpr, m_height * dpr);
m_lastRenderedPixmap.setDevicePixelRatio(dpr);
m_previousPagePixmap = QPixmap();
} else {
m_previousPagePixmap = m_lastRenderedPixmap;
}
// opens the painter over the pixmap
QPainter pixmapPainter;
pixmapPainter.begin(&m_lastRenderedPixmap);
// generate welcome page
if (m_frameIndex == -1) {
generateIntroPage(pixmapPainter);
}
// generate a normal pixmap with extended margin filling
if (m_frameIndex >= 0 && m_frameIndex < (int)m_document->pages()) {
generateContentsPage(m_frameIndex, pixmapPainter);
}
pixmapPainter.end();
// generate the top-right corner overlay
#ifdef ENABLE_PROGRESS_OVERLAY
if (Okular::Settings::slidesShowProgress() && m_frameIndex != -1) {
generateOverlay();
}
#endif
// start transition on pages that have one
disableTransition |= (Okular::Settings::slidesTransition() == Okular::Settings::EnumSlidesTransition::NoTransitions);
if (!disableTransition) {
const Okular::PageTransition *transition = m_frameIndex != -1 ? m_frames[m_frameIndex]->page->transition() : nullptr;
if (transition) {
initTransition(transition);
} else {
Okular::PageTransition trans = defaultTransition();
initTransition(&trans);
}
} else {
Okular::PageTransition trans = defaultTransition(Okular::Settings::EnumSlidesTransition::Replace);
initTransition(&trans);
}
// update cursor + tooltip
if (!m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden) {
QPoint p = mapFromGlobal(QCursor::pos());
testCursorOnLink(p.x(), p.y());
}
}
void PresentationWidget::generateIntroPage(QPainter &p)
{
qreal dpr = devicePixelRatioF();
// use a vertical gray gradient background
int blend1 = m_height / 10, blend2 = 9 * m_height / 10;
int baseTint = QColor(Qt::gray).red();
for (int i = 0; i < m_height; i++) {
int k = baseTint;
if (i < blend1) {
k -= (int)(baseTint * (i - blend1) * (i - blend1) / (float)(blend1 * blend1));
}
if (i > blend2) {
k += (int)((255 - baseTint) * (i - blend2) * (i - blend2) / (float)(blend1 * blend1));
}
p.fillRect(0, i, m_width, 1, QColor(k, k, k));
}
// draw okular logo in the four corners
QPixmap logo = QIcon::fromTheme(QStringLiteral("okular")).pixmap(64 * dpr);
logo.setDevicePixelRatio(dpr);
if (!logo.isNull()) {
p.drawPixmap(5, 5, logo);
p.drawPixmap(m_width - 5 - logo.width(), 5, logo);
p.drawPixmap(m_width - 5 - logo.width(), m_height - 5 - logo.height(), logo);
p.drawPixmap(5, m_height - 5 - logo.height(), logo);
}
// draw metadata text (the last line is 'click to begin')
int strNum = m_metaStrings.count(), strHeight = m_height / (strNum + 4), fontHeight = 2 * strHeight / 3;
QFont font(p.font());
font.setPixelSize(fontHeight);
QFontMetrics metrics(font);
for (int i = 0; i < strNum; i++) {
// set a font to fit text width
float wScale = (float)metrics.boundingRect(m_metaStrings[i]).width() / (float)m_width;
QFont f(font);
if (wScale > 1.0) {
f.setPixelSize((int)((float)fontHeight / (float)wScale));
}
p.setFont(f);
// text shadow
p.setPen(Qt::darkGray);
p.drawText(2, m_height / 4 + strHeight * i + 2, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i]);
// text body
p.setPen(128 + (127 * i) / strNum);
p.drawText(0, m_height / 4 + strHeight * i, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i]);
}
}
void PresentationWidget::generateContentsPage(int pageNum, QPainter &p)
{
PresentationFrame *frame = m_frames[pageNum];
// translate painter and contents rect
QRect geom(frame->geometry);
p.translate(geom.left(), geom.top());
geom.translate(-geom.left(), -geom.top());
// draw the page using the shared PagePainter class
int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations;
PagePainter::paintPageOnPainter(&p, frame->page, this, flags, geom.width(), geom.height(), geom);
// restore painter
p.translate(-frame->geometry.left(), -frame->geometry.top());
// fill unpainted areas with background color
const QRegion unpainted(QRect(0, 0, m_width, m_height));
const QRegion rgn = unpainted.subtracted(frame->geometry);
for (const QRect &r : rgn) {
p.fillRect(r, Okular::Settings::slidesBackgroundColor());
}
}
// from Arthur - Qt4 - (is defined elsewhere as 'qt_div_255' to not break final compilation)
inline int qt_div255(int x)
{
return (x + (x >> 8) + 0x80) >> 8;
}
void PresentationWidget::generateOverlay()
{
#ifdef ENABLE_PROGRESS_OVERLAY
qreal dpr = devicePixelRatioF();
// calculate overlay geometry and resize pixmap if needed
double side = m_width / 16.0;
m_overlayGeometry.setRect(m_width - side - 4, 4, side, side);
// note: to get a sort of antialiasing, we render the pixmap double sized
// and the resulting image is smoothly scaled down. So here we open a
// painter on the double sized pixmap.
side *= 2;
QPixmap doublePixmap(side * dpr, side * dpr);
doublePixmap.setDevicePixelRatio(dpr);
doublePixmap.fill(Qt::black);
QPainter pixmapPainter(&doublePixmap);
pixmapPainter.setRenderHints(QPainter::Antialiasing);
// draw PIE SLICES in blue levels (the levels will then be the alpha component)
int pages = m_document->pages();
if (pages > 28) { // draw continuous slices
int degrees = (int)(360 * (float)(m_frameIndex + 1) / (float)pages);
pixmapPainter.setPen(0x05);
pixmapPainter.setBrush(QColor(0x40));
pixmapPainter.drawPie(2, 2, side - 4, side - 4, 90 * 16, (360 - degrees) * 16);
pixmapPainter.setPen(0x40);
pixmapPainter.setBrush(QColor(0xF0));
pixmapPainter.drawPie(2, 2, side - 4, side - 4, 90 * 16, -degrees * 16);
} else { // draw discrete slices
float oldCoord = -90;
for (int i = 0; i < pages; i++) {
float newCoord = -90 + 360 * (float)(i + 1) / (float)pages;
pixmapPainter.setPen(i <= m_frameIndex ? 0x40 : 0x05);
pixmapPainter.setBrush(QColor(i <= m_frameIndex ? 0xF0 : 0x40));
pixmapPainter.drawPie(2, 2, side - 4, side - 4, (int)(-16 * (oldCoord + 1)), (int)(-16 * (newCoord - (oldCoord + 2))));
oldCoord = newCoord;
}
}
int circleOut = side / 4;
pixmapPainter.setPen(Qt::black);
pixmapPainter.setBrush(Qt::black);
pixmapPainter.drawEllipse(circleOut, circleOut, side - 2 * circleOut, side - 2 * circleOut);
// draw TEXT using maximum opacity
QFont f(pixmapPainter.font());
f.setPixelSize(side / 4);
pixmapPainter.setFont(f);
pixmapPainter.setPen(0xFF);
// use a little offset to prettify output
pixmapPainter.drawText(2, 2, side, side, Qt::AlignCenter, QString::number(m_frameIndex + 1));
// end drawing pixmap and halve image
pixmapPainter.end();
QImage image(doublePixmap.toImage().scaled((side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
image.setDevicePixelRatio(dpr);
image = image.convertToFormat(QImage::Format_ARGB32);
image.setDevicePixelRatio(dpr);
// draw circular shadow using the same technique
doublePixmap.fill(Qt::black);
pixmapPainter.begin(&doublePixmap);
pixmapPainter.setPen(0x40);
pixmapPainter.setBrush(QColor(0x80));
pixmapPainter.drawEllipse(0, 0, side, side);
pixmapPainter.end();
QImage shadow(doublePixmap.toImage().scaled((side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
shadow.setDevicePixelRatio(dpr);
// generate a 2 colors pixmap using mixing shadow (made with highlight color)
// and image (made with highlightedText color)
QPalette pal = palette();
QColor color = pal.color(QPalette::Active, QPalette::HighlightedText);
int red = color.red(), green = color.green(), blue = color.blue();
color = pal.color(QPalette::Active, QPalette::Highlight);
int sRed = color.red(), sGreen = color.green(), sBlue = color.blue();
// pointers
unsigned int *data = reinterpret_cast<unsigned int *>(image.bits()), *shadowData = reinterpret_cast<unsigned int *>(shadow.bits()), pixels = image.width() * image.height();
// cache data (reduce computation time to 26%!)
int c1 = -1, c2 = -1, cR = 0, cG = 0, cB = 0, cA = 0;
// foreach pixel
for (unsigned int i = 0; i < pixels; ++i) {
// alpha for shadow and image
int shadowAlpha = shadowData[i] & 0xFF, srcAlpha = data[i] & 0xFF;
// cache values
if (srcAlpha != c1 || shadowAlpha != c2) {
c1 = srcAlpha;
c2 = shadowAlpha;
// fuse color components and alpha value of image over shadow
data[i] = qRgba(cR = qt_div255(srcAlpha * red + (255 - srcAlpha) * sRed),
cG = qt_div255(srcAlpha * green + (255 - srcAlpha) * sGreen),
cB = qt_div255(srcAlpha * blue + (255 - srcAlpha) * sBlue),
cA = qt_div255(srcAlpha * srcAlpha + (255 - srcAlpha) * shadowAlpha));
} else {
data[i] = qRgba(cR, cG, cB, cA);
}
}
m_lastRenderedOverlay = QPixmap::fromImage(image);
m_lastRenderedOverlay.setDevicePixelRatio(dpr);
// start the autohide timer
// repaint( m_overlayGeometry ); // toggle with next line
update(m_overlayGeometry);
m_overlayHideTimer->start(2500);
#endif
}
QRect PresentationWidget::routeMouseDrawingEvent(QMouseEvent *e)
{
if (m_frameIndex == -1) { // Can't draw on the summary page
return QRect();
}
const QRect &geom = m_frames[m_frameIndex]->geometry;
const Okular::Page *page = m_frames[m_frameIndex]->page;
AnnotatorEngine::EventType eventType;
AnnotatorEngine::Button button;
AnnotatorEngine::Modifiers modifiers;
// figure out the event type and button
AnnotatorEngine::decodeEvent(e, &eventType, &button);
static bool hasclicked = false;
if (eventType == AnnotatorEngine::Press) {
hasclicked = true;
}
QPointF mousePos = e->localPos();
double nX = (mousePos.x() - (double)geom.left()) / (double)geom.width();
double nY = (mousePos.y() - (double)geom.top()) / (double)geom.height();
QRect ret;
bool isInside = nX >= 0 && nX < 1 && nY >= 0 && nY < 1;
if (hasclicked && !isInside) {
// Fake a move to the last border pos
nX = qBound(0., nX, 1.);
nY = qBound(0., nY, 1.);
m_drawingEngine->event(AnnotatorEngine::Move, button, modifiers, nX, nY, geom.width(), geom.height(), page);
// Fake a release in the following lines
eventType = AnnotatorEngine::Release;
isInside = true;
} else if (!hasclicked && isInside) {
// we're coming from the outside, pretend we started clicking at the closest border
if (nX < (1 - nX) && nX < nY && nX < (1 - nY)) {
nX = 0;
} else if (nY < (1 - nY) && nY < nX && nY < (1 - nX)) {
nY = 0;
} else if ((1 - nX) < nX && (1 - nX) < nY && (1 - nX) < (1 - nY)) {
nX = 1;
} else {
nY = 1;
}
hasclicked = true;
eventType = AnnotatorEngine::Press;
}
if (hasclicked && isInside) {
ret = m_drawingEngine->event(eventType, button, modifiers, nX, nY, geom.width(), geom.height(), page);
}
if (eventType == AnnotatorEngine::Release) {
hasclicked = false;
}
if (m_drawingEngine->creationCompleted()) {
// add drawing to current page
m_frames[m_frameIndex]->drawings.emplace_back(m_drawingEngine->endSmoothPath());
// remove the actual drawer and create a new one just after
// that - that gives continuous drawing
delete m_drawingEngine;
m_drawingRect = QRect();
m_drawingEngine = new SmoothPathEngine(m_currentDrawingToolElement);
// schedule repaint
update();
}
return ret;
}
void PresentationWidget::startAutoChangeTimer()
{
double pageDuration = m_frameIndex >= 0 && m_frameIndex < (int)m_frames.count() ? m_frames[m_frameIndex]->page->duration() : -1;
if (m_advanceSlides || pageDuration >= 0.0) {
double secs;
if (pageDuration < 0.0) {
secs = Okular::SettingsCore::slidesAdvanceTime();
} else if (m_advanceSlides) {
secs = qMin<double>(pageDuration, Okular::SettingsCore::slidesAdvanceTime());
} else {
secs = pageDuration;
}
m_nextPageTimer->start((int)(secs * 1000));
}
setPlayPauseIcon();
}
QScreen *PresentationWidget::defaultScreen() const
{
const int preferenceScreen = Okular::Settings::slidesScreen();
if (preferenceScreen == -2) {
return oldQt_screenOf(m_parentWidget);
} else if (preferenceScreen == -1) {
return QApplication::primaryScreen();
} else if (preferenceScreen >= 0 && preferenceScreen < QApplication::screens().count()) {
return QApplication::screens().at(preferenceScreen);
} else {
return oldQt_screenOf(m_parentWidget);
}
}
void PresentationWidget::requestPixmaps()
{
const qreal dpr = devicePixelRatioF();
PresentationFrame *frame = m_frames[m_frameIndex];
int pixW = frame->geometry.width();
int pixH = frame->geometry.height();
// operation will take long: set busy cursor
QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
// request the pixmap
QList<Okular::PixmapRequest *> requests;
requests.push_back(new Okular::PixmapRequest(this, m_frameIndex, pixW, pixH, dpr, PRESENTATION_PRIO, Okular::PixmapRequest::NoFeature));
// restore cursor
QApplication::restoreOverrideCursor();
// ask for next and previous page if not in low memory usage setting
if (Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low) {
int pagesToPreload = 1;
// If greedy, preload everything
if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) {
pagesToPreload = (int)m_document->pages();
}
Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload;
requestFeatures |= Okular::PixmapRequest::Asynchronous;
for (int j = 1; j <= pagesToPreload; j++) {
int tailRequest = m_frameIndex + j;
if (tailRequest < (int)m_document->pages()) {
PresentationFrame *nextFrame = m_frames[tailRequest];
pixW = nextFrame->geometry.width();
pixH = nextFrame->geometry.height();
if (!nextFrame->page->hasPixmap(this, pixW, pixH)) {
requests.push_back(new Okular::PixmapRequest(this, tailRequest, pixW, pixH, dpr, PRESENTATION_PRELOAD_PRIO, requestFeatures));
}
}
int headRequest = m_frameIndex - j;
if (headRequest >= 0) {
PresentationFrame *prevFrame = m_frames[headRequest];
pixW = prevFrame->geometry.width();
pixH = prevFrame->geometry.height();
if (!prevFrame->page->hasPixmap(this, pixW, pixH)) {
requests.push_back(new Okular::PixmapRequest(this, headRequest, pixW, pixH, dpr, PRESENTATION_PRELOAD_PRIO, requestFeatures));
}
}
// stop if we've already reached both ends of the document
if (headRequest < 0 && tailRequest >= (int)m_document->pages()) {
break;
}
}
}
m_document->requestPixmaps(requests);
}
void PresentationWidget::slotNextPage()
{
int nextIndex = m_frameIndex + 1;
// loop when configured
if (nextIndex == m_frames.count() && Okular::Settings::slidesLoop()) {
nextIndex = 0;
}
if (nextIndex < m_frames.count()) {
// go to next page
changePage(nextIndex);
// auto advance to the next page if set
startAutoChangeTimer();
} else {
#ifdef ENABLE_PROGRESS_OVERLAY
if (Okular::Settings::slidesShowProgress()) {
generateOverlay();
}
#endif
if (m_transitionTimer->isActive()) {
m_transitionTimer->stop();
m_lastRenderedPixmap = m_currentPagePixmap;
update();
}
}
// we need the setFocus() call here to let KCursor::autoHide() work correctly
setFocus();
}
void PresentationWidget::slotPrevPage()
{
if (m_frameIndex > 0) {
// go to previous page
changePage(m_frameIndex - 1);
// auto advance to the next page if set
startAutoChangeTimer();
} else {
#ifdef ENABLE_PROGRESS_OVERLAY
if (Okular::Settings::slidesShowProgress()) {
generateOverlay();
}
#endif
if (m_transitionTimer->isActive()) {
m_transitionTimer->stop();
m_lastRenderedPixmap = m_currentPagePixmap;
update();
}
}
}
void PresentationWidget::slotFirstPage()
{
changePage(0);
}
void PresentationWidget::slotLastPage()
{
changePage((int)m_frames.count() - 1);
}
void PresentationWidget::slotHideOverlay()
{
QRect geom(m_overlayGeometry);
m_overlayGeometry.setCoords(0, 0, -1, -1);
update(geom);
}
void PresentationWidget::slotTransitionStep()
{
switch (m_currentTransition.type()) {
case Okular::PageTransition::Fade: {
QPainter pixmapPainter;
m_currentPixmapOpacity += 1.0 / m_transitionSteps;
m_lastRenderedPixmap = QPixmap(m_lastRenderedPixmap.size());
m_lastRenderedPixmap.setDevicePixelRatio(devicePixelRatioF());
m_lastRenderedPixmap.fill(Qt::transparent);
pixmapPainter.begin(&m_lastRenderedPixmap);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.setOpacity(1 - m_currentPixmapOpacity);
pixmapPainter.drawPixmap(0, 0, m_previousPagePixmap);
pixmapPainter.setOpacity(m_currentPixmapOpacity);
pixmapPainter.drawPixmap(0, 0, m_currentPagePixmap);
update();
if (m_currentPixmapOpacity >= 1) {
return;
}
} break;
default: {
if (m_transitionRects.empty()) {
// it's better to fix the transition to cover the whole screen than
// enabling the following line that wastes cpu for nothing
// update();
return;
}
for (int i = 0; i < m_transitionMul && !m_transitionRects.empty(); i++) {
update(m_transitionRects.first());
m_transitionRects.pop_front();
}
} break;
}
m_transitionTimer->start(m_transitionDelay);
}
void PresentationWidget::slotDelayedEvents()
{
setScreen(defaultScreen());
show();
if (m_screenSelect) {
m_screenSelect->setCurrentItem(QApplication::screens().indexOf(oldQt_screenOf(this)));
connect(m_screenSelect->selectableActionGroup(), &QActionGroup::triggered, this, &PresentationWidget::chooseScreen);
}
// inform user on how to exit from presentation mode
KMessageBox::information(
this,
i18n("There are two ways of exiting presentation mode, you can press either ESC key or click with the quit button that appears when placing the mouse in the top-right corner. Of course you can cycle windows (Alt+TAB by default)"),
QString(),
QStringLiteral("presentationInfo"));
}
void PresentationWidget::slotPageChanged()
{
bool ok = true;
int p = m_pagesEdit->text().toInt(&ok);
if (!ok) {
return;
}
changePage(p - 1);
}
void PresentationWidget::slotChangeDrawingToolEngine(const QDomElement &element)
{
if (element.isNull()) {
delete m_drawingEngine;
m_drawingEngine = nullptr;
m_drawingRect = QRect();
setCursor(Qt::ArrowCursor);
} else {
m_drawingEngine = new SmoothPathEngine(element);
setCursor(QCursor(QPixmap(QStringLiteral("pencil")), Qt::ArrowCursor));
m_currentDrawingToolElement = element;
}
}
void PresentationWidget::slotAddDrawingToolActions()
{
DrawingToolActions *drawingToolActions = qobject_cast<DrawingToolActions *>(sender());
const QList<QAction *> actionsList = drawingToolActions->actions();
for (QAction *action : actionsList) {
action->setEnabled(true);
m_topBar->addAction(action);
addAction(action);
}
}
void PresentationWidget::clearDrawings()
{
if (m_frameIndex != -1) {
m_frames[m_frameIndex]->drawings.clear();
}
update();
}
void PresentationWidget::chooseScreen(QAction *act)
{
if (!act || act->data().type() != QVariant::Int) {
return;
}
const int newScreen = act->data().toInt();
if (newScreen < QApplication::screens().count()) {
setScreen(QApplication::screens().at(newScreen));
}
}
void PresentationWidget::toggleBlackScreenMode(bool)
{
m_inBlackScreenMode = !m_inBlackScreenMode;
update();
}
void PresentationWidget::setScreen(const QScreen *newScreen)
{
// To move to a new screen, need to disable fullscreen first:
if (newScreen != screen()) {
setWindowState(windowState() & ~Qt::WindowFullScreen);
}
setGeometry(newScreen->geometry());
setWindowState(windowState() | Qt::WindowFullScreen);
}
void PresentationWidget::inhibitPowerManagement()
{
#if HAVE_DBUS
#ifdef Q_OS_LINUX
QString reason = i18nc("Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a presentation");
if (!m_screenInhibitCookie) {
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("Inhibit"));
message << QCoreApplication::applicationName();
message << reason;
QDBusPendingReply<uint> reply = QDBusConnection::sessionBus().asyncCall(message);
reply.waitForFinished();
if (reply.isValid()) {
m_screenInhibitCookie = reply.value();
qCDebug(OkularUiDebug) << "Screen inhibition cookie" << m_screenInhibitCookie;
} else {
qCWarning(OkularUiDebug) << "Unable to inhibit screensaver" << reply.error();
}
}
if (m_sleepInhibitFd != -1) {
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("Inhibit"));
message << QStringLiteral("sleep");
message << QCoreApplication::applicationName();
message << reason;
message << QStringLiteral("block");
QDBusPendingReply<QDBusUnixFileDescriptor> reply = QDBusConnection::systemBus().asyncCall(message);
reply.waitForFinished();
if (reply.isValid()) {
m_sleepInhibitFd = dup(reply.value().fileDescriptor());
} else {
qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error();
}
}
#endif // Q_OS_LINUX
#endif // HAVE_DBUS
}
void PresentationWidget::allowPowerManagement()
{
#if HAVE_DBUS
#ifdef Q_OS_LINUX
if (m_sleepInhibitFd != -1) {
::close(m_sleepInhibitFd);
m_sleepInhibitFd = -1;
}
if (m_screenInhibitCookie) {
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("UnInhibit"));
message << m_screenInhibitCookie;
QDBusPendingReply<uint> reply = QDBusConnection::sessionBus().asyncCall(message);
reply.waitForFinished();
m_screenInhibitCookie = 0;
}
#endif // Q_OS_LINUX
#endif // HAVE_DBUS
}
void PresentationWidget::showTopBar(bool show)
{
if (show) {
m_topBar->show();
// Don't autohide the mouse cursor if it's over the toolbar
if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
KCursor::setAutoHideCursor(this, false);
}
// Always show a cursor when topBar is visible
if (!m_drawingEngine) {
setCursor(QCursor(Qt::ArrowCursor));
}
} else {
m_topBar->hide();
// Reenable autohide if need be when leaving the toolbar
if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) {
KCursor::setAutoHideCursor(this, true);
}
// Or hide the cursor again if hidden cursor is enabled
else if (Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) {
// Don't hide the cursor if drawing mode is on
if (!m_drawingEngine) {
setCursor(QCursor(Qt::BlankCursor));
}
}
}
// Make sure mouse tracking isn't off after the KCursor::setAutoHideCursor() calls
setMouseTracking(true);
}
void PresentationWidget::slotFind()
{
if (!m_searchBar) {
m_searchBar = new PresentationSearchBar(m_document, this, this);
m_searchBar->forceSnap();
}
m_searchBar->focusOnSearchEdit();
m_searchBar->show();
}
const Okular::PageTransition PresentationWidget::defaultTransition() const
{
return defaultTransition(Okular::Settings::slidesTransition());
}
const Okular::PageTransition PresentationWidget::defaultTransition(int type) const
{
switch (type) {
case Okular::Settings::EnumSlidesTransition::BlindsHorizontal: {
Okular::PageTransition transition(Okular::PageTransition::Blinds);
transition.setAlignment(Okular::PageTransition::Horizontal);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::BlindsVertical: {
Okular::PageTransition transition(Okular::PageTransition::Blinds);
transition.setAlignment(Okular::PageTransition::Vertical);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::BoxIn: {
Okular::PageTransition transition(Okular::PageTransition::Box);
transition.setDirection(Okular::PageTransition::Inward);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::BoxOut: {
Okular::PageTransition transition(Okular::PageTransition::Box);
transition.setDirection(Okular::PageTransition::Outward);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::Dissolve: {
return Okular::PageTransition(Okular::PageTransition::Dissolve);
break;
}
case Okular::Settings::EnumSlidesTransition::GlitterDown: {
Okular::PageTransition transition(Okular::PageTransition::Glitter);
transition.setAngle(270);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::GlitterRight: {
Okular::PageTransition transition(Okular::PageTransition::Glitter);
transition.setAngle(0);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::GlitterRightDown: {
Okular::PageTransition transition(Okular::PageTransition::Glitter);
transition.setAngle(315);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::Random: {
return defaultTransition(QRandomGenerator::global()->bounded(18));
break;
}
case Okular::Settings::EnumSlidesTransition::SplitHorizontalIn: {
Okular::PageTransition transition(Okular::PageTransition::Split);
transition.setAlignment(Okular::PageTransition::Horizontal);
transition.setDirection(Okular::PageTransition::Inward);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::SplitHorizontalOut: {
Okular::PageTransition transition(Okular::PageTransition::Split);
transition.setAlignment(Okular::PageTransition::Horizontal);
transition.setDirection(Okular::PageTransition::Outward);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::SplitVerticalIn: {
Okular::PageTransition transition(Okular::PageTransition::Split);
transition.setAlignment(Okular::PageTransition::Vertical);
transition.setDirection(Okular::PageTransition::Inward);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::SplitVerticalOut: {
Okular::PageTransition transition(Okular::PageTransition::Split);
transition.setAlignment(Okular::PageTransition::Vertical);
transition.setDirection(Okular::PageTransition::Outward);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::WipeDown: {
Okular::PageTransition transition(Okular::PageTransition::Wipe);
transition.setAngle(270);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::WipeRight: {
Okular::PageTransition transition(Okular::PageTransition::Wipe);
transition.setAngle(0);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::WipeLeft: {
Okular::PageTransition transition(Okular::PageTransition::Wipe);
transition.setAngle(180);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::WipeUp: {
Okular::PageTransition transition(Okular::PageTransition::Wipe);
transition.setAngle(90);
return transition;
break;
}
case Okular::Settings::EnumSlidesTransition::Fade: {
return Okular::PageTransition(Okular::PageTransition::Fade);
break;
}
case Okular::Settings::EnumSlidesTransition::NoTransitions:
case Okular::Settings::EnumSlidesTransition::Replace:
default:
return Okular::PageTransition(Okular::PageTransition::Replace);
break;
}
// should not happen, just make gcc happy
return Okular::PageTransition();
}
/** ONLY the TRANSITIONS GENERATION function from here on **/
void PresentationWidget::initTransition(const Okular::PageTransition *transition)
{
// if it's just a 'replace' transition, repaint the screen
if (transition->type() == Okular::PageTransition::Replace) {
update();
return;
}
const bool isInward = transition->direction() == Okular::PageTransition::Inward;
const bool isHorizontal = transition->alignment() == Okular::PageTransition::Horizontal;
const float totalTime = transition->duration();
m_transitionRects.clear();
m_currentTransition = *transition;
m_currentPagePixmap = m_lastRenderedPixmap;
switch (transition->type()) {
// split: horizontal / vertical and inward / outward
case Okular::PageTransition::Split: {
const int steps = isHorizontal ? 100 : 75;
if (isHorizontal) {
if (isInward) {
int xPosition = 0;
for (int i = 0; i < steps; i++) {
int xNext = ((i + 1) * m_width) / (2 * steps);
m_transitionRects.push_back(QRect(xPosition, 0, xNext - xPosition, m_height));
m_transitionRects.push_back(QRect(m_width - xNext, 0, xNext - xPosition, m_height));
xPosition = xNext;
}
} else {
int xPosition = m_width / 2;
for (int i = 0; i < steps; i++) {
int xNext = ((steps - (i + 1)) * m_width) / (2 * steps);
m_transitionRects.push_back(QRect(xNext, 0, xPosition - xNext, m_height));
m_transitionRects.push_back(QRect(m_width - xPosition, 0, xPosition - xNext, m_height));
xPosition = xNext;
}
}
} else {
if (isInward) {
int yPosition = 0;
for (int i = 0; i < steps; i++) {
int yNext = ((i + 1) * m_height) / (2 * steps);
m_transitionRects.push_back(QRect(0, yPosition, m_width, yNext - yPosition));
m_transitionRects.push_back(QRect(0, m_height - yNext, m_width, yNext - yPosition));
yPosition = yNext;
}
} else {
int yPosition = m_height / 2;
for (int i = 0; i < steps; i++) {
int yNext = ((steps - (i + 1)) * m_height) / (2 * steps);
m_transitionRects.push_back(QRect(0, yNext, m_width, yPosition - yNext));
m_transitionRects.push_back(QRect(0, m_height - yPosition, m_width, yPosition - yNext));
yPosition = yNext;
}
}
}
m_transitionMul = 2;
m_transitionDelay = (int)((totalTime * 1000) / steps);
} break;
// blinds: horizontal(l-to-r) / vertical(t-to-b)
case Okular::PageTransition::Blinds: {
const int blinds = isHorizontal ? 8 : 6;
const int steps = m_width / (4 * blinds);
if (isHorizontal) {
int xPosition[8];
for (int b = 0; b < blinds; b++) {
xPosition[b] = (b * m_width) / blinds;
}
for (int i = 0; i < steps; i++) {
int stepOffset = (int)(((float)i * (float)m_width) / ((float)blinds * (float)steps));
for (int b = 0; b < blinds; b++) {
m_transitionRects.push_back(QRect(xPosition[b], 0, stepOffset, m_height));
xPosition[b] = stepOffset + (b * m_width) / blinds;
}
}
} else {
int yPosition[6];
for (int b = 0; b < blinds; b++) {
yPosition[b] = (b * m_height) / blinds;
}
for (int i = 0; i < steps; i++) {
int stepOffset = (int)(((float)i * (float)m_height) / ((float)blinds * (float)steps));
for (int b = 0; b < blinds; b++) {
m_transitionRects.push_back(QRect(0, yPosition[b], m_width, stepOffset));
yPosition[b] = stepOffset + (b * m_height) / blinds;
}
}
}
m_transitionMul = blinds;
m_transitionDelay = (int)((totalTime * 1000) / steps);
} break;
// box: inward / outward
case Okular::PageTransition::Box: {
const int steps = m_width / 10;
if (isInward) {
int L = 0, T = 0, R = m_width, B = m_height;
for (int i = 0; i < steps; i++) {
// compute shrunk box coords
int newL = ((i + 1) * m_width) / (2 * steps);
int newT = ((i + 1) * m_height) / (2 * steps);
int newR = m_width - newL;
int newB = m_height - newT;
// add left, right, topcenter, bottomcenter rects
m_transitionRects.push_back(QRect(L, T, newL - L, B - T));
m_transitionRects.push_back(QRect(newR, T, R - newR, B - T));
m_transitionRects.push_back(QRect(newL, T, newR - newL, newT - T));
m_transitionRects.push_back(QRect(newL, newB, newR - newL, B - newB));
L = newL;
T = newT;
R = newR, B = newB;
}
} else {
int L = m_width / 2, T = m_height / 2, R = L, B = T;
for (int i = 0; i < steps; i++) {
// compute shrunk box coords
int newL = ((steps - (i + 1)) * m_width) / (2 * steps);
int newT = ((steps - (i + 1)) * m_height) / (2 * steps);
int newR = m_width - newL;
int newB = m_height - newT;
// add left, right, topcenter, bottomcenter rects
m_transitionRects.push_back(QRect(newL, newT, L - newL, newB - newT));
m_transitionRects.push_back(QRect(R, newT, newR - R, newB - newT));
m_transitionRects.push_back(QRect(L, newT, R - L, T - newT));
m_transitionRects.push_back(QRect(L, B, R - L, newB - B));
L = newL;
T = newT;
R = newR, B = newB;
}
}
m_transitionMul = 4;
m_transitionDelay = (int)((totalTime * 1000) / steps);
} break;
// wipe: implemented for 4 canonical angles
case Okular::PageTransition::Wipe: {
const int angle = transition->angle();
const int steps = (angle == 0) || (angle == 180) ? m_width / 8 : m_height / 8;
if (angle == 0) {
int xPosition = 0;
for (int i = 0; i < steps; i++) {
int xNext = ((i + 1) * m_width) / steps;
m_transitionRects.push_back(QRect(xPosition, 0, xNext - xPosition, m_height));
xPosition = xNext;
}
} else if (angle == 90) {
int yPosition = m_height;
for (int i = 0; i < steps; i++) {
int yNext = ((steps - (i + 1)) * m_height) / steps;
m_transitionRects.push_back(QRect(0, yNext, m_width, yPosition - yNext));
yPosition = yNext;
}
} else if (angle == 180) {
int xPosition = m_width;
for (int i = 0; i < steps; i++) {
int xNext = ((steps - (i + 1)) * m_width) / steps;
m_transitionRects.push_back(QRect(xNext, 0, xPosition - xNext, m_height));
xPosition = xNext;
}
} else if (angle == 270) {
int yPosition = 0;
for (int i = 0; i < steps; i++) {
int yNext = ((i + 1) * m_height) / steps;
m_transitionRects.push_back(QRect(0, yPosition, m_width, yNext - yPosition));
yPosition = yNext;
}
} else {
update();
return;
}
m_transitionMul = 1;
m_transitionDelay = (int)((totalTime * 1000) / steps);
} break;
// dissolve: replace 'random' rects
case Okular::PageTransition::Dissolve: {
const int gridXsteps = 50;
const int gridYsteps = 38;
const int steps = gridXsteps * gridYsteps;
int oldX = 0;
int oldY = 0;
// create a grid of gridXstep by gridYstep QRects
for (int y = 0; y < gridYsteps; y++) {
int newY = (int)(m_height * ((float)(y + 1) / (float)gridYsteps));
for (int x = 0; x < gridXsteps; x++) {
int newX = (int)(m_width * ((float)(x + 1) / (float)gridXsteps));
m_transitionRects.push_back(QRect(oldX, oldY, newX - oldX, newY - oldY));
oldX = newX;
}
oldX = 0;
oldY = newY;
}
// randomize the grid
for (int i = 0; i < steps; i++) {
#ifndef Q_OS_WIN
int n1 = (int)(steps * drand48());
int n2 = (int)(steps * drand48());
#else
int n1 = (int)(steps * (std::rand() / RAND_MAX));
int n2 = (int)(steps * (std::rand() / RAND_MAX));
#endif
// swap items if index differs
if (n1 != n2) {
QRect r = m_transitionRects[n2];
m_transitionRects[n2] = m_transitionRects[n1];
m_transitionRects[n1] = r;
}
}
// set global transition parameters
m_transitionMul = 40;
m_transitionDelay = (int)((m_transitionMul * 1000 * totalTime) / steps);
} break;
// glitter: similar to dissolve but has a direction
case Okular::PageTransition::Glitter: {
const int gridXsteps = 50;
const int gridYsteps = 38;
const int steps = gridXsteps * gridYsteps;
const int angle = transition->angle();
// generate boxes using a given direction
if (angle == 90) {
int yPosition = m_height;
for (int i = 0; i < gridYsteps; i++) {
int yNext = ((gridYsteps - (i + 1)) * m_height) / gridYsteps;
int xPosition = 0;
for (int j = 0; j < gridXsteps; j++) {
int xNext = ((j + 1) * m_width) / gridXsteps;
m_transitionRects.push_back(QRect(xPosition, yNext, xNext - xPosition, yPosition - yNext));
xPosition = xNext;
}
yPosition = yNext;
}
} else if (angle == 180) {
int xPosition = m_width;
for (int i = 0; i < gridXsteps; i++) {
int xNext = ((gridXsteps - (i + 1)) * m_width) / gridXsteps;
int yPosition = 0;
for (int j = 0; j < gridYsteps; j++) {
int yNext = ((j + 1) * m_height) / gridYsteps;
m_transitionRects.push_back(QRect(xNext, yPosition, xPosition - xNext, yNext - yPosition));
yPosition = yNext;
}
xPosition = xNext;
}
} else if (angle == 270) {
int yPosition = 0;
for (int i = 0; i < gridYsteps; i++) {
int yNext = ((i + 1) * m_height) / gridYsteps;
int xPosition = 0;
for (int j = 0; j < gridXsteps; j++) {
int xNext = ((j + 1) * m_width) / gridXsteps;
m_transitionRects.push_back(QRect(xPosition, yPosition, xNext - xPosition, yNext - yPosition));
xPosition = xNext;
}
yPosition = yNext;
}
} else // if angle is 0 or 315
{
int xPosition = 0;
for (int i = 0; i < gridXsteps; i++) {
int xNext = ((i + 1) * m_width) / gridXsteps;
int yPosition = 0;
for (int j = 0; j < gridYsteps; j++) {
int yNext = ((j + 1) * m_height) / gridYsteps;
m_transitionRects.push_back(QRect(xPosition, yPosition, xNext - xPosition, yNext - yPosition));
yPosition = yNext;
}
xPosition = xNext;
}
}
// add a 'glitter' (1 over 10 pieces is randomized)
int randomSteps = steps / 20;
for (int i = 0; i < randomSteps; i++) {
#ifndef Q_OS_WIN
int n1 = (int)(steps * drand48());
int n2 = (int)(steps * drand48());
#else
int n1 = (int)(steps * (std::rand() / RAND_MAX));
int n2 = (int)(steps * (std::rand() / RAND_MAX));
#endif
// swap items if index differs
if (n1 != n2) {
QRect r = m_transitionRects[n2];
m_transitionRects[n2] = m_transitionRects[n1];
m_transitionRects[n1] = r;
}
}
// set global transition parameters
m_transitionMul = (angle == 90) || (angle == 270) ? gridYsteps : gridXsteps;
m_transitionMul /= 2;
m_transitionDelay = (int)((m_transitionMul * 1000 * totalTime) / steps);
} break;
case Okular::PageTransition::Fade: {
enum { FADE_TRANSITION_FPS = 20 };
const int steps = totalTime * FADE_TRANSITION_FPS;
m_transitionSteps = steps;
QPainter pixmapPainter;
m_currentPixmapOpacity = (double)1 / steps;
m_transitionDelay = (int)(totalTime * 1000) / steps;
m_lastRenderedPixmap = QPixmap(m_lastRenderedPixmap.size());
m_lastRenderedPixmap.fill(Qt::transparent);
pixmapPainter.begin(&m_lastRenderedPixmap);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.setOpacity(1 - m_currentPixmapOpacity);
pixmapPainter.drawPixmap(0, 0, m_previousPagePixmap);
pixmapPainter.setOpacity(m_currentPixmapOpacity);
pixmapPainter.drawPixmap(0, 0, m_currentPagePixmap);
pixmapPainter.end();
update();
} break;
// implement missing transitions (a binary raster engine needed here)
case Okular::PageTransition::Fly:
case Okular::PageTransition::Push:
case Okular::PageTransition::Cover:
case Okular::PageTransition::Uncover:
default:
update();
return;
}
// send the first start to the timer
m_transitionTimer->start(0);
}
void PresentationWidget::slotProcessMovieAction(const Okular::MovieAction *action)
{
const Okular::MovieAnnotation *movieAnnotation = action->annotation();
if (!movieAnnotation) {
return;
}
Okular::Movie *movie = movieAnnotation->movie();
if (!movie) {
return;
}
VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movieAnnotation->movie());
if (!vw) {
return;
}
vw->show();
switch (action->operation()) {
case Okular::MovieAction::Play:
vw->stop();
vw->play();
break;
case Okular::MovieAction::Stop:
vw->stop();
break;
case Okular::MovieAction::Pause:
vw->pause();
break;
case Okular::MovieAction::Resume:
vw->play();
break;
};
}
void PresentationWidget::slotProcessRenditionAction(const Okular::RenditionAction *action)
{
Okular::Movie *movie = action->movie();
if (!movie) {
return;
}
VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movie);
if (!vw) {
return;
}
if (action->operation() == Okular::RenditionAction::None) {
return;
}
vw->show();
switch (action->operation()) {
case Okular::RenditionAction::Play:
vw->stop();
vw->play();
break;
case Okular::RenditionAction::Stop:
vw->stop();
break;
case Okular::RenditionAction::Pause:
vw->pause();
break;
case Okular::RenditionAction::Resume:
vw->play();
break;
default:
return;
};
}
void PresentationWidget::slotTogglePlayPause()
{
if (!m_nextPageTimer->isActive()) {
m_advanceSlides = true;
startAutoChangeTimer();
} else {
m_nextPageTimer->stop();
m_advanceSlides = false;
setPlayPauseIcon();
}
}
#include "presentationwidget.moc"