File indexing completed on 2024-05-12 04:34:11
0001 /* 0002 SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "pageitem.h" 0008 #include "documentitem.h" 0009 0010 #include <QPainter> 0011 #include <QQuickWindow> 0012 #include <QSGSimpleTextureNode> 0013 #include <QStyleOptionGraphicsItem> 0014 #include <QTimer> 0015 0016 #include <core/bookmarkmanager.h> 0017 #include <core/generator.h> 0018 #include <core/page.h> 0019 0020 #include "gui/pagepainter.h" 0021 #include "gui/priorities.h" 0022 #include "settings.h" 0023 0024 #define REDRAW_TIMEOUT 250 0025 0026 PageItem::PageItem(QQuickItem *parent) 0027 : QQuickItem(parent) 0028 , Okular::View(QStringLiteral("PageView")) 0029 , m_page(nullptr) 0030 , m_bookmarked(false) 0031 , m_isThumbnail(false) 0032 { 0033 setFlag(QQuickItem::ItemHasContents, true); 0034 0035 m_viewPort.rePos.enabled = true; 0036 0037 m_redrawTimer = new QTimer(this); 0038 m_redrawTimer->setInterval(REDRAW_TIMEOUT); 0039 m_redrawTimer->setSingleShot(true); 0040 connect(m_redrawTimer, &QTimer::timeout, this, &PageItem::requestPixmap); 0041 connect(this, &QQuickItem::windowChanged, m_redrawTimer, [this]() { m_redrawTimer->start(); }); 0042 } 0043 0044 PageItem::~PageItem() 0045 { 0046 } 0047 0048 void PageItem::setFlickable(QQuickItem *flickable) 0049 { 0050 if (m_flickable.data() == flickable) { 0051 return; 0052 } 0053 0054 // check the object can act as a flickable 0055 if (!flickable->property("contentX").isValid() || !flickable->property("contentY").isValid()) { 0056 return; 0057 } 0058 0059 if (m_flickable) { 0060 disconnect(m_flickable.data(), nullptr, this, nullptr); 0061 } 0062 0063 // check the object can act as a flickable 0064 if (!flickable->property("contentX").isValid() || !flickable->property("contentY").isValid()) { 0065 m_flickable.clear(); 0066 return; 0067 } 0068 0069 m_flickable = flickable; 0070 0071 if (flickable) { 0072 // QQuickFlickable is not exported so we need the old-style connects here 0073 connect(flickable, SIGNAL(contentXChanged()), this, SLOT(contentXChanged())); // clazy:exclude=old-style-connect 0074 connect(flickable, SIGNAL(contentYChanged()), this, SLOT(contentYChanged())); // clazy:exclude=old-style-connect 0075 } 0076 0077 Q_EMIT flickableChanged(); 0078 } 0079 0080 QQuickItem *PageItem::flickable() const 0081 { 0082 return m_flickable.data(); 0083 } 0084 0085 DocumentItem *PageItem::document() const 0086 { 0087 return m_documentItem.data(); 0088 } 0089 0090 void PageItem::setDocument(DocumentItem *doc) 0091 { 0092 if (doc == m_documentItem.data() || !doc) { 0093 return; 0094 } 0095 0096 m_page = nullptr; 0097 disconnect(doc, nullptr, this, nullptr); 0098 m_documentItem = doc; 0099 Observer *observer = m_isThumbnail ? m_documentItem.data()->thumbnailObserver() : m_documentItem.data()->pageviewObserver(); 0100 connect(observer, &Observer::pageChanged, this, &PageItem::pageHasChanged); 0101 connect(doc->document()->bookmarkManager(), &Okular::BookmarkManager::bookmarksChanged, this, &PageItem::checkBookmarksChanged); 0102 setPageNumber(0); 0103 Q_EMIT documentChanged(); 0104 m_redrawTimer->start(); 0105 0106 connect(doc, &DocumentItem::urlChanged, this, &PageItem::refreshPage); 0107 } 0108 0109 int PageItem::pageNumber() const 0110 { 0111 return m_viewPort.pageNumber; 0112 } 0113 0114 void PageItem::setPageNumber(int number) 0115 { 0116 if ((m_page && m_viewPort.pageNumber == number) || !m_documentItem || !m_documentItem.data()->isOpened() || number < 0) { 0117 return; 0118 } 0119 0120 m_viewPort.pageNumber = number; 0121 refreshPage(); 0122 Q_EMIT pageNumberChanged(); 0123 checkBookmarksChanged(); 0124 } 0125 0126 void PageItem::refreshPage() 0127 { 0128 if (uint(m_viewPort.pageNumber) < m_documentItem.data()->document()->pages()) { 0129 m_page = m_documentItem.data()->document()->page(m_viewPort.pageNumber); 0130 } else { 0131 m_page = nullptr; 0132 } 0133 0134 Q_EMIT implicitWidthChanged(); 0135 Q_EMIT implicitHeightChanged(); 0136 0137 m_redrawTimer->start(); 0138 } 0139 0140 int PageItem::implicitWidth() const 0141 { 0142 if (m_page) { 0143 return m_page->width(); 0144 } 0145 return 0; 0146 } 0147 0148 int PageItem::implicitHeight() const 0149 { 0150 if (m_page) { 0151 return m_page->height(); 0152 } 0153 return 0; 0154 } 0155 0156 bool PageItem::isBookmarked() 0157 { 0158 return m_bookmarked; 0159 } 0160 0161 void PageItem::setBookmarked(bool bookmarked) 0162 { 0163 if (!m_documentItem) { 0164 return; 0165 } 0166 0167 if (bookmarked == m_bookmarked) { 0168 return; 0169 } 0170 0171 if (bookmarked) { 0172 m_documentItem.data()->document()->bookmarkManager()->addBookmark(m_viewPort); 0173 } else { 0174 m_documentItem.data()->document()->bookmarkManager()->removeBookmark(m_viewPort.pageNumber); 0175 } 0176 m_bookmarked = bookmarked; 0177 Q_EMIT bookmarkedChanged(); 0178 } 0179 0180 QStringList PageItem::bookmarks() const 0181 { 0182 QStringList list; 0183 const KBookmark::List pageMarks = m_documentItem.data()->document()->bookmarkManager()->bookmarks(m_viewPort.pageNumber); 0184 for (const KBookmark &bookmark : pageMarks) { 0185 list << bookmark.url().toString(); 0186 } 0187 return list; 0188 } 0189 0190 void PageItem::goToBookmark(const QString &bookmark) 0191 { 0192 Okular::DocumentViewport viewPort(QUrl::fromUserInput(bookmark).fragment(QUrl::FullyDecoded)); 0193 setPageNumber(viewPort.pageNumber); 0194 0195 // Are we in a flickable? 0196 if (m_flickable) { 0197 // normalizedX is a proportion, so contentX will be the difference between document and viewport times normalizedX 0198 m_flickable.data()->setProperty("contentX", qMax((qreal)0, width() - m_flickable.data()->width()) * viewPort.rePos.normalizedX); 0199 0200 m_flickable.data()->setProperty("contentY", qMax((qreal)0, height() - m_flickable.data()->height()) * viewPort.rePos.normalizedY); 0201 } 0202 } 0203 0204 QPointF PageItem::bookmarkPosition(const QString &bookmark) const 0205 { 0206 Okular::DocumentViewport viewPort(QUrl::fromUserInput(bookmark).fragment(QUrl::FullyDecoded)); 0207 0208 if (viewPort.pageNumber != m_viewPort.pageNumber) { 0209 return QPointF(-1, -1); 0210 } 0211 0212 return QPointF(qMax((qreal)0, width() - m_flickable.data()->width()) * viewPort.rePos.normalizedX, qMax((qreal)0, height() - m_flickable.data()->height()) * viewPort.rePos.normalizedY); 0213 } 0214 0215 void PageItem::setBookmarkAtPos(qreal x, qreal y) 0216 { 0217 Okular::DocumentViewport viewPort(m_viewPort); 0218 viewPort.rePos.normalizedX = x; 0219 viewPort.rePos.normalizedY = y; 0220 0221 m_documentItem.data()->document()->bookmarkManager()->addBookmark(viewPort); 0222 0223 if (!m_bookmarked) { 0224 m_bookmarked = true; 0225 Q_EMIT bookmarkedChanged(); 0226 } 0227 0228 Q_EMIT bookmarksChanged(); 0229 } 0230 0231 void PageItem::removeBookmarkAtPos(qreal x, qreal y) 0232 { 0233 Okular::DocumentViewport viewPort(m_viewPort); 0234 viewPort.rePos.enabled = true; 0235 viewPort.rePos.normalizedX = x; 0236 viewPort.rePos.normalizedY = y; 0237 0238 m_documentItem.data()->document()->bookmarkManager()->addBookmark(viewPort); 0239 0240 if (m_bookmarked && m_documentItem.data()->document()->bookmarkManager()->bookmarks(m_viewPort.pageNumber).count() == 0) { 0241 m_bookmarked = false; 0242 Q_EMIT bookmarkedChanged(); 0243 } 0244 0245 Q_EMIT bookmarksChanged(); 0246 } 0247 0248 void PageItem::removeBookmark(const QString &bookmark) 0249 { 0250 m_documentItem.data()->document()->bookmarkManager()->removeBookmark(Okular::DocumentViewport(bookmark)); 0251 Q_EMIT bookmarksChanged(); 0252 } 0253 0254 // Reimplemented 0255 void PageItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) 0256 { 0257 if (newGeometry.size().isEmpty()) { 0258 return; 0259 } 0260 0261 bool changed = false; 0262 if (newGeometry.size() != oldGeometry.size()) { 0263 changed = true; 0264 m_redrawTimer->start(); 0265 } 0266 0267 QQuickItem::geometryChange(newGeometry, oldGeometry); 0268 0269 if (changed) { 0270 // Why aren't they automatically emitted? 0271 Q_EMIT widthChanged(); 0272 Q_EMIT heightChanged(); 0273 } 0274 } 0275 0276 QSGNode *PageItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData * /*data*/) 0277 { 0278 if (!window() || m_buffer.isNull()) { 0279 delete node; 0280 return nullptr; 0281 } 0282 QSGSimpleTextureNode *n = static_cast<QSGSimpleTextureNode *>(node); 0283 if (!n) { 0284 n = new QSGSimpleTextureNode(); 0285 n->setOwnsTexture(true); 0286 } 0287 0288 n->setTexture(window()->createTextureFromImage(m_buffer)); 0289 n->setRect(boundingRect()); 0290 return n; 0291 } 0292 0293 void PageItem::requestPixmap() 0294 { 0295 if (!m_documentItem || !m_page || !window() || width() <= 0 || height() < 0) { 0296 if (!m_buffer.isNull()) { 0297 m_buffer = QImage(); 0298 update(); 0299 } 0300 return; 0301 } 0302 0303 Observer *observer = m_isThumbnail ? m_documentItem.data()->thumbnailObserver() : m_documentItem.data()->pageviewObserver(); 0304 const int priority = m_isThumbnail ? THUMBNAILS_PRIO : PAGEVIEW_PRIO; 0305 0306 const qreal dpr = window()->devicePixelRatio(); 0307 0308 // Here we want to request the pixmap for the page, but it may happen that the page 0309 // already has the pixmap, thus requestPixmaps would not trigger pageHasChanged 0310 // and we would not call paint. Always call paint, if we don't have a pixmap 0311 // it's a noop. Requesting a page that already has a pixmap is also 0312 // almost a noop. 0313 // Ideally we would do one or the other but for now this is good enough 0314 paint(); 0315 { 0316 auto request = new Okular::PixmapRequest(observer, m_viewPort.pageNumber, width(), height(), dpr, priority, Okular::PixmapRequest::Asynchronous); 0317 request->setNormalizedRect(Okular::NormalizedRect(0, 0, 1, 1)); 0318 const Okular::Document::PixmapRequestFlag prf = Okular::Document::NoOption; 0319 m_documentItem.data()->document()->requestPixmaps({request}, prf); 0320 } 0321 } 0322 0323 void PageItem::paint() 0324 { 0325 Observer *observer = m_isThumbnail ? m_documentItem.data()->thumbnailObserver() : m_documentItem.data()->pageviewObserver(); 0326 const int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations; 0327 0328 const qreal dpr = window()->devicePixelRatio(); 0329 const QRect limits(QPoint(0, 0), QSize(width() * dpr, height() * dpr)); 0330 QPixmap pix(limits.size()); 0331 pix.setDevicePixelRatio(dpr); 0332 QPainter p(&pix); 0333 p.setRenderHint(QPainter::Antialiasing, false); 0334 PagePainter::paintPageOnPainter(&p, m_page, observer, flags, width(), height(), limits); 0335 p.end(); 0336 0337 m_buffer = pix.toImage(); 0338 0339 update(); 0340 } 0341 0342 // Protected slots 0343 void PageItem::pageHasChanged(int page, int flags) 0344 { 0345 if (m_viewPort.pageNumber == page) { 0346 if (flags == Okular::DocumentObserver::BoundingBox) { 0347 // skip bounding box updates 0348 // kDebug() << "32" << m_page->boundingBox(); 0349 } else if (flags == Okular::DocumentObserver::Pixmap) { 0350 // if pixmaps have updated, just repaint .. don't bother updating pixmaps AGAIN 0351 paint(); 0352 } else { 0353 m_redrawTimer->start(); 0354 } 0355 } 0356 } 0357 0358 void PageItem::checkBookmarksChanged() 0359 { 0360 if (!m_documentItem) { 0361 return; 0362 } 0363 0364 bool newBookmarked = m_documentItem.data()->document()->bookmarkManager()->isBookmarked(m_viewPort.pageNumber); 0365 if (m_bookmarked != newBookmarked) { 0366 m_bookmarked = newBookmarked; 0367 Q_EMIT bookmarkedChanged(); 0368 } 0369 0370 // TODO: check the page 0371 Q_EMIT bookmarksChanged(); 0372 } 0373 0374 void PageItem::contentXChanged() 0375 { 0376 if (!m_flickable || !m_flickable.data()->property("contentX").isValid()) { 0377 return; 0378 } 0379 0380 m_viewPort.rePos.normalizedX = m_flickable.data()->property("contentX").toReal() / (width() - m_flickable.data()->width()); 0381 } 0382 0383 void PageItem::contentYChanged() 0384 { 0385 if (!m_flickable || !m_flickable.data()->property("contentY").isValid()) { 0386 return; 0387 } 0388 0389 m_viewPort.rePos.normalizedY = m_flickable.data()->property("contentY").toReal() / (height() - m_flickable.data()->height()); 0390 } 0391 0392 void PageItem::setIsThumbnail(bool thumbnail) 0393 { 0394 if (thumbnail == m_isThumbnail) { 0395 return; 0396 } 0397 0398 m_isThumbnail = thumbnail; 0399 0400 /* 0401 m_redrawTimer->setInterval(thumbnail ? 0 : REDRAW_TIMEOUT); 0402 m_redrawTimer->setSingleShot(true); 0403 */ 0404 }