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; */