diff options
Diffstat (limited to 'src/pdfwidgets/qpdfview.cpp')
-rw-r--r-- | src/pdfwidgets/qpdfview.cpp | 215 |
1 files changed, 195 insertions, 20 deletions
diff --git a/src/pdfwidgets/qpdfview.cpp b/src/pdfwidgets/qpdfview.cpp index 831b51515..a67667fed 100644 --- a/src/pdfwidgets/qpdfview.cpp +++ b/src/pdfwidgets/qpdfview.cpp @@ -8,16 +8,24 @@ #include "qpdfpagerenderer.h" #include <QGuiApplication> +#include <QLoggingCategory> #include <QPainter> #include <QPaintEvent> #include <QPdfDocument> #include <QPdfPageNavigator> +#include <QPdfSearchModel> #include <QScreen> #include <QScrollBar> -#include <QScroller> QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(qLcLink, "qt.pdf.links") +//#define DEBUG_LINKS + +static const QColor SearchResultHighlight("#80B0C4DE"); +static const QColor CurrentSearchResultHighlight(Qt::cyan); +static const int CurrentSearchResultWidth(2); + QPdfViewPrivate::QPdfViewPrivate(QPdfView *q) : q_ptr(q) , m_document(nullptr) @@ -98,8 +106,9 @@ void QPdfViewPrivate::setViewport(QRect viewport) const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2); int currentPage = 0; - for (auto it = m_documentLayout.pageGeometries.cbegin(); it != m_documentLayout.pageGeometries.cend(); ++it) { - const QRect pageGeometry = it.value(); + for (auto it = m_documentLayout.pageGeometryAndScale.cbegin(); + it != m_documentLayout.pageGeometryAndScale.cend(); ++it) { + const QRect pageGeometry = it.value().first; if (pageGeometry.intersects(currentPageLine)) { currentPage = it.key(); break; @@ -174,7 +183,7 @@ QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const if (!m_document || m_document->status() != QPdfDocument::Status::Ready) return documentLayout; - QHash<int, QRect> pageGeometries; + QHash<int, QPair<QRect, qreal>> pageGeometryAndScale; const int pageCount = m_document->pageCount(); @@ -186,24 +195,28 @@ QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const // calculate page sizes for (int page = startPage; page < endPage; ++page) { QSize pageSize; + qreal pageScale = m_zoomFactor; if (m_zoomMode == QPdfView::ZoomMode::Custom) { pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize(); } else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) { pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize(); - const qreal factor = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) / - qreal(pageSize.width())); - pageSize *= factor; + pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) / + qreal(pageSize.width())); + pageSize *= pageScale; } else if (m_zoomMode == QPdfView::ZoomMode::FitInView) { const QSize viewportSize(m_viewport.size() + QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing)); pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize(); - pageSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio); + QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio); + // because of KeepAspectRatio, the ratio of widths should be the same as the ratio of heights + pageScale = qreal(scaledSize.width()) / qreal(pageSize.width()); + pageSize = scaledSize; } totalWidth = qMax(totalWidth, pageSize.width()); - pageGeometries[page] = QRect(QPoint(0, 0), pageSize); + pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale}; } totalWidth += m_documentMargins.left() + m_documentMargins.right(); @@ -212,19 +225,19 @@ QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const // calculate page positions for (int page = startPage; page < endPage; ++page) { - const QSize pageSize = pageGeometries[page].size(); + const QSize pageSize = pageGeometryAndScale[page].first.size(); // center horizontal inside the viewport const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2; - pageGeometries[page].moveTopLeft(QPoint(pageX, pageY)); + pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY)); pageY += pageSize.height() + m_pageSpacing; } pageY += m_documentMargins.bottom(); - documentLayout.pageGeometries = pageGeometries; + documentLayout.pageGeometryAndScale = pageGeometryAndScale; // calculate overall document size documentLayout.documentSize = QSize(totalWidth, pageY); @@ -234,11 +247,26 @@ QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const qreal QPdfViewPrivate::yPositionForPage(int pageNumber) const { - const auto it = m_documentLayout.pageGeometries.constFind(pageNumber); - if (it == m_documentLayout.pageGeometries.cend()) + const auto it = m_documentLayout.pageGeometryAndScale.constFind(pageNumber); + if (it == m_documentLayout.pageGeometryAndScale.cend()) return 0.0; - return (*it).y(); + return (*it).first.y(); +} + +QTransform QPdfViewPrivate::screenScaleTransform(int page) const +{ + qreal scale = m_screenResolution * m_zoomFactor; + switch (m_zoomMode) { + case QPdfView::ZoomMode::FitToWidth: + case QPdfView::ZoomMode::FitInView: + scale = m_screenResolution * m_documentLayout.pageGeometryAndScale[page].second; + break; + default: + break; + } + + return QTransform::fromScale(scale, scale); } void QPdfViewPrivate::updateDocumentLayout() @@ -281,8 +309,7 @@ QPdfView::QPdfView(QWidget *parent) verticalScrollBar()->setSingleStep(20); horizontalScrollBar()->setSingleStep(20); - QScroller::grabGesture(this); - + setMouseTracking(true); d->calculateViewport(); } @@ -317,6 +344,7 @@ void QPdfView::setDocument(QPdfDocument *document) [d](){ d->documentStatusChanged(); }); d->m_pageRenderer->setDocument(d->m_document); + d->m_linkModel.setDocument(d->m_document); d->documentStatusChanged(); } @@ -329,6 +357,69 @@ QPdfDocument *QPdfView::document() const } /*! + \since 6.6 + \property QPdfView::searchModel + + If this property is set, QPdfView draws highlight rectangles over the + search results provided by \l QPdfSearchModel::resultsOnPage(). By default + it is \c nullptr. +*/ +void QPdfView::setSearchModel(QPdfSearchModel *searchModel) +{ + Q_D(QPdfView); + if (d->m_searchModel == searchModel) + return; + + if (d->m_searchModel) + d->m_searchModel->disconnect(this); + + d->m_searchModel = searchModel; + emit searchModelChanged(searchModel); + + if (searchModel) { + connect(searchModel, &QPdfSearchModel::dataChanged, this, + [this](const QModelIndex &, const QModelIndex &, const QList<int> &) { update(); }); + } + setCurrentSearchResultIndex(-1); +} + +QPdfSearchModel *QPdfView::searchModel() const +{ + Q_D(const QPdfView); + return d->m_searchModel; +} + +/*! + \since 6.6 + \property QPdfView::currentSearchResultIndex + + If this property is set to a positive number, and \l searchModel is set, + QPdfView draws a frame around the search result provided by + \l QPdfSearchModel at the given index. For example, if QPdfSearchModel is + used as the model for a QListView, you can keep this property updated by + connecting QItemSelectionModel::currentChanged() from + QListView::selectionModel() to a function that will in turn call this function. + + By default it is \c -1, so that no search results are framed. +*/ +void QPdfView::setCurrentSearchResultIndex(int currentResult) +{ + Q_D(QPdfView); + if (d->m_currentSearchResultIndex == currentResult) + return; + + d->m_currentSearchResultIndex = currentResult; + emit currentSearchResultIndexChanged(currentResult); + viewport()->update(); //update(); +} + +int QPdfView::currentSearchResultIndex() const +{ + Q_D(const QPdfView); + return d->m_currentSearchResultIndex; +} + +/*! This accessor returns the navigation stack that will handle back/forward navigation. */ QPdfPageNavigator *QPdfView::pageNavigator() const @@ -496,9 +587,9 @@ void QPdfView::paintEvent(QPaintEvent *event) painter.fillRect(event->rect(), palette().brush(QPalette::Dark)); painter.translate(-d->m_viewport.x(), -d->m_viewport.y()); - for (auto it = d->m_documentLayout.pageGeometries.cbegin(); - it != d->m_documentLayout.pageGeometries.cend(); ++it) { - const QRect pageGeometry = it.value(); + for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin(); + it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) { + const QRect pageGeometry = it.value().first; if (pageGeometry.intersects(d->m_viewport)) { // page needs to be painted painter.fillRect(pageGeometry, Qt::white); @@ -510,6 +601,44 @@ void QPdfView::paintEvent(QPaintEvent *event) } else { d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF()); } + + const QTransform scaleTransform = d->screenScaleTransform(page); +#ifdef DEBUG_LINKS + const QString fmt = u"page %1 @ %2, %3"_s; + d->m_linkModel.setPage(page); + const int linkCount = d->m_linkModel.rowCount({}); + for (int i = 0; i < linkCount; ++i) { + const QRectF linkBounds = scaleTransform.mapRect( + d->m_linkModel.data(d->m_linkModel.index(i), + int(QPdfLinkModel::Role::Rect)).toRectF()) + .translated(pageGeometry.topLeft()); + painter.setPen(Qt::blue); + painter.drawRect(linkBounds); + painter.setPen(Qt::red); + const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i), + int(QPdfLinkModel::Role::Location)).toPoint(); + // TODO maybe draw destination URL if that's what it is + painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2), + fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i), + int(QPdfLinkModel::Role::Page)).toInt()) + .arg(loc.x()).arg(loc.y())); + } +#endif + if (d->m_searchModel) { + for (const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) { + for (const QRectF &rect : result.rectangles()) + painter.fillRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()), SearchResultHighlight); + } + + if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) { + const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex); + if (cur.page() == page) { + painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth}); + for (const auto &rect : cur.rectangles()) + painter.drawRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft())); + } + } + } } } } @@ -533,6 +662,52 @@ void QPdfView::scrollContentsBy(int dx, int dy) d->calculateViewport(); } +void QPdfView::mousePressEvent(QMouseEvent *event) +{ + Q_ASSERT(event->isAccepted()); +} + +void QPdfView::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QPdfView); + for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin(); + it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) { + const int page = it.key(); + const QTransform screenInvTransform = d->screenScaleTransform(page).inverted(); + const QRect pageGeometry = it.value().first; + if (pageGeometry.contains(event->position().toPoint())) { + const QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()); + d->m_linkModel.setPage(page); + auto dest = d->m_linkModel.linkAt(posInPoints); + setCursor(dest.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor); + if (dest.isValid()) + qCDebug(qLcLink) << event->position() << ":" << posInPoints << "pt ->" << dest; + } + } +} + +void QPdfView::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QPdfView); + for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin(); + it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) { + const int page = it.key(); + const QTransform screenInvTransform = d->screenScaleTransform(page).inverted(); + const QRect pageGeometry = it.value().first; + if (pageGeometry.contains(event->position().toPoint())) { + const QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()); + d->m_linkModel.setPage(page); + auto dest = d->m_linkModel.linkAt(posInPoints); + if (dest.isValid()) { + qCDebug(qLcLink) << event << ": jumping to" << dest; + d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom()); + // TODO scroll and zoom to where the link tells us to + } + return; + } + } +} + QT_END_NAMESPACE #include "moc_qpdfview.cpp" |