presentationwidget.cpp 82.6 KB
Newer Older
1
2
3
4
5
/*
    SPDX-FileCopyrightText: 2004 Enrico Ros <eros.kde@email.it>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
6

Albert Astals Cid's avatar
Albert Astals Cid committed
7
8
#include "presentationwidget.h"

9
// qt/kde includes
Yuri Chornoivan's avatar
Yuri Chornoivan committed
10
11
12
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusReply>
Albert Astals Cid's avatar
Albert Astals Cid committed
13
#include <QLoggingCategory>
14

Albert Astals Cid's avatar
Albert Astals Cid committed
15
16
17
18
19
20
21
22
23
24
#include <KActionCollection>
#include <KCursor>
#include <KLineEdit>
#include <KLocalizedString>
#include <KMessageBox>
#include <KRandom>
#include <KSelectAction>
#include <QAction>
#include <QApplication>
#include <QDialog>
25
26
#include <QEvent>
#include <QFontMetrics>
Albert Astals Cid's avatar
Albert Astals Cid committed
27
#include <QGestureEvent>
Laurent Montel's avatar
Laurent Montel committed
28
#include <QIcon>
29
30
31
32
#include <QImage>
#include <QLabel>
#include <QLayout>
#include <QPainter>
33
#include <QScreen>
34
35
#include <QStyle>
#include <QStyleOption>
Albert Astals Cid's avatar
Albert Astals Cid committed
36
37
#include <QTimer>
#include <QToolBar>
38
39
#include <QToolTip>
#include <QValidator>
40

41
42
43
44
#ifdef Q_OS_LINUX
#include <QDBusUnixFileDescriptor>
#include <unistd.h> // For ::close() for sleep inhibition
#endif
45

46
// system includes
47
#include <math.h>
Albert Astals Cid's avatar
Albert Astals Cid committed
48
#include <stdlib.h>
49
50

// local includes
51
#include "annotationtools.h"
Pino Toscano's avatar
Pino Toscano committed
52
#include "core/action.h"
53
#include "core/annotations.h"
54
#include "core/audioplayer.h"
55
#include "core/document.h"
56
#include "core/generator.h"
57
#include "core/movie.h"
58
#include "core/page.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
59
60
61
62
63
64
#include "debug_ui.h"
#include "drawingtoolactions.h"
#include "guiutils.h"
#include "pagepainter.h"
#include "presentationsearchbar.h"
#include "priorities.h"
65
#include "settings.h"
66
#include "settings_core.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
67
#include "videowidget.h"
68
69
70
71
72
73

// 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
Albert Astals Cid's avatar
Albert Astals Cid committed
74
struct PresentationFrame {
75
76
    PresentationFrame() = default;

77
78
    ~PresentationFrame()
    {
Albert Astals Cid's avatar
Albert Astals Cid committed
79
        qDeleteAll(videoWidgets);
80
81
    }

82
83
84
    PresentationFrame(const PresentationFrame &) = delete;
    PresentationFrame &operator=(const PresentationFrame &) = delete;

Albert Astals Cid's avatar
Albert Astals Cid committed
85
    void recalcGeometry(int width, int height, float screenRatio)
86
87
88
    {
        // calculate frame geometry keeping constant aspect ratio
        float pageRatio = page->ratio();
Albert Astals Cid's avatar
Albert Astals Cid committed
89
90
91
        int pageWidth = width, pageHeight = height;
        if (pageRatio > screenRatio)
            pageWidth = (int)((float)pageHeight / pageRatio);
92
        else
Albert Astals Cid's avatar
Albert Astals Cid committed
93
94
            pageHeight = (int)((float)pageWidth * pageRatio);
        geometry.setRect((width - pageWidth) / 2, (height - pageHeight) / 2, pageWidth, pageHeight);
95

Albert Astals Cid's avatar
Albert Astals Cid committed
96
        for (VideoWidget *vw : qAsConst(videoWidgets)) {
97
            const Okular::NormalizedRect r = vw->normGeometry();
Albert Astals Cid's avatar
Albert Astals Cid committed
98
99
100
            QRect vwgeom = r.geometry(geometry.width(), geometry.height());
            vw->resize(vwgeom.size());
            vw->move(geometry.topLeft() + vwgeom.topLeft());
101
        }
102
103
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
104
    const Okular::Page *page;
105
    QRect geometry;
Albert Astals Cid's avatar
Albert Astals Cid committed
106
107
    QHash<Okular::Movie *, VideoWidget *> videoWidgets;
    QLinkedList<SmoothPath> drawings;
108
109
};

110
111
112
113
// a custom QToolBar that basically does not propagate the event if the widget
// background is not automatically filled
class PresentationToolBar : public QToolBar
{
Albert Vaca Cintora's avatar
Albert Vaca Cintora committed
114
115
    Q_OBJECT

Albert Astals Cid's avatar
Albert Astals Cid committed
116
public:
117
    explicit PresentationToolBar(QWidget *parent = Q_NULLPTR)
Albert Astals Cid's avatar
Albert Astals Cid committed
118
119
120
        : QToolBar(parent)
    {
    }
121

Albert Astals Cid's avatar
Albert Astals Cid committed
122
123
124
125
126
127
protected:
    void mousePressEvent(QMouseEvent *e) override
    {
        QToolBar::mousePressEvent(e);
        e->accept();
    }
128

Albert Astals Cid's avatar
Albert Astals Cid committed
129
130
131
132
133
    void mouseReleaseEvent(QMouseEvent *e) override
    {
        QToolBar::mouseReleaseEvent(e);
        e->accept();
    }
134
135
};

Albert Astals Cid's avatar
Albert Astals Cid committed
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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)
158
{
Albert Astals Cid's avatar
Albert Astals Cid committed
159
160
161
162
163
    setAttribute(Qt::WA_DeleteOnClose);
    setAttribute(Qt::WA_OpaquePaintEvent);
    setObjectName(QStringLiteral("presentationWidget"));
    QString caption = doc->metaData(QStringLiteral("DocumentTitle")).toString();
    if (caption.trimmed().isEmpty())
164
        caption = doc->currentDocument().fileName();
Albert Astals Cid's avatar
Albert Astals Cid committed
165
166
    caption = i18nc("[document title/filename] – Presentation", "%1 – Presentation", caption);
    setWindowTitle(caption);
167
168

    m_width = -1;
169
170

    // create top toolbar
Albert Astals Cid's avatar
Albert Astals Cid committed
171
172
173
    m_topBar = new PresentationToolBar(this);
    m_topBar->setObjectName(QStringLiteral("presentationBar"));
    m_topBar->setMovable(false);
Laurent Montel's avatar
Laurent Montel committed
174
    m_topBar->layout()->setContentsMargins(0, 0, 0, 0);
Albert Astals Cid's avatar
Albert Astals Cid committed
175
176
    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);
177
    QSizePolicy sp = m_pagesEdit->sizePolicy();
Albert Astals Cid's avatar
Albert Astals Cid committed
178
179
180
    sp.setHorizontalPolicy(QSizePolicy::Minimum);
    m_pagesEdit->setSizePolicy(sp);
    QFontMetrics fm(m_pagesEdit->font());
181
    QStyleOptionFrame option;
Albert Astals Cid's avatar
Albert Astals Cid committed
182
    option.initFrom(m_pagesEdit);
183
    m_pagesEdit->setMaximumWidth(fm.horizontalAdvance(QString::number(m_document->pages())) + 2 * style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, m_pagesEdit) +
Albert Astals Cid's avatar
Albert Astals Cid committed
184
185
186
187
188
189
190
                                 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);
Laurent Montel's avatar
Laurent Montel committed
191
    connect(m_pagesEdit, &QLineEdit::returnPressed, this, &PresentationWidget::slotPageChanged);
Albert Astals Cid's avatar
Albert Astals Cid committed
192
    m_topBar->addAction(QIcon::fromTheme(layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-previous") : QStringLiteral("go-next")), i18n("Next Page"), this, SLOT(slotNextPage()));
193
    m_topBar->addSeparator();
Albert Astals Cid's avatar
Albert Astals Cid committed
194
195
    QAction *playPauseAct = collection->action(QStringLiteral("presentation_play_pause"));
    playPauseAct->setEnabled(true);
Laurent Montel's avatar
Laurent Montel committed
196
    connect(playPauseAct, &QAction::triggered, this, &PresentationWidget::slotTogglePlayPause);
Albert Astals Cid's avatar
Albert Astals Cid committed
197
198
    m_topBar->addAction(playPauseAct);
    addAction(playPauseAct);
199
    m_topBar->addSeparator();
200

201
    const QList<QAction *> actionsList = drawingToolActions->actions();
Albert Astals Cid's avatar
Albert Astals Cid committed
202
203
204
205
    for (QAction *action : actionsList) {
        action->setEnabled(true);
        m_topBar->addAction(action);
        addAction(action);
206
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
207
208
    connect(drawingToolActions, &DrawingToolActions::changeEngine, this, &PresentationWidget::slotChangeDrawingToolEngine);
    connect(drawingToolActions, &DrawingToolActions::actionsRecreated, this, &PresentationWidget::slotAddDrawingToolActions);
209

Albert Astals Cid's avatar
Albert Astals Cid committed
210
211
    QAction *eraseDrawingAct = collection->action(QStringLiteral("presentation_erase_drawings"));
    eraseDrawingAct->setEnabled(true);
212
    connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings);
Albert Astals Cid's avatar
Albert Astals Cid committed
213
214
    m_topBar->addAction(eraseDrawingAct);
    addAction(eraseDrawingAct);
215

216
217
    const int screenCount = QApplication::screens().count();
    if (screenCount > 1) {
218
        m_topBar->addSeparator();
Albert Astals Cid's avatar
Albert Astals Cid committed
219
220
221
222
223
224
225
        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));
226
227
        }
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
228
229
230
231
232
233
    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);
234
235
    // change topbar background color
    QPalette p = m_topBar->palette();
Albert Astals Cid's avatar
Albert Astals Cid committed
236
237
238
    p.setColor(QPalette::Active, QPalette::Button, Qt::gray);
    p.setColor(QPalette::Active, QPalette::Window, Qt::darkGray);
    m_topBar->setPalette(p);
239

240
241
242
    // Grab swipe gestures to change pages
    grabGesture(Qt::SwipeGesture);

243
    // misc stuff
Albert Astals Cid's avatar
Albert Astals Cid committed
244
245
246
247
    setMouseTracking(true);
    setContextMenuPolicy(Qt::PreventContextMenu);
    m_transitionTimer = new QTimer(this);
    m_transitionTimer->setSingleShot(true);
Laurent Montel's avatar
Laurent Montel committed
248
    connect(m_transitionTimer, &QTimer::timeout, this, &PresentationWidget::slotTransitionStep);
Albert Astals Cid's avatar
Albert Astals Cid committed
249
250
    m_overlayHideTimer = new QTimer(this);
    m_overlayHideTimer->setSingleShot(true);
Laurent Montel's avatar
Laurent Montel committed
251
    connect(m_overlayHideTimer, &QTimer::timeout, this, &PresentationWidget::slotHideOverlay);
Albert Astals Cid's avatar
Albert Astals Cid committed
252
253
    m_nextPageTimer = new QTimer(this);
    m_nextPageTimer->setSingleShot(true);
Laurent Montel's avatar
Laurent Montel committed
254
    connect(m_nextPageTimer, &QTimer::timeout, this, &PresentationWidget::slotNextPage);
255
    setPlayPauseIcon();
256

Laurent Montel's avatar
Laurent Montel committed
257
258
    connect(m_document, &Okular::Document::processMovieAction, this, &PresentationWidget::slotProcessMovieAction);
    connect(m_document, &Okular::Document::processRenditionAction, this, &PresentationWidget::slotProcessRenditionAction);
259

260
    // handle cursor appearance as specified in configuration
Albert Astals Cid's avatar
Albert Astals Cid committed
261
262
263
264
265
    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));
266
    }
267

268
    setupActions();
269

270
271
    // inhibit power management
    inhibitPowerManagement();
272

Albert Astals Cid's avatar
Albert Astals Cid committed
273
    QTimer::singleShot(0, this, &PresentationWidget::slotDelayedEvents);
274
275

    // setFocus() so KCursor::setAutoHideCursor() goes into effect if it's enabled
Albert Astals Cid's avatar
Albert Astals Cid committed
276
    setFocus(Qt::OtherFocusReason);
277
278

    // Catch TabletEnterProximity and TabletLeaveProximity events from the QApplication
Albert Astals Cid's avatar
Albert Astals Cid committed
279
    qApp->installEventFilter(this);
280
281
282
283
}

PresentationWidget::~PresentationWidget()
{
284
285
    // allow power management saver again
    allowPowerManagement();
286

287
288
289
    // stop the audio playbacks
    Okular::AudioPlayer::instance()->stopPlaybacks();

290
    // remove our highlights
Albert Astals Cid's avatar
Albert Astals Cid committed
291
292
    if (m_searchBar) {
        m_document->resetSearch(PRESENTATION_SEARCH_ID);
293
294
    }

295
    // remove this widget from document observer
Albert Astals Cid's avatar
Albert Astals Cid committed
296
    m_document->removeObserver(this);
297

298
    const QList<QAction *> actionsList = actions();
Albert Astals Cid's avatar
Albert Astals Cid committed
299
300
301
    for (QAction *action : actionsList) {
        action->setChecked(false);
        action->setEnabled(false);
302
    }
303

304
    delete m_drawingEngine;
305

306
    // delete frames
Albert Astals Cid's avatar
Albert Astals Cid committed
307
    qDeleteAll(m_frames);
308

Albert Astals Cid's avatar
Albert Astals Cid committed
309
    qApp->removeEventFilter(this);
310
311
}

Albert Astals Cid's avatar
Albert Astals Cid committed
312
void PresentationWidget::notifySetup(const QVector<Okular::Page *> &pageSet, int setupFlags)
313
{
314
315
    // same document, nothing to change - here we assume the document sets up
    // us with the whole document set as first notifySetup()
Albert Astals Cid's avatar
Albert Astals Cid committed
316
    if (!(setupFlags & Okular::DocumentObserver::DocumentChanged))
317
318
        return;

319
    // delete previous frames (if any (shouldn't be))
Albert Astals Cid's avatar
Albert Astals Cid committed
320
321
    qDeleteAll(m_frames);
    if (!m_frames.isEmpty())
322
        qCWarning(OkularUiDebug) << "Frames setup changed while a Presentation is in progress.";
323
324
325
326
    m_frames.clear();

    // create the new frames
    float screenRatio = (float)m_height / (float)m_width;
Albert Astals Cid's avatar
Albert Astals Cid committed
327
328
    for (const Okular::Page *page : pageSet) {
        PresentationFrame *frame = new PresentationFrame();
329
        frame->page = page;
Albert Astals Cid's avatar
Albert Astals Cid committed
330
331
332
333
334
335
        const QLinkedList<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);
336
                vw->pageInitialized();
Albert Astals Cid's avatar
Albert Astals Cid committed
337
338
339
340
341
            } 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);
342
343
                    vw->pageInitialized();
                }
Albert Astals Cid's avatar
Albert Astals Cid committed
344
345
346
347
348
349
            } 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);
350
351
352
                    vw->pageInitialized();
                }
            }
353
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
354
        frame->recalcGeometry(m_width, m_height, screenRatio);
355
        // add the frame to the vector
Albert Astals Cid's avatar
Albert Astals Cid committed
356
        m_frames.push_back(frame);
357
    }
358

359
360
    // get metadata from the document
    m_metaStrings.clear();
Albert Astals Cid's avatar
Albert Astals Cid committed
361
362
363
364
365
366
367
    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");
368
369

    m_isSetup = true;
370
371
}

Albert Astals Cid's avatar
Albert Astals Cid committed
372
void PresentationWidget::notifyViewportChanged(bool /*smoothMove*/)
373
{
374
    // display the current page
Albert Astals Cid's avatar
Albert Astals Cid committed
375
    changePage(m_document->viewport().pageNumber);
376
377

    // auto advance to the next page if set
378
    startAutoChangeTimer();
379
380
}

Albert Astals Cid's avatar
Albert Astals Cid committed
381
void PresentationWidget::notifyPageChanged(int pageNumber, int changedFlags)
382
{
383
    // if we are blocking the notifications, do nothing
Albert Astals Cid's avatar
Albert Astals Cid committed
384
    if (m_blockNotifications)
385
386
        return;

387
    // check if it's the last requested pixmap. if so update the widget.
Albert Astals Cid's avatar
Albert Astals Cid committed
388
389
    if ((changedFlags & (DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights)) && pageNumber == m_frameIndex)
        generatePage(changedFlags & (DocumentObserver::Annotations | DocumentObserver::Highlights));
390
391
}

Albert Astals Cid's avatar
Albert Astals Cid committed
392
void PresentationWidget::notifyCurrentPageChanged(int previousPage, int currentPage)
393
{
Albert Astals Cid's avatar
Albert Astals Cid committed
394
    if (previousPage != -1) {
395
        // stop video playback
Albert Astals Cid's avatar
Albert Astals Cid committed
396
        for (VideoWidget *vw : qAsConst(m_frames[previousPage]->videoWidgets)) {
397
398
399
400
401
402
403
404
            vw->stop();
            vw->pageLeft();
        }

        // stop audio playback, if any
        Okular::AudioPlayer::instance()->stopPlaybacks();

        // perform the page closing action, if any
Albert Astals Cid's avatar
Albert Astals Cid committed
405
406
        if (m_document->page(previousPage)->pageAction(Okular::Page::Closing))
            m_document->processAction(m_document->page(previousPage)->pageAction(Okular::Page::Closing));
407
408

        // perform the additional actions of the page's annotations, if any
Albert Astals Cid's avatar
Albert Astals Cid committed
409
410
        const QLinkedList<Okular::Annotation *> annotationsList = m_document->page(previousPage)->annotations();
        for (const Okular::Annotation *annotation : annotationsList) {
411
            Okular::Action *action = nullptr;
412

Albert Astals Cid's avatar
Albert Astals Cid committed
413
414
415
416
            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);
417

Albert Astals Cid's avatar
Albert Astals Cid committed
418
419
            if (action)
                m_document->processAction(action);
420
        }
421
422
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
423
    if (currentPage != -1) {
424
425
426
        m_frameIndex = currentPage;

        // check if pixmap exists or else request it
Albert Astals Cid's avatar
Albert Astals Cid committed
427
        PresentationFrame *frame = m_frames[m_frameIndex];
428
429
430
431
        int pixW = frame->geometry.width();
        int pixH = frame->geometry.height();

        bool signalsBlocked = m_pagesEdit->signalsBlocked();
Albert Astals Cid's avatar
Albert Astals Cid committed
432
433
434
        m_pagesEdit->blockSignals(true);
        m_pagesEdit->setText(QString::number(m_frameIndex + 1));
        m_pagesEdit->blockSignals(signalsBlocked);
435
436
437

        // if pixmap not inside the Okular::Page we request it and wait for
        // notifyPixmapChanged call or else we can proceed to pixmap generation
438
        if (!frame->page->hasPixmap(this, ceil(pixW * devicePixelRatioF()), ceil(pixH * devicePixelRatioF()))) {
439
            requestPixmaps();
Albert Astals Cid's avatar
Albert Astals Cid committed
440
        } else {
441
442
443
444
445
            // make the background pixmap
            generatePage();
        }

        // perform the page opening action, if any
Albert Astals Cid's avatar
Albert Astals Cid committed
446
447
        if (m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening))
            m_document->processAction(m_document->page(m_frameIndex)->pageAction(Okular::Page::Opening));
448

449
        // perform the additional actions of the page's annotations, if any
Albert Astals Cid's avatar
Albert Astals Cid committed
450
451
        const QLinkedList<Okular::Annotation *> annotationsList = m_document->page(m_frameIndex)->annotations();
        for (const Okular::Annotation *annotation : annotationsList) {
452
            Okular::Action *action = nullptr;
453

Albert Astals Cid's avatar
Albert Astals Cid committed
454
455
456
457
            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);
458

Albert Astals Cid's avatar
Albert Astals Cid committed
459
460
            if (action)
                m_document->processAction(action);
461
462
        }

463
        // start autoplay video playback
Albert Astals Cid's avatar
Albert Astals Cid committed
464
        for (VideoWidget *vw : qAsConst(m_frames[m_frameIndex]->videoWidgets)) {
465
            vw->pageEntered();
466
        }
467
468
469
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
470
bool PresentationWidget::canUnloadPixmap(int pageNumber) const
471
{
Albert Astals Cid's avatar
Albert Astals Cid committed
472
    if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal) {
473
474
        // can unload all pixmaps except for the currently visible one
        return pageNumber != m_frameIndex;
Albert Astals Cid's avatar
Albert Astals Cid committed
475
    } else {
476
477
478
        // can unload all pixmaps except for the currently visible one, previous and next
        return qAbs(pageNumber - m_frameIndex) <= 1;
    }
479
480
}

481
void PresentationWidget::setupActions()
482
{
Albert Astals Cid's avatar
Albert Astals Cid committed
483
484
485
486
487
488
489
490
    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"));
Laurent Montel's avatar
Laurent Montel committed
491
    connect(action, &QAction::toggled, this, &PresentationWidget::toggleBlackScreenMode);
Albert Astals Cid's avatar
Albert Astals Cid committed
492
493
    action->setEnabled(true);
    addAction(action);
494
495
}

496
497
void PresentationWidget::setPlayPauseIcon()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
498
    QAction *playPauseAction = m_ac->action(QStringLiteral("presentation_play_pause"));
499
    if (m_nextPageTimer->isActive()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
500
501
502
503
504
        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"));
505
506
    }
}
507

Albert Astals Cid's avatar
Albert Astals Cid committed
508
bool PresentationWidget::eventFilter(QObject *o, QEvent *e)
509
{
Albert Astals Cid's avatar
Albert Astals Cid committed
510
511
512
513
514
515
    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) {
516
517
                // 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
Albert Astals Cid's avatar
Albert Astals Cid committed
518
519
520
521
                QKeyEvent kp(QEvent::KeyPress, 0, Qt::NoModifier);
                qApp->sendEvent(this, &kp);
                QKeyEvent kr(QEvent::KeyRelease, 0, Qt::NoModifier);
                qApp->sendEvent(this, &kr);
522
523
524
525
526
527
            }
        }
    }
    return false;
}

528
// <widget events>
Albert Astals Cid's avatar
Albert Astals Cid committed
529
bool PresentationWidget::event(QEvent *e)
530
{
Albert Astals Cid's avatar
Albert Astals Cid committed
531
532
    if (e->type() == QEvent::Gesture)
        return gestureEvent(static_cast<QGestureEvent *>(e));
533

Albert Astals Cid's avatar
Albert Astals Cid committed
534
535
    if (e->type() == QEvent::ToolTip) {
        QHelpEvent *he = (QHelpEvent *)e;
536

537
        QRect r;
Albert Astals Cid's avatar
Albert Astals Cid committed
538
        const Okular::Action *link = getLink(he->x(), he->y(), &r);
539

Albert Astals Cid's avatar
Albert Astals Cid committed
540
        if (link) {
Pino Toscano's avatar
Pino Toscano committed
541
            QString tip = link->actionTip();
Albert Astals Cid's avatar
Albert Astals Cid committed
542
543
            if (!tip.isEmpty())
                QToolTip::showText(he->globalPos(), tip, this, r);
544
545
546
        }
        e->accept();
        return true;
Albert Astals Cid's avatar
Albert Astals Cid committed
547
    } else
548
        // do not stop the event
Albert Astals Cid's avatar
Albert Astals Cid committed
549
        return QWidget::event(e);
550
551
}

Albert Astals Cid's avatar
Albert Astals Cid committed
552
bool PresentationWidget::gestureEvent(QGestureEvent *event)
553
554
555
{
    // 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.
Albert Astals Cid's avatar
Albert Astals Cid committed
556
557
    if (QGesture *swipe = event->gesture(Qt::SwipeGesture)) {
        QSwipeGesture *swipeEvent = static_cast<QSwipeGesture *>(swipe);
558

Albert Astals Cid's avatar
Albert Astals Cid committed
559
560
        if (swipeEvent->state() == Qt::GestureFinished) {
            if (swipeEvent->horizontalDirection() == QSwipeGesture::Left) {
561
562
563
564
                slotPrevPage();
                event->accept();
                return true;
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
565
            if (swipeEvent->horizontalDirection() == QSwipeGesture::Right) {
566
567
568
569
570
571
572
573
574
                slotNextPage();
                event->accept();
                return true;
            }
        }
    }

    return false;
}
Albert Astals Cid's avatar
Albert Astals Cid committed
575
void PresentationWidget::keyPressEvent(QKeyEvent *e)
576
{
Albert Astals Cid's avatar
Albert Astals Cid committed
577
    if (!m_isSetup)
578
        return;
Enrico Ros's avatar
Enrico Ros committed
579

Albert Astals Cid's avatar
Albert Astals Cid committed
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
    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;
605
606
607
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
608
void PresentationWidget::wheelEvent(QWheelEvent *e)
609
{
Albert Astals Cid's avatar
Albert Astals Cid committed
610
    if (!m_isSetup)
611
        return;
612

613
    // performance note: don't remove the clipping
Laurent Montel's avatar
Laurent Montel committed
614
    int div = e->angleDelta().y() / 120;
Albert Astals Cid's avatar
Albert Astals Cid committed
615
616
    if (div > 0) {
        if (div > 3)
617
            div = 3;
Albert Astals Cid's avatar
Albert Astals Cid committed
618
        while (div--)
619
            slotPrevPage();
Albert Astals Cid's avatar
Albert Astals Cid committed
620
621
    } else if (div < 0) {
        if (div < -3)
622
            div = -3;
Albert Astals Cid's avatar
Albert Astals Cid committed
623
        while (div++)
624
            slotNextPage();
625
626
627
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
628
void PresentationWidget::mousePressEvent(QMouseEvent *e)
629
{
Albert Astals Cid's avatar
Albert Astals Cid committed
630
    if (!m_isSetup)
631
        return;
632

Albert Astals Cid's avatar
Albert Astals Cid committed
633
634
635
636
637
    if (m_drawingEngine) {
        QRect r = routeMouseDrawingEvent(e);
        if (r.isValid()) {
            m_drawingRect |= r.translated(m_frames[m_frameIndex]->geometry.topLeft());
            update(m_drawingRect);
638
639
640
641
        }
        return;
    }

642
    // pressing left button
Albert Astals Cid's avatar
Albert Astals Cid committed
643
    if (e->button() == Qt::LeftButton) {
644
        // if pressing on a link, skip other checks
Albert Astals Cid's avatar
Albert Astals Cid committed
645
        if ((m_pressedLink = getLink(e->x(), e->y())))
646
647
            return;

Albert Astals Cid's avatar
Albert Astals Cid committed
648
649
650
651
        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);
652

Albert Astals Cid's avatar
Albert Astals Cid committed
653
                VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(movieAnnotation->movie());
654
655
656
                vw->show();
                vw->play();
                return;
Albert Astals Cid's avatar
Albert Astals Cid committed
657
658
            } else if (annotation->subType() == Okular::Annotation::ARichMedia) {
                const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast<const Okular::RichMediaAnnotation *>(annotation);
659

Albert Astals Cid's avatar
Albert Astals Cid committed
660
                VideoWidget *vw = m_frames[m_frameIndex]->videoWidgets.value(richMediaAnnotation->movie());
661
662
663
                vw->show();
                vw->play();
                return;
Albert Astals Cid's avatar
Albert Astals Cid committed
664
665
            } else if (annotation->subType() == Okular::Annotation::AScreen) {
                m_document->processAction(static_cast<const Okular::ScreenAnnotation *>(annotation)->action());
666
667
                return;
            }
668
669
        }

670
        // handle clicking on top-right overlay
Albert Astals Cid's avatar
Albert Astals Cid committed
671
672
        if (!(Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden) && m_overlayGeometry.contains(e->pos())) {
            overlayClick(e->pos());
673
674
            return;
        }
675

676
        // Actual mouse press events always lead to the next page page
Albert Astals Cid's avatar
Albert Astals Cid committed
677
        if (e->source() == Qt::MouseEventNotSynthesized) {
678
679
680
            m_goToNextPageOnRelease = true;
        }
        // Touch events may lead to the previous or next page
Albert Astals Cid's avatar
Albert Astals Cid committed
681
682
683
684
685
686
        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 {
687
688
                    m_goToNextPageOnRelease = true;
                }
Albert Astals Cid's avatar
Albert Astals Cid committed
689
690
691
692
693
694
695
696
697
                break;
            }
            case Okular::Settings::EnumSlidesTapNavigation::Forward: {
                m_goToNextPageOnRelease = true;
                break;
            }
            case Okular::Settings::EnumSlidesTapNavigation::Disabled: {
                // Do Nothing
            }
698
699
            }
        }
700
    }
701
    // pressing forward button
Albert Astals Cid's avatar
Albert Astals Cid committed
702
    else if (e->button() == Qt::ForwardButton) {
703
        m_goToNextPageOnRelease = true;
704
705
    }
    // pressing right or backward button
Albert Astals Cid's avatar
Albert Astals Cid committed
706
    else if (e->button() == Qt::RightButton || e->button() == Qt::BackButton)
707
        m_goToPreviousPageOnRelease = true;
708
709
}

Albert Astals Cid's avatar
Albert Astals Cid committed
710
void PresentationWidget::mouseReleaseEvent(QMouseEvent *e)
711
{
Albert Astals Cid's avatar
Albert Astals Cid committed
712
713
    if (m_drawingEngine) {
        routeMouseDrawingEvent(e);
714
715
716
        return;
    }

717
    // if releasing on the same link we pressed over, execute it
Albert Astals Cid's avatar
Albert Astals Cid committed
718
719
720
721
    if (m_pressedLink && e->button() == Qt::LeftButton) {
        const Okular::Action *link = getLink(e->x(), e->y());
        if (link == m_pressedLink)
            m_document->processAction(link);
722
        m_pressedLink = nullptr;
723
    }
724

Albert Astals Cid's avatar
Albert Astals Cid committed
725
    if (m_goToPreviousPageOnRelease) {
726
727
728
729
        slotPrevPage();
        m_goToPreviousPageOnRelease = false;
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
730
    if (m_goToNextPageOnRelease) {
731
732
733
        slotNextPage();
        m_goToNextPageOnRelease = false;
    }
734
735
}

Albert Astals Cid's avatar
Albert Astals Cid committed
736
void PresentationWidget::mouseMoveEvent(QMouseEvent *e)
737
{
738
    // safety check
Albert Astals Cid's avatar
Albert Astals Cid committed
739
    if (!m_isSetup)
740
741
742
        return;

    // update cursor and tooltip if hovering a link
Albert Astals Cid's avatar
Albert Astals Cid committed
743
744
    if (!m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden)
        testCursorOnLink(e->x(), e->y());
745

Albert Astals Cid's avatar
Albert Astals Cid committed
746
    if (!m_topBar->isHidden()) {
747
        // hide a shown bar when exiting the area
Albert Astals Cid's avatar
Albert Astals Cid committed
748
749
750
        if (e->y() > (m_topBar->height() + 1)) {
            showTopBar(false);
            setFocus(Qt::OtherFocusReason);
751
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
752
753
754
755
756
757
    } 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);
758
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
759
        } else {
760
            // show the bar if reaching top 2 pixels
Albert Astals Cid's avatar
Albert Astals Cid committed
761
762
            if (e->y() <= 1)
                showTopBar(true);
763
            // handle "dragging the wheel" if clicking on its geometry
Albert Astals Cid's avatar
Albert Astals Cid committed
764
765
            else if ((QApplication::mouseButtons() & Qt::LeftButton) && m_overlayGeometry.contains(e->pos()))
                overlayClick(e->pos());
766
        }
767
    }
768
769
}

Albert Astals Cid's avatar
Albert Astals Cid committed
770
void PresentationWidget::paintEvent(QPaintEvent *pe)
771
{
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
772
773
    qreal dpr = devicePixelRatioF();

Albert Astals Cid's avatar
Albert Astals Cid committed
774
775
776
    if (m_inBlackScreenMode) {
        QPainter painter(this);
        painter.fillRect(pe->rect(), Qt::black);
777
778
779
        return;
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
780
    if (!m_isSetup) {
781
782
        m_width = width();
        m_height = height();
783

Laurent Montel's avatar
Laurent Montel committed
784
        connect(m_document, &Okular::Document::linkFind, this, &PresentationWidget::slotFind);
785

786
        // register this observer in document. events will come immediately
Albert Astals Cid's avatar
Albert Astals Cid committed
787
        m_document->addObserver(this);
788
789

        // show summary if requested
Albert Astals Cid's avatar
Albert Astals Cid committed
790
        if (Okular::Settings::slidesShowSummary())
791
792
793
            generatePage();
    }

Yuri Chornoivan's avatar
Yuri Chornoivan committed
794
    // check painting rect consistency
Albert Astals Cid's avatar
Albert Astals Cid committed
795
796
    QRect r = pe->rect().intersected(QRect(QPoint(0, 0), geometry().size()));
    if (r.isNull())
797
798
        return;

Albert Astals Cid's avatar
Albert Astals Cid committed
799
800
801
    if (m_lastRenderedPixmap.isNull()) {
        QPainter painter(this);
        painter.fillRect(pe->rect(), Okular::Settings::slidesBackgroundColor());
802
803
804
        return;
    }

805
    // blit the pixmap to the screen
Albert Astals Cid's avatar
Albert Astals Cid committed
806
807
808
    QPainter painter(this);
    for (const QRect &r : pe->region()) {
        if (!r.isValid())
809
            continue;
810
#ifdef ENABLE_PROGRESS_OVERLAY
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
811
        const QRect dR(QRectF(r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr).toAlignedRect());
Albert Astals Cid's avatar
Albert Astals Cid committed
812
        if (Okular::Settings::slidesShowProgress() && r.intersects(m_overlayGeometry)) {
813
            // backbuffer the overlay operation
Albert Astals Cid's avatar
Albert Astals Cid committed
814
815
816
            QPixmap backPixmap(dR.size());
            backPixmap.setDevicePixelRatio(dpr);
            QPainter pixPainter(&backPixmap);
817
818

            // first draw the background on the backbuffer
Albert Astals Cid's avatar
Albert Astals Cid committed
819
            pixPainter.drawPixmap(QPoint(0, 0), m_lastRenderedPixmap, dR);
820
821

            // then blend the overlay (a piece of) over the background
Albert Astals Cid's avatar
Albert Astals Cid committed
822
823
            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);
824
825
826

            // finally blit the pixmap to the screen
            pixPainter.end();
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
827
828
            const QRect backPixmapRect = backPixmap.rect();
            const QRect dBackPixmapRect(QRectF(backPixmapRect.x() * dpr, backPixmapRect.y() * dpr, backPixmapRect.width() * dpr, backPixmapRect.height() * dpr).toAlignedRect());
Albert Astals Cid's avatar
Albert Astals Cid committed
829
            painter.drawPixmap(r.topLeft(), backPixmap, dBackPixmapRect);
830
831
        } else
#endif
Albert Astals Cid's avatar
Albert Astals Cid committed
832
833
            // copy the rendered pixmap to the screen
            painter.drawPixmap(r.topLeft(), m_lastRenderedPixmap, dR);
834
    }
835
836

    // paint drawings
Albert Astals Cid's avatar
Albert Astals Cid committed
837
    if (m_frameIndex != -1) {
838
        painter.save();
839

Albert Astals Cid's avatar
Albert Astals Cid committed
840
        const QRect &geom = m_frames[m_frameIndex]->geometry;
841

Albert Astals Cid's avatar
Albert Astals Cid committed
842
843
844
845
        const QSize pmSize(geom.width() * dpr, geom.height() * dpr);
        QPixmap pm(pmSize);
        pm.fill(Qt::transparent);
        QPainter pmPainter(&pm);
846

Albert Astals Cid's avatar
Albert Astals Cid committed
847
848
        pm.setDevicePixelRatio(dpr);
        pmPainter.setRenderHints(QPainter::Antialiasing);
849
850

        // Paint old paths
Albert Astals Cid's avatar
Albert Astals Cid committed
851
852
        for (const SmoothPath &drawing : qAsConst(m_frames[m_frameIndex]->drawings)) {
            drawing.paint(&pmPainter, pmSize.width(), pmSize.height());
853
        }
854

855
        // Paint the path that is currently being drawn by the user
Albert Astals Cid's avatar
Albert Astals Cid committed
856
857
        if (m_drawingEngine && m_drawingRect.intersects(pe->rect()))
            m_drawingEngine->paint(&pmPainter, pmSize.width(), pmSize.height(), m_drawingRect.intersected(pe->rect()));
858

Albert Astals Cid's avatar
Albert Astals Cid committed
859
860
        painter.setRenderHints(QPainter::Antialiasing);
        painter.drawPixmap(geom.topLeft(), pm);
861

862
863
        painter.restore();
    }
Pino Toscano's avatar
Pino Toscano committed
864
    painter.end();
865
}
866

Albert Astals Cid's avatar
Albert Astals Cid committed
867
void PresentationWidget::resizeEvent(QResizeEvent *re)
868
{
869
870
871
872
873
    m_width = width();
    m_height = height();