File indexing completed on 2024-04-28 15:52:00

0001 /*
0002     SPDX-FileCopyrightText: 2004-2006 Albert Astals Cid <aacid@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "thumbnaillist.h"
0008 
0009 // qt/kde includes
0010 #include <QAction>
0011 #include <QApplication>
0012 #include <QIcon>
0013 #include <QPainter>
0014 #include <QResizeEvent>
0015 #include <QScrollBar>
0016 #include <QSizePolicy>
0017 #include <QStyle>
0018 #include <QTimer>
0019 #include <QVBoxLayout>
0020 
0021 #include <KActionCollection>
0022 #include <KLocalizedString>
0023 #include <KTitleWidget>
0024 
0025 #include <kwidgetsaddons_version.h>
0026 
0027 // local includes
0028 #include "core/area.h"
0029 #include "core/bookmarkmanager.h"
0030 #include "core/document.h"
0031 #include "core/generator.h"
0032 #include "core/page.h"
0033 #include "cursorwraphelper.h"
0034 #include "gui/pagepainter.h"
0035 #include "gui/priorities.h"
0036 #include "settings.h"
0037 
0038 class ThumbnailWidget;
0039 
0040 ThumbnailsBox::ThumbnailsBox(QWidget *parent)
0041     : QWidget(parent)
0042 {
0043     QVBoxLayout *vbox = new QVBoxLayout(this);
0044     vbox->setSpacing(0);
0045 
0046     KTitleWidget *titleWidget = new KTitleWidget(this);
0047     titleWidget->setLevel(4);
0048     titleWidget->setText(i18n("Thumbnails"));
0049     vbox->addWidget(titleWidget);
0050     vbox->setAlignment(titleWidget, Qt::AlignHCenter);
0051 }
0052 
0053 QSize ThumbnailsBox::sizeHint() const
0054 {
0055     return QSize();
0056 }
0057 
0058 class ThumbnailListPrivate : public QWidget
0059 {
0060     Q_OBJECT
0061 public:
0062     ThumbnailListPrivate(ThumbnailList *qq, Okular::Document *document);
0063     ~ThumbnailListPrivate() override;
0064 
0065     enum ChangePageDirection { Null, Left, Right, Up, Down };
0066 
0067     ThumbnailList *q;
0068     Okular::Document *m_document;
0069     ThumbnailWidget *m_selected;
0070     QTimer *m_delayTimer;
0071     QPixmap m_bookmarkOverlay;
0072     QVector<ThumbnailWidget *> m_thumbnails;
0073     QList<ThumbnailWidget *> m_visibleThumbnails;
0074     int m_vectorIndex;
0075     // Grabbing variables
0076     QPoint m_mouseGrabPos;
0077     ThumbnailWidget *m_mouseGrabItem;
0078     int m_pageCurrentlyGrabbed;
0079 
0080     // resize thumbnails to fit the width
0081     void viewportResizeEvent(QResizeEvent *);
0082     // called by ThumbnailWidgets to send (forward) the mouse move signals
0083     ChangePageDirection forwardTrack(const QPoint, const QSize);
0084 
0085     ThumbnailWidget *itemFor(const QPoint p) const;
0086     void delayedRequestVisiblePixmaps(int delayMs = 0);
0087 
0088     // SLOTS:
0089     // make requests for generating pixmaps for visible thumbnails
0090     void slotRequestVisiblePixmaps();
0091     // delay timeout: resize overlays and requests pixmaps
0092     void slotDelayTimeout();
0093     ThumbnailWidget *getPageByNumber(int page) const;
0094     int getNewPageOffset(int n, ThumbnailListPrivate::ChangePageDirection dir) const;
0095     ThumbnailWidget *getThumbnailbyOffset(int current, int offset) const;
0096 
0097 protected:
0098     void mousePressEvent(QMouseEvent *e) override;
0099     void mouseReleaseEvent(QMouseEvent *e) override;
0100     void mouseMoveEvent(QMouseEvent *e) override;
0101     void wheelEvent(QWheelEvent *e) override;
0102     void contextMenuEvent(QContextMenuEvent *e) override;
0103     void paintEvent(QPaintEvent *e) override;
0104 };
0105 
0106 // ThumbnailWidget represents a single thumbnail in the ThumbnailList
0107 class ThumbnailWidget
0108 {
0109 public:
0110     ThumbnailWidget(ThumbnailListPrivate *parent, const Okular::Page *page);
0111 
0112     // set internal parameters to fit the page in the given width
0113     void resizeFitWidth(int width);
0114     // set thumbnail's selected state
0115     void setSelected(bool selected);
0116     // set the visible rect of the current page
0117     void setVisibleRect(const Okular::NormalizedRect &rect);
0118 
0119     // query methods
0120     int heightHint() const
0121     {
0122         return m_pixmapHeight + m_labelHeight + m_margin;
0123     }
0124     int pixmapWidth() const
0125     {
0126         return m_pixmapWidth;
0127     }
0128     int pixmapHeight() const
0129     {
0130         return m_pixmapHeight;
0131     }
0132     int pageNumber() const
0133     {
0134         return m_page->number();
0135     }
0136     const Okular::Page *page() const
0137     {
0138         return m_page;
0139     }
0140     QRect visibleRect() const
0141     {
0142         return m_visibleRect.geometry(m_pixmapWidth, m_pixmapHeight);
0143     }
0144 
0145     void paint(QPainter &p, const QRect clipRect);
0146 
0147     static int margin()
0148     {
0149         return m_margin;
0150     }
0151 
0152     // simulating QWidget
0153     QRect rect() const
0154     {
0155         return m_rect;
0156     }
0157     int height() const
0158     {
0159         return m_rect.height();
0160     }
0161     int width() const
0162     {
0163         return m_rect.width();
0164     }
0165     QPoint pos() const
0166     {
0167         return m_rect.topLeft();
0168     }
0169     void move(int x, int y)
0170     {
0171         m_rect.setTopLeft(QPoint(x, y));
0172     }
0173     void update()
0174     {
0175         m_parent->update(m_rect);
0176     }
0177     void update(const QRect rect)
0178     {
0179         m_parent->update(rect.translated(m_rect.topLeft()));
0180     }
0181 
0182 private:
0183     // the margin around the widget
0184     static int const m_margin = 16;
0185 
0186     ThumbnailListPrivate *m_parent;
0187     const Okular::Page *m_page;
0188     bool m_selected;
0189     int m_pixmapWidth, m_pixmapHeight;
0190     int m_labelHeight, m_labelNumber;
0191     Okular::NormalizedRect m_visibleRect;
0192     QRect m_rect;
0193 };
0194 
0195 ThumbnailListPrivate::ThumbnailListPrivate(ThumbnailList *qq, Okular::Document *document)
0196     : QWidget()
0197     , q(qq)
0198     , m_document(document)
0199     , m_selected(nullptr)
0200     , m_delayTimer(nullptr)
0201     , m_vectorIndex(0)
0202     , m_pageCurrentlyGrabbed(0)
0203 {
0204     setMouseTracking(true);
0205     m_mouseGrabItem = nullptr;
0206 }
0207 
0208 ThumbnailWidget *ThumbnailListPrivate::getPageByNumber(int page) const
0209 {
0210     QVector<ThumbnailWidget *>::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd();
0211     for (; tIt != tEnd; ++tIt) {
0212         if ((*tIt)->pageNumber() == page) {
0213             return (*tIt);
0214         }
0215     }
0216     return nullptr;
0217 }
0218 
0219 ThumbnailListPrivate::~ThumbnailListPrivate()
0220 {
0221 }
0222 
0223 ThumbnailWidget *ThumbnailListPrivate::itemFor(const QPoint p) const
0224 {
0225     QVector<ThumbnailWidget *>::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd();
0226     for (; tIt != tEnd; ++tIt) {
0227         if ((*tIt)->rect().contains(p)) {
0228             return (*tIt);
0229         }
0230     }
0231     return nullptr;
0232 }
0233 
0234 void ThumbnailListPrivate::paintEvent(QPaintEvent *e)
0235 {
0236     QPainter painter(this);
0237     QVector<ThumbnailWidget *>::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd();
0238     for (; tIt != tEnd; ++tIt) {
0239         QRect rect = e->rect().intersected((*tIt)->rect());
0240         if (!rect.isNull()) {
0241             rect.translate(-(*tIt)->pos());
0242             painter.save();
0243             painter.translate((*tIt)->pos());
0244             (*tIt)->paint(painter, rect);
0245             painter.restore();
0246         }
0247     }
0248 }
0249 
0250 /** ThumbnailList implementation **/
0251 
0252 ThumbnailList::ThumbnailList(QWidget *parent, Okular::Document *document)
0253     : QScrollArea(parent)
0254     , d(new ThumbnailListPrivate(this, document))
0255 {
0256     setObjectName(QStringLiteral("okular::Thumbnails"));
0257     // set scrollbars
0258     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0259     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0260     verticalScrollBar()->setEnabled(false);
0261 
0262     setAttribute(Qt::WA_StaticContents);
0263 
0264     viewport()->setBackgroundRole(QPalette::Base);
0265 
0266     setWidget(d);
0267     // widget setup: can be focused by mouse click (not wheel nor tab)
0268     widget()->setFocusPolicy(Qt::ClickFocus);
0269     widget()->show();
0270     widget()->setBackgroundRole(QPalette::Base);
0271 
0272     connect(verticalScrollBar(), &QScrollBar::valueChanged, d, &ThumbnailListPrivate::slotRequestVisiblePixmaps);
0273 }
0274 
0275 ThumbnailList::~ThumbnailList()
0276 {
0277     d->m_document->removeObserver(this);
0278 }
0279 
0280 // BEGIN DocumentObserver inherited methods
0281 void ThumbnailList::notifySetup(const QVector<Okular::Page *> &pages, int setupFlags)
0282 {
0283     // if there was a widget selected, save its pagenumber to restore
0284     // its selection (if available in the new set of pages)
0285     int prevPage = -1;
0286     if (!(setupFlags & Okular::DocumentObserver::DocumentChanged) && d->m_selected) {
0287         prevPage = d->m_selected->page()->number();
0288     } else {
0289         prevPage = d->m_document->viewport().pageNumber;
0290     }
0291 
0292     // delete all the Thumbnails
0293     QVector<ThumbnailWidget *>::const_iterator tIt = d->m_thumbnails.constBegin(), tEnd = d->m_thumbnails.constEnd();
0294     for (; tIt != tEnd; ++tIt) {
0295         delete *tIt;
0296     }
0297     d->m_thumbnails.clear();
0298     d->m_visibleThumbnails.clear();
0299     d->m_selected = nullptr;
0300     d->m_mouseGrabItem = nullptr;
0301 
0302     if (pages.count() < 1) {
0303         widget()->resize(0, 0);
0304         return;
0305     }
0306 
0307     // show pages containing highlighted text or bookmarked ones
0308     // RESTORE THIS int flags = Okular::Settings::filterBookmarks() ? Okular::Page::Bookmark : Okular::Page::Highlight;
0309 
0310     // if no page matches filter rule, then display all pages
0311     bool skipCheck = true;
0312     for (const Okular::Page *pIt : pages) {
0313         // if ( (*pIt)->attributes() & flags )
0314         if (pIt->hasHighlights(SW_SEARCH_ID)) {
0315             skipCheck = false;
0316         }
0317     }
0318 
0319     // generate Thumbnails for the given set of pages
0320     const int width = viewport()->width();
0321     int height = 0;
0322     int centerHeight = 0;
0323     for (const Okular::Page *pIt : pages) {
0324         // if ( skipCheck || (*pIt)->attributes() & flags )
0325         if (skipCheck || pIt->hasHighlights(SW_SEARCH_ID)) {
0326             ThumbnailWidget *t = new ThumbnailWidget(d, pIt);
0327             t->move(0, height);
0328             // add to the internal queue
0329             d->m_thumbnails.push_back(t);
0330             // update total height (asking widget its own height)
0331             t->resizeFitWidth(width);
0332             // restoring the previous selected page, if any
0333             if (pIt->number() < prevPage) {
0334                 centerHeight = height + t->height() + this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical) / 2;
0335             }
0336             if (pIt->number() == prevPage) {
0337                 d->m_selected = t;
0338                 d->m_selected->setSelected(true);
0339                 centerHeight = height + t->height() / 2;
0340             }
0341             height += t->height() + this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical);
0342         }
0343     }
0344 
0345     // update scrollview's contents size (sets scrollbars limits)
0346     height -= this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical);
0347     widget()->resize(width, height);
0348 
0349     // enable scrollbar when there's something to scroll
0350     verticalScrollBar()->setEnabled(viewport()->height() < height);
0351     verticalScrollBar()->setValue(centerHeight - viewport()->height() / 2);
0352 
0353     // request for thumbnail generation
0354     d->delayedRequestVisiblePixmaps(200);
0355 }
0356 
0357 void ThumbnailList::notifyCurrentPageChanged(int previousPage, int currentPage)
0358 {
0359     Q_UNUSED(previousPage)
0360 
0361     // skip notifies for the current page (already selected)
0362     if (d->m_selected && d->m_selected->pageNumber() == currentPage) {
0363         return;
0364     }
0365 
0366     // deselect previous thumbnail
0367     if (d->m_selected) {
0368         d->m_selected->setSelected(false);
0369     }
0370     d->m_selected = nullptr;
0371 
0372     // select the page with viewport and ensure it's centered in the view
0373     d->m_vectorIndex = 0;
0374     QVector<ThumbnailWidget *>::const_iterator tIt = d->m_thumbnails.constBegin(), tEnd = d->m_thumbnails.constEnd();
0375     for (; tIt != tEnd; ++tIt) {
0376         if ((*tIt)->pageNumber() == currentPage) {
0377             d->m_selected = *tIt;
0378             d->m_selected->setSelected(true);
0379             if (Okular::Settings::syncThumbnailsViewport()) {
0380                 syncThumbnail();
0381             }
0382             break;
0383         }
0384         d->m_vectorIndex++;
0385     }
0386 }
0387 
0388 void ThumbnailList::syncThumbnail()
0389 {
0390     int yOffset = qMax(viewport()->height() / 4, d->m_selected->height() / 2);
0391     ensureVisible(0, d->m_selected->pos().y() + d->m_selected->height() / 2, 0, yOffset);
0392 }
0393 
0394 void ThumbnailList::notifyPageChanged(int pageNumber, int changedFlags)
0395 {
0396     static const int interestingFlags = DocumentObserver::Pixmap | DocumentObserver::Bookmark | DocumentObserver::Highlights | DocumentObserver::Annotations;
0397     // only handle change notifications we are interested in
0398     if (!(changedFlags & interestingFlags)) {
0399         return;
0400     }
0401 
0402     // iterate over visible items: if page(pageNumber) is one of them, repaint it
0403     QList<ThumbnailWidget *>::const_iterator vIt = d->m_visibleThumbnails.constBegin(), vEnd = d->m_visibleThumbnails.constEnd();
0404     for (; vIt != vEnd; ++vIt) {
0405         if ((*vIt)->pageNumber() == pageNumber) {
0406             (*vIt)->update();
0407             break;
0408         }
0409     }
0410 }
0411 
0412 void ThumbnailList::notifyContentsCleared(int changedFlags)
0413 {
0414     // if pixmaps were cleared, re-ask them
0415     if (changedFlags & DocumentObserver::Pixmap) {
0416         d->slotRequestVisiblePixmaps();
0417     }
0418 }
0419 
0420 void ThumbnailList::notifyVisibleRectsChanged()
0421 {
0422     const QVector<Okular::VisiblePageRect *> &visibleRects = d->m_document->visiblePageRects();
0423     QVector<ThumbnailWidget *>::const_iterator tIt = d->m_thumbnails.constBegin(), tEnd = d->m_thumbnails.constEnd();
0424     QVector<Okular::VisiblePageRect *>::const_iterator vEnd = visibleRects.end();
0425     for (; tIt != tEnd; ++tIt) {
0426         bool found = false;
0427         QVector<Okular::VisiblePageRect *>::const_iterator vIt = visibleRects.begin();
0428         for (; (vIt != vEnd) && !found; ++vIt) {
0429             if ((*tIt)->pageNumber() == (*vIt)->pageNumber) {
0430                 (*tIt)->setVisibleRect((*vIt)->rect);
0431                 found = true;
0432             }
0433         }
0434         if (!found) {
0435             (*tIt)->setVisibleRect(Okular::NormalizedRect());
0436         }
0437     }
0438 }
0439 
0440 bool ThumbnailList::canUnloadPixmap(int pageNumber) const
0441 {
0442     // if the thumbnail 'pageNumber' is one of the visible ones, forbid unloading
0443     QList<ThumbnailWidget *>::const_iterator vIt = d->m_visibleThumbnails.constBegin(), vEnd = d->m_visibleThumbnails.constEnd();
0444     for (; vIt != vEnd; ++vIt) {
0445         if ((*vIt)->pageNumber() == pageNumber) {
0446             return false;
0447         }
0448     }
0449     // if hidden permit unloading
0450     return true;
0451 }
0452 // END DocumentObserver inherited methods
0453 
0454 void ThumbnailList::updateWidgets()
0455 {
0456     // Update all visible widgets
0457     QList<ThumbnailWidget *>::const_iterator vIt = d->m_visibleThumbnails.constBegin(), vEnd = d->m_visibleThumbnails.constEnd();
0458     for (; vIt != vEnd; ++vIt) {
0459         ThumbnailWidget *t = *vIt;
0460         t->update();
0461     }
0462 }
0463 
0464 int ThumbnailListPrivate::getNewPageOffset(int n, ThumbnailListPrivate::ChangePageDirection dir) const
0465 {
0466     int reason = 1;
0467     int facingFirst = 0; // facingFirstCentered cornercase
0468     if (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing) {
0469         reason = 2;
0470     } else if (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered) {
0471         facingFirst = 1;
0472         reason = 2;
0473     } else if (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Summary) {
0474         reason = 3;
0475     }
0476     if (dir == ThumbnailListPrivate::Up) {
0477         if (facingFirst && n == 1) {
0478             return -1;
0479         }
0480         return -reason;
0481     }
0482     if (dir == ThumbnailListPrivate::Down) {
0483         return reason;
0484     }
0485     if (dir == ThumbnailListPrivate::Left && reason > 1 && (n + facingFirst) % reason) {
0486         return -1;
0487     }
0488     if (dir == ThumbnailListPrivate::Right && reason > 1 && (n + 1 + facingFirst) % reason) {
0489         return 1;
0490     }
0491     return 0;
0492 }
0493 
0494 ThumbnailWidget *ThumbnailListPrivate::getThumbnailbyOffset(int current, int offset) const
0495 {
0496     QVector<ThumbnailWidget *>::const_iterator it = m_thumbnails.begin();
0497     QVector<ThumbnailWidget *>::const_iterator itE = m_thumbnails.end();
0498     int idx = 0;
0499     while (it != itE) {
0500         if ((*it)->pageNumber() == current) {
0501             break;
0502         }
0503         ++idx;
0504         ++it;
0505     }
0506     if (it == itE) {
0507         return nullptr;
0508     }
0509     idx += offset;
0510     if (idx < 0 || idx >= m_thumbnails.size()) {
0511         return nullptr;
0512     }
0513     return m_thumbnails[idx];
0514 }
0515 
0516 ThumbnailListPrivate::ChangePageDirection ThumbnailListPrivate::forwardTrack(const QPoint point, const QSize r)
0517 {
0518     Okular::DocumentViewport vp = m_document->viewport();
0519     const double deltaX = (double)point.x() / r.width(), deltaY = (double)point.y() / r.height();
0520     vp.rePos.normalizedX -= deltaX;
0521     vp.rePos.normalizedY -= deltaY;
0522     if (vp.rePos.normalizedY > 1.0) {
0523         return ThumbnailListPrivate::Down;
0524     }
0525     if (vp.rePos.normalizedY < 0.0) {
0526         return ThumbnailListPrivate::Up;
0527     }
0528     if (vp.rePos.normalizedX > 1.0) {
0529         return ThumbnailListPrivate::Right;
0530     }
0531     if (vp.rePos.normalizedX < 0.0) {
0532         return ThumbnailListPrivate::Left;
0533     }
0534     vp.rePos.enabled = true;
0535     m_document->setViewport(vp);
0536     return ThumbnailListPrivate::Null;
0537 }
0538 
0539 void ThumbnailList::slotFilterBookmarks(bool filterOn)
0540 {
0541     // save state
0542     Okular::Settings::setFilterBookmarks(filterOn);
0543     Okular::Settings::self()->save();
0544     // ask for the 'notifySetup' with a little trick (on reinsertion the
0545     // document sends the list again)
0546     d->m_document->removeObserver(this);
0547     d->m_document->addObserver(this);
0548 }
0549 
0550 // BEGIN widget events
0551 void ThumbnailList::keyPressEvent(QKeyEvent *keyEvent)
0552 {
0553     if (d->m_thumbnails.count() < 1) {
0554         keyEvent->ignore();
0555         return;
0556     }
0557 
0558     int nextPage = -1;
0559     if (keyEvent->key() == Qt::Key_Up) {
0560         if (!d->m_selected) {
0561             nextPage = 0;
0562         } else if (d->m_vectorIndex > 0) {
0563             nextPage = d->m_thumbnails[d->m_vectorIndex - 1]->pageNumber();
0564         }
0565     } else if (keyEvent->key() == Qt::Key_Down) {
0566         if (!d->m_selected) {
0567             nextPage = 0;
0568         } else if (d->m_vectorIndex < (int)d->m_thumbnails.count() - 1) {
0569             nextPage = d->m_thumbnails[d->m_vectorIndex + 1]->pageNumber();
0570         }
0571     } else if (keyEvent->key() == Qt::Key_PageUp) {
0572         verticalScrollBar()->triggerAction(QScrollBar::SliderPageStepSub);
0573     } else if (keyEvent->key() == Qt::Key_PageDown) {
0574         verticalScrollBar()->triggerAction(QScrollBar::SliderPageStepAdd);
0575     } else if (keyEvent->key() == Qt::Key_Home) {
0576         nextPage = d->m_thumbnails[0]->pageNumber();
0577     } else if (keyEvent->key() == Qt::Key_End) {
0578         nextPage = d->m_thumbnails[d->m_thumbnails.count() - 1]->pageNumber();
0579     }
0580 
0581     if (nextPage == -1) {
0582         keyEvent->ignore();
0583         return;
0584     }
0585 
0586     keyEvent->accept();
0587     if (d->m_selected) {
0588         d->m_selected->setSelected(false);
0589     }
0590     d->m_selected = nullptr;
0591     d->m_document->setViewportPage(nextPage);
0592 }
0593 
0594 bool ThumbnailList::viewportEvent(QEvent *e)
0595 {
0596     switch (e->type()) {
0597     case QEvent::Resize: {
0598         d->viewportResizeEvent(static_cast<QResizeEvent *>(e));
0599         break;
0600     }
0601     default:;
0602     }
0603     return QScrollArea::viewportEvent(e);
0604 }
0605 
0606 void ThumbnailListPrivate::viewportResizeEvent(QResizeEvent *e)
0607 {
0608     if (m_thumbnails.count() < 1 || width() < 1) {
0609         return;
0610     }
0611 
0612     // if width changed resize all the Thumbnails, reposition them to the
0613     // right place and recalculate the contents area
0614     if (e->size().width() != e->oldSize().width()) {
0615         // runs the timer avoiding a thumbnail regeneration by 'contentsMoving'
0616         delayedRequestVisiblePixmaps(2000);
0617 
0618         // resize and reposition items
0619         const int newWidth = q->viewport()->width();
0620         int newHeight = 0;
0621         QVector<ThumbnailWidget *>::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd();
0622         for (; tIt != tEnd; ++tIt) {
0623             ThumbnailWidget *t = *tIt;
0624             t->move(0, newHeight);
0625             t->resizeFitWidth(newWidth);
0626             newHeight += t->height() + this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical);
0627         }
0628 
0629         // update scrollview's contents size (sets scrollbars limits)
0630         newHeight -= this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical);
0631         const int oldHeight = q->widget()->height();
0632         const int oldYCenter = q->verticalScrollBar()->value() + q->viewport()->height() / 2;
0633         q->widget()->resize(newWidth, newHeight);
0634 
0635         // enable scrollbar when there's something to scroll
0636         q->verticalScrollBar()->setEnabled(q->viewport()->height() < newHeight);
0637 
0638         // ensure that what was visible before remains visible now
0639         q->ensureVisible(0, int((qreal)oldYCenter * q->widget()->height() / oldHeight), 0, q->viewport()->height() / 2);
0640     } else if (e->size().height() <= e->oldSize().height()) {
0641         return;
0642     }
0643 
0644     // invalidate the bookmark overlay
0645     m_bookmarkOverlay = QPixmap {};
0646 
0647     // update Thumbnails since width has changed or height has increased
0648     delayedRequestVisiblePixmaps(500);
0649 }
0650 // END widget events
0651 
0652 // BEGIN internal SLOTS
0653 void ThumbnailListPrivate::slotRequestVisiblePixmaps()
0654 {
0655     // if an update is already scheduled or the widget is hidden, don't proceed
0656     if ((m_delayTimer && m_delayTimer->isActive()) || q->isHidden()) {
0657         return;
0658     }
0659 
0660     // scroll from the top to the last visible thumbnail
0661     m_visibleThumbnails.clear();
0662     QList<Okular::PixmapRequest *> requestedPixmaps;
0663     QVector<ThumbnailWidget *>::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd();
0664     const QRect viewportRect = q->viewport()->rect().translated(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value());
0665     for (; tIt != tEnd; ++tIt) {
0666         ThumbnailWidget *t = *tIt;
0667         const QRect thumbRect = t->rect();
0668         if (!thumbRect.intersects(viewportRect)) {
0669             continue;
0670         }
0671         // add ThumbnailWidget to visible list
0672         m_visibleThumbnails.push_back(t);
0673         // if pixmap not present add it to requests
0674         if (!t->page()->hasPixmap(q, t->pixmapWidth(), t->pixmapHeight())) {
0675             Okular::PixmapRequest *p = new Okular::PixmapRequest(q, t->pageNumber(), t->pixmapWidth(), t->pixmapHeight(), devicePixelRatioF(), THUMBNAILS_PRIO, Okular::PixmapRequest::Asynchronous);
0676             requestedPixmaps.push_back(p);
0677         }
0678     }
0679 
0680     // actually request pixmaps
0681     if (!requestedPixmaps.isEmpty()) {
0682         m_document->requestPixmaps(requestedPixmaps);
0683     }
0684 }
0685 
0686 void ThumbnailListPrivate::slotDelayTimeout()
0687 {
0688     // resize the bookmark overlay
0689     const int expectedWidth = q->viewport()->width() / 4;
0690     if (expectedWidth > 10) {
0691         m_bookmarkOverlay = QPixmap(QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(expectedWidth));
0692     } else {
0693         m_bookmarkOverlay = QPixmap {};
0694     }
0695 
0696     // request pixmaps
0697     slotRequestVisiblePixmaps();
0698 }
0699 // END internal SLOTS
0700 
0701 void ThumbnailListPrivate::delayedRequestVisiblePixmaps(int delayMs)
0702 {
0703     if (!m_delayTimer) {
0704         m_delayTimer = new QTimer(q);
0705         m_delayTimer->setSingleShot(true);
0706         connect(m_delayTimer, &QTimer::timeout, this, &ThumbnailListPrivate::slotDelayTimeout);
0707     }
0708     m_delayTimer->start(delayMs);
0709 }
0710 
0711 /** ThumbnailWidget implementation **/
0712 
0713 ThumbnailWidget::ThumbnailWidget(ThumbnailListPrivate *parent, const Okular::Page *page)
0714     : m_parent(parent)
0715     , m_page(page)
0716     , m_selected(false)
0717     , m_pixmapWidth(10)
0718     , m_pixmapHeight(10)
0719 {
0720     m_labelNumber = m_page->number() + 1;
0721     m_labelHeight = QFontMetrics(m_parent->font()).height();
0722 }
0723 
0724 void ThumbnailWidget::resizeFitWidth(int width)
0725 {
0726     m_pixmapWidth = width - m_margin;
0727     m_pixmapHeight = qRound(m_page->ratio() * (double)m_pixmapWidth);
0728     m_rect.setSize(QSize(width, heightHint()));
0729 }
0730 
0731 void ThumbnailWidget::setSelected(bool selected)
0732 {
0733     // update selected state
0734     if (m_selected != selected) {
0735         m_selected = selected;
0736         update();
0737     }
0738 }
0739 
0740 void ThumbnailWidget::setVisibleRect(const Okular::NormalizedRect &rect)
0741 {
0742     if (rect == m_visibleRect) {
0743         return;
0744     }
0745 
0746     m_visibleRect = rect;
0747     update();
0748 }
0749 
0750 void ThumbnailListPrivate::mousePressEvent(QMouseEvent *e)
0751 {
0752     ThumbnailWidget *item = itemFor(e->pos());
0753     if (!item) { // mouse on the spacing between items
0754         e->ignore();
0755         return;
0756     }
0757 
0758     const QRect r = item->visibleRect();
0759     const int margin = ThumbnailWidget::margin();
0760     const QPoint p = e->pos() - item->pos();
0761 
0762     if (e->button() != Qt::RightButton && r.contains(p - QPoint(margin / 2, margin / 2))) {
0763         m_mouseGrabPos.setX(0);
0764         m_mouseGrabPos.setY(0);
0765         m_mouseGrabItem = item;
0766         m_pageCurrentlyGrabbed = item->pageNumber();
0767         m_mouseGrabItem = item;
0768     } else {
0769         m_mouseGrabPos.setX(0);
0770         m_mouseGrabPos.setY(0);
0771         m_mouseGrabItem = nullptr;
0772     }
0773 
0774     CursorWrapHelper::startDrag();
0775 }
0776 
0777 void ThumbnailListPrivate::mouseReleaseEvent(QMouseEvent *e)
0778 {
0779     ThumbnailWidget *item = itemFor(e->pos());
0780     m_mouseGrabItem = item;
0781     if (!item) { // mouse on the spacing between items
0782         e->ignore();
0783         return;
0784     }
0785 
0786     QRect r = item->visibleRect();
0787     const QPoint p = e->pos() - item->pos();
0788 
0789     // jump center of viewport to cursor if it wasn't dragged
0790     if (m_mouseGrabPos.isNull()) {
0791         r = item->visibleRect();
0792         Okular::DocumentViewport vp = Okular::DocumentViewport(item->pageNumber());
0793         vp.rePos.normalizedX = double(p.x()) / double(item->rect().width());
0794         vp.rePos.normalizedY = double(p.y()) / double(item->rect().height());
0795         vp.rePos.pos = Okular::DocumentViewport::Center;
0796         vp.rePos.enabled = true;
0797         m_document->setViewport(vp, nullptr, true);
0798     }
0799     setCursor(Qt::OpenHandCursor);
0800     m_mouseGrabPos.setX(0);
0801     m_mouseGrabPos.setY(0);
0802 }
0803 
0804 void ThumbnailListPrivate::mouseMoveEvent(QMouseEvent *e)
0805 {
0806     if (e->buttons() == Qt::NoButton) {
0807         const ThumbnailWidget *item = itemFor(e->pos());
0808         if (!item) { // mouse on the spacing between items
0809             e->ignore();
0810             return;
0811         }
0812 
0813         QRect r = item->visibleRect();
0814         const int margin = ThumbnailWidget::margin();
0815         const QPoint p = e->pos() - item->pos();
0816         if (r.contains(p - QPoint(margin / 2, margin / 2))) {
0817             setCursor(Qt::OpenHandCursor);
0818         } else {
0819             setCursor(Qt::ArrowCursor);
0820         }
0821 
0822         e->ignore();
0823         return;
0824     }
0825     // no item under the mouse or previously selected
0826     if (!m_mouseGrabItem) {
0827         e->ignore();
0828         return;
0829     }
0830     const QRect r = m_mouseGrabItem->rect();
0831     if (!m_mouseGrabPos.isNull()) {
0832         const QPoint mousePos = e->pos();
0833         const QPoint delta = m_mouseGrabPos - mousePos;
0834         m_mouseGrabPos = e->pos();
0835         // don't handle the mouse move, forward it to the thumbnail list
0836         ThumbnailListPrivate::ChangePageDirection direction;
0837         if ((direction = forwardTrack(delta, r.size())) != ThumbnailListPrivate::Null) {
0838             // Changing the selected page
0839             const int offset = getNewPageOffset(m_pageCurrentlyGrabbed, direction);
0840             const ThumbnailWidget *newThumb = getThumbnailbyOffset(m_pageCurrentlyGrabbed, offset);
0841             if (!newThumb) {
0842                 return;
0843             }
0844             int newPageOn = newThumb->pageNumber();
0845             if (newPageOn == m_pageCurrentlyGrabbed || newPageOn < 0 || newPageOn >= (int)m_document->pages()) {
0846                 return;
0847             }
0848             Okular::DocumentViewport vp = m_document->viewport();
0849             const float origNormalX = vp.rePos.normalizedX;
0850             const float origNormalY = vp.rePos.normalizedY;
0851             vp = Okular::DocumentViewport(newPageOn);
0852             vp.rePos.normalizedX = origNormalX;
0853             vp.rePos.normalizedY = origNormalY;
0854             if (direction == ThumbnailListPrivate::Up) {
0855                 vp.rePos.normalizedY = 1.0;
0856                 if (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered && !newPageOn) {
0857                     if (m_pageCurrentlyGrabbed == 1) {
0858                         vp.rePos.normalizedX = origNormalX - 0.5;
0859                     } else {
0860                         vp.rePos.normalizedX = origNormalX + 0.5;
0861                     }
0862                     if (vp.rePos.normalizedX < 0.0) {
0863                         vp.rePos.normalizedX = 0.0;
0864                     } else if (vp.rePos.normalizedX > 1.0) {
0865                         vp.rePos.normalizedX = 1.0;
0866                     }
0867                 }
0868             } else if (direction == ThumbnailListPrivate::Down) {
0869                 vp.rePos.normalizedY = 0.0;
0870                 if (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered && !m_pageCurrentlyGrabbed) {
0871                     if (origNormalX < 0.5) {
0872                         vp = Okular::DocumentViewport(--newPageOn);
0873                         vp.rePos.normalizedX = origNormalX + 0.5;
0874                     } else {
0875                         vp.rePos.normalizedX = origNormalX - 0.5;
0876                     }
0877                     if (vp.rePos.normalizedX < 0.0) {
0878                         vp.rePos.normalizedX = 0.0;
0879                     } else if (vp.rePos.normalizedX > 1.0) {
0880                         vp.rePos.normalizedX = 1.0;
0881                     }
0882                 }
0883             } else if (Okular::Settings::viewMode() != Okular::Settings::EnumViewMode::Single) {
0884                 if (direction == ThumbnailListPrivate::Left) {
0885                     vp.rePos.normalizedX = 1.0;
0886                 } else {
0887                     vp.rePos.normalizedX = 0.0;
0888                 }
0889             }
0890             vp.rePos.pos = Okular::DocumentViewport::Center;
0891             vp.rePos.enabled = true;
0892             m_document->setViewport(vp);
0893             m_mouseGrabPos.setX(0);
0894             m_mouseGrabPos.setY(0);
0895             m_pageCurrentlyGrabbed = newPageOn;
0896             m_mouseGrabItem = getPageByNumber(m_pageCurrentlyGrabbed);
0897         }
0898 
0899         // Wrap mouse cursor
0900         if (Okular::Settings::dragBeyondScreenEdges() && !CursorWrapHelper::wrapCursor(mousePos, Qt::TopEdge | Qt::BottomEdge).isNull()) {
0901             m_mouseGrabPos.setX(0);
0902             m_mouseGrabPos.setY(0);
0903         }
0904     } else {
0905         setCursor(Qt::ClosedHandCursor);
0906         m_mouseGrabPos = e->pos();
0907     }
0908 }
0909 
0910 void ThumbnailListPrivate::wheelEvent(QWheelEvent *e)
0911 {
0912     const ThumbnailWidget *item = itemFor(e->position().toPoint());
0913     if (!item) { // wheeling on the spacing between items
0914         e->ignore();
0915         return;
0916     }
0917 
0918     const QRect r = item->visibleRect();
0919     const int margin = ThumbnailWidget::margin();
0920 
0921     Qt::Orientation orientation {qAbs(e->angleDelta().x()) > qAbs(e->angleDelta().y()) ? Qt::Horizontal : Qt::Vertical};
0922 
0923     if (r.contains(e->position().toPoint() - QPoint(margin / 2, margin / 2)) && orientation == Qt::Vertical && e->modifiers() == Qt::ControlModifier) {
0924         m_document->setZoom(e->angleDelta().y());
0925     } else {
0926         e->ignore();
0927     }
0928 }
0929 
0930 void ThumbnailListPrivate::contextMenuEvent(QContextMenuEvent *e)
0931 {
0932     const ThumbnailWidget *item = itemFor(e->pos());
0933     if (item) {
0934         Q_EMIT q->rightClick(item->page(), e->globalPos());
0935     }
0936 }
0937 
0938 void ThumbnailWidget::paint(QPainter &p, const QRect _clipRect)
0939 {
0940     const int itemWidth = m_pixmapWidth + m_margin;
0941     QRect clipRect = _clipRect;
0942     const QPalette pal = m_parent->palette();
0943 
0944     // draw the bottom label + highlight mark
0945     const QColor fillColor = m_selected ? pal.color(QPalette::Active, QPalette::Highlight) : pal.color(QPalette::Active, QPalette::Base);
0946     p.fillRect(clipRect, fillColor);
0947     p.setPen(m_selected ? pal.color(QPalette::Active, QPalette::HighlightedText) : pal.color(QPalette::Active, QPalette::Text));
0948     p.drawText(0, m_pixmapHeight + (m_margin - 3), itemWidth, m_labelHeight, Qt::AlignCenter, QString::number(m_labelNumber));
0949 
0950     // draw page outline and pixmap
0951     if (clipRect.top() < m_pixmapHeight + m_margin) {
0952         // if page is bookmarked draw a colored border
0953         const bool isBookmarked = m_parent->m_document->bookmarkManager()->isBookmarked(pageNumber());
0954         // draw the inner rect
0955         p.setPen(isBookmarked ? QColor(0xFF8000) : Qt::black);
0956         p.drawRect(m_margin / 2 - 1, m_margin / 2 - 1, m_pixmapWidth + 1, m_pixmapHeight + 1);
0957         // draw the clear rect
0958         p.setPen(isBookmarked ? QColor(0x804000) : pal.color(QPalette::Active, QPalette::Base));
0959         // draw the bottom and right shadow edges
0960         if (!isBookmarked) {
0961             int left, right, bottom, top;
0962             left = m_margin / 2 + 1;
0963             right = m_margin / 2 + m_pixmapWidth + 1;
0964             bottom = m_pixmapHeight + m_margin / 2 + 1;
0965             top = m_margin / 2 + 1;
0966             p.setPen(Qt::gray);
0967             p.drawLine(left, bottom, right, bottom);
0968             p.drawLine(right, top, right, bottom);
0969         }
0970 
0971         // draw the page using the shared PagePainter class
0972         p.translate(m_margin / 2.0, m_margin / 2.0);
0973         clipRect.translate(-m_margin / 2, -m_margin / 2);
0974         clipRect = clipRect.intersected(QRect(0, 0, m_pixmapWidth, m_pixmapHeight));
0975         if (clipRect.isValid()) {
0976             int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations;
0977             PagePainter::paintPageOnPainter(&p, m_page, m_parent->q, flags, m_pixmapWidth, m_pixmapHeight, clipRect);
0978         }
0979 
0980         if (!m_visibleRect.isNull()) {
0981             p.save();
0982             p.setPen(QColor(255, 255, 0, 200));
0983             p.setBrush(QColor(0, 0, 0, 100));
0984             p.drawRect(m_visibleRect.geometry(m_pixmapWidth, m_pixmapHeight).adjusted(0, 0, -1, -1));
0985             p.restore();
0986         }
0987 
0988         // draw the bookmark overlay on the top-right corner
0989         const QPixmap bookmarkPixmap = m_parent->m_bookmarkOverlay;
0990         if (isBookmarked && !bookmarkPixmap.isNull()) {
0991             int pixW = bookmarkPixmap.width(), pixH = bookmarkPixmap.height();
0992             clipRect = clipRect.intersected(QRect(m_pixmapWidth - pixW, 0, pixW, pixH));
0993             if (clipRect.isValid()) {
0994                 p.drawPixmap(m_pixmapWidth - pixW, -pixH / 8, bookmarkPixmap);
0995             }
0996         }
0997     }
0998 }
0999 
1000 /** ThumbnailsController implementation **/
1001 
1002 #define FILTERB_ID 1
1003 
1004 ThumbnailController::ThumbnailController(QWidget *parent, ThumbnailList *list)
1005     : QToolBar(parent)
1006 {
1007     setObjectName(QStringLiteral("ThumbsControlBar"));
1008     // change toolbar appearance
1009     setIconSize(QSize(16, 16));
1010     setMovable(false);
1011     QSizePolicy sp = sizePolicy();
1012     sp.setVerticalPolicy(QSizePolicy::Minimum);
1013     setSizePolicy(sp);
1014 
1015     // insert a togglebutton [show only bookmarked pages]
1016     // insertSeparator();
1017     QAction *showBoomarkOnlyAction = addAction(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Show bookmarked pages only"));
1018     showBoomarkOnlyAction->setCheckable(true);
1019     connect(showBoomarkOnlyAction, &QAction::toggled, list, &ThumbnailList::slotFilterBookmarks);
1020     showBoomarkOnlyAction->setChecked(Okular::Settings::filterBookmarks());
1021     // insertLineSeparator();
1022 }
1023 
1024 #include "thumbnaillist.moc"
1025 
1026 /* kate: replace-tabs on; indent-width 4; */