diff options
Diffstat (limited to 'src/pdfquick/PdfMultiPageView.qml')
-rw-r--r-- | src/pdfquick/PdfMultiPageView.qml | 275 |
1 files changed, 120 insertions, 155 deletions
diff --git a/src/pdfquick/PdfMultiPageView.qml b/src/pdfquick/PdfMultiPageView.qml index 2457c5010..194d7866e 100644 --- a/src/pdfquick/PdfMultiPageView.qml +++ b/src/pdfquick/PdfMultiPageView.qml @@ -1,41 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtPDF module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls import QtQuick.Pdf @@ -92,8 +59,8 @@ Item { */ function selectAll() { const currentItem = tableView.itemAtCell(tableView.cellAtPos(root.width / 2, root.height / 2)) - if (currentItem) - currentItem.selection.selectAll() + const pdfSelection = currentItem?.selection as PdfSelection + pdfSelection?.selectAll() } /*! @@ -106,9 +73,9 @@ Item { */ function copySelectionToClipboard() { const currentItem = tableView.itemAtCell(tableView.cellAtPos(root.width / 2, root.height / 2)) - console.log(lcMPV, "currentItem", currentItem, "sel", currentItem.selection.text) - if (currentItem) - currentItem.selection.copyToClipboard() + const pdfSelection = currentItem?.selection as PdfSelection + console.log(lcMPV, "currentItem", currentItem, "sel", pdfSelection?.text) + pdfSelection?.copyToClipboard() } // -------------------------------- @@ -125,9 +92,9 @@ Item { \c onCurrentPageChanged script) to update the part of the user interface that shows the current page number, such as a \l SpinBox. - \sa PdfNavigationStack::currentPage + \sa PdfPageNavigator::currentPage */ - property alias currentPage: navigationStack.currentPage + property alias currentPage: pageNavigator.currentPage /*! \qmlproperty bool PdfMultiPageView::backEnabled @@ -136,9 +103,9 @@ Item { This property indicates if it is possible to go back in the navigation history to a previous-viewed page. - \sa PdfNavigationStack::backAvailable, back() + \sa PdfPageNavigator::backAvailable, back() */ - property alias backEnabled: navigationStack.backAvailable + property alias backEnabled: pageNavigator.backAvailable /*! \qmlproperty bool PdfMultiPageView::forwardEnabled @@ -147,9 +114,9 @@ Item { This property indicates if it is possible to go to next location in the navigation history. - \sa PdfNavigationStack::forwardAvailable, forward() + \sa PdfPageNavigator::forwardAvailable, forward() */ - property alias forwardEnabled: navigationStack.forwardAvailable + property alias forwardEnabled: pageNavigator.forwardAvailable /*! \qmlmethod void PdfMultiPageView::back() @@ -158,9 +125,9 @@ Item { recently; or does nothing if there is no previous location on the navigation stack. - \sa PdfNavigationStack::back(), currentPage, backEnabled + \sa PdfPageNavigator::back(), currentPage, backEnabled */ - function back() { navigationStack.back() } + function back() { pageNavigator.back() } /*! \qmlmethod void PdfMultiPageView::forward() @@ -169,19 +136,19 @@ Item { method was called; or does nothing if there is no "next" location on the navigation stack. - \sa PdfNavigationStack::forward(), currentPage + \sa PdfPageNavigator::forward(), currentPage */ - function forward() { navigationStack.forward() } + function forward() { pageNavigator.forward() } /*! \qmlmethod void PdfMultiPageView::goToPage(int page) Scrolls the view to the given \a page number, if possible. - \sa PdfNavigationStack::jump(), currentPage + \sa PdfPageNavigator::jump(), currentPage */ function goToPage(page) { - if (page === navigationStack.currentPage) + if (page === pageNavigator.currentPage) return goToLocation(page, Qt.point(-1, -1), 0) } @@ -192,15 +159,22 @@ Item { Scrolls the view to the \a location on the \a page, if possible, and sets the \a zoom level. - \sa PdfNavigationStack::jump(), currentPage + \sa PdfPageNavigator::jump(), currentPage */ function goToLocation(page, location, zoom) { + if (tableView.rows === 0) { + // save this request for later + tableView.pendingRow = page + tableView.pendingLocation = location + tableView.pendingZoom = zoom + return + } if (zoom > 0) { - navigationStack.jumping = true // don't call navigationStack.update() because we will jump() instead + pageNavigator.jumping = true // don't call pageNavigator.update() because we will jump() instead root.renderScale = zoom - navigationStack.jumping = false + pageNavigator.jumping = false } - navigationStack.jump(page, location, zoom) // actually jump + pageNavigator.jump(page, location, zoom) // actually jump } /*! @@ -208,8 +182,6 @@ Item { This property holds the \l {QtQuick::Image::status}{rendering status} of the \l {currentPage}{current page}. - - \sa PdfPageImage::status */ property int currentPageRenderingStatus: Image.Null @@ -221,8 +193,6 @@ Item { This property holds the ratio of pixels to points. The default is \c 1, meaning one point (1/72 of an inch) equals 1 logical pixel. - - \sa PdfPageImage::status */ property real renderScale: 1 @@ -233,8 +203,6 @@ Item { The default value is \c 0 degrees (that is, no rotation relative to the orientation of the pages as stored in the PDF file). - - \sa PdfPageImage::rotation */ property real pageRotation: 0 @@ -300,8 +268,8 @@ Item { \qmlproperty string PdfMultiPageView::searchString This property holds the search string that the user may choose to search - for. It is typically used in a binding to the - \l {QtQuick.Controls::TextField::text}{text} property of a TextField. + for. It is typically used in a binding to the \c text property of a + TextField. \sa searchModel */ @@ -335,29 +303,40 @@ Item { TableView { id: tableView property bool debug: false - property vector2d jumpLocationMargin: Qt.vector2d(10, 10) // px from top-left corner + property real minScale: 0.1 + property real maxScale: 10 + property point jumpLocationMargin: Qt.point(10, 10) // px away from viewport edges anchors.fill: parent anchors.leftMargin: 2 - model: modelInUse && root.document ? root.document.pageCount : 0 - // workaround to make TableView do scheduleRebuildTable(RebuildOption::All) in cases when forceLayout() doesn't - property bool modelInUse: true - function rebuild() { - modelInUse = false - modelInUse = true - } - // end workaround + model: root.document ? root.document.pageCount : 0 rowSpacing: 6 property real rotationNorm: Math.round((360 + (root.pageRotation % 360)) % 360) property bool rot90: rotationNorm == 90 || rotationNorm == 270 onRot90Changed: forceLayout() onHeightChanged: forceLayout() onWidthChanged: forceLayout() - property size firstPagePointSize: document?.status === PdfDocument.Ready ? document.pagePointSize(0) : Qt.size(1, 1) - property real pageHolderWidth: Math.max(root.width, ((rot90 ? document?.maxPageHeight : document?.maxPageWidth) ?? 0) * root.renderScale) - columnWidthProvider: function(col) { return document ? pageHolderWidth + vscroll.width + 2 : 0 } - rowHeightProvider: function(row) { return (rot90 ? document.pagePointSize(row).width : document.pagePointSize(row).height) * root.renderScale } + property size firstPagePointSize: root.document?.status === PdfDocument.Ready ? root.document.pagePointSize(0) : Qt.size(1, 1) + property real pageHolderWidth: Math.max(root.width, ((rot90 ? root.document?.maxPageHeight : root.document?.maxPageWidth) ?? 0) * root.renderScale) + columnWidthProvider: function(col) { return root.document ? pageHolderWidth + vscroll.width + 2 : 0 } + rowHeightProvider: function(row) { return (rot90 ? root.document.pagePointSize(row).width : root.document.pagePointSize(row).height) * root.renderScale } + + // delayed-jump feature in case the user called goToPage() or goToLocation() too early + property int pendingRow: -1 + property point pendingLocation + property real pendingZoom: -1 + onRowsChanged: { + if (rows > 0 && tableView.pendingRow >= 0) { + console.log(lcMPV, "initiating delayed jump to page", tableView.pendingRow, "loc", tableView.pendingLocation, "zoom", tableView.pendingZoom) + root.goToLocation(tableView.pendingRow, tableView.pendingLocation, tableView.pendingZoom) + tableView.pendingRow = -1 + tableView.pendingLocation = Qt.point(-1, -1) + tableView.pendingZoom = -1 + } + } + delegate: Rectangle { id: pageHolder + required property int index color: tableView.debug ? "beige" : "transparent" Text { visible: tableView.debug @@ -372,12 +351,12 @@ Item { height: image.height rotation: root.pageRotation anchors.centerIn: pinch.active ? undefined : parent - property size pagePointSize: document.pagePointSize(index) + property size pagePointSize: root.document.pagePointSize(pageHolder.index) property real pageScale: image.paintedWidth / pagePointSize.width PdfPageImage { id: image document: root.document - currentPage: index + currentFrame: pageHolder.index asynchronous: true fillMode: Image.PreserveAspectFit width: paper.pagePointSize.width * root.renderScale @@ -391,7 +370,7 @@ Item { searchHighlights.update() } onStatusChanged: { - if (index === navigationStack.currentPage) + if (pageHolder.index === pageNavigator.currentPage) root.currentPageRenderingStatus = status } } @@ -407,7 +386,7 @@ Item { id: searchHighlights function update() { // paths could be a binding, but we need to be able to "kick" it sometimes - paths = searchModel.boundingPolygonsOnPage(index) + paths = searchModel.boundingPolygonsOnPage(pageHolder.index) } } } @@ -428,7 +407,7 @@ Item { } Shape { anchors.fill: parent - visible: image.status === Image.Ready && searchModel.currentPage === index + visible: image.status === Image.Ready && searchModel.currentPage === pageHolder.index ShapePath { strokeWidth: style.currentSearchResultStrokeWidth strokeColor: style.currentSearchResultStrokeColor @@ -441,11 +420,10 @@ Item { } PinchHandler { id: pinch - minimumScale: 0.1 - maximumScale: root.renderScale < 4 ? 2 : 1 + minimumScale: tableView.minScale / root.renderScale + maximumScale: Math.max(1, tableView.maxScale / root.renderScale) minimumRotation: root.pageRotation maximumRotation: root.pageRotation - enabled: image.sourceSize.width < 5000 onActiveChanged: if (active) { paper.z = 10 @@ -456,12 +434,16 @@ Item { const centroidInFlickable = tableView.mapFromItem(paper, pinch.centroid.position.x, pinch.centroid.position.y) const newSourceWidth = image.sourceSize.width * paper.scale const ratio = newSourceWidth / image.sourceSize.width - console.log(lcMPV, "pinch ended on page", index, "with centroid", pinch.centroid.position, centroidInPoints, "wrt flickable", centroidInFlickable, + console.log(lcMPV, "pinch ended on page", pageHolder.index, + "with scale", paper.scale.toFixed(3), "ratio", ratio.toFixed(3), + "centroid", pinch.centroid.position, centroidInPoints, + "wrt flickable", centroidInFlickable, "page at", pageHolder.x.toFixed(2), pageHolder.y.toFixed(2), "contentX/Y were", tableView.contentX.toFixed(2), tableView.contentY.toFixed(2)) if (ratio > 1.1 || ratio < 0.9) { const centroidOnPage = Qt.point(centroidInPoints.x * root.renderScale * ratio, centroidInPoints.y * root.renderScale * ratio) paper.scale = 1 + pinch.persistentScale = 1 paper.x = 0 paper.y = 0 root.renderScale *= ratio @@ -506,57 +488,31 @@ Item { model: PdfLinkModel { id: linkModel document: root.document - page: image.currentPage + page: image.currentFrame } - delegate: Shape { - required property rect rect - required property url url - required property int page - required property point location - required property real zoom - x: rect.x * paper.pageScale - y: rect.y * paper.pageScale - width: rect.width * paper.pageScale - height: rect.height * paper.pageScale + delegate: PdfLinkDelegate { + x: rectangle.x * paper.pageScale + y: rectangle.y * paper.pageScale + width: rectangle.width * paper.pageScale + height: rectangle.height * paper.pageScale visible: image.status === Image.Ready - ShapePath { - strokeWidth: style.linkUnderscoreStrokeWidth - strokeColor: style.linkUnderscoreColor - strokeStyle: style.linkUnderscoreStrokeStyle - dashPattern: style.linkUnderscoreDashPattern - startX: 0; startY: height - PathLine { x: width; y: height } - } - HoverHandler { - id: linkHH - cursorShape: Qt.PointingHandCursor - } - TapHandler { - onTapped: { - if (page >= 0) - root.goToLocation(page, location, zoom) + onTapped: + (link) => { + if (link.page >= 0) + root.goToLocation(link.page, link.location, link.zoom) else Qt.openUrlExternally(url) } - } - ToolTip { - visible: linkHH.hovered - delay: 1000 - text: page >= 0 ? - ("page " + (page + 1) + - " location " + location.x.toFixed(1) + ", " + location.y.toFixed(1) + - " zoom " + zoom) : url - } } } PdfSelection { id: selection anchors.fill: parent document: root.document - page: image.currentPage + page: image.currentFrame renderScale: image.renderScale - fromPoint: textSelectionDrag.centroid.pressPosition - toPoint: textSelectionDrag.centroid.position + from: textSelectionDrag.centroid.pressPosition + to: textSelectionDrag.centroid.position hold: !textSelectionDrag.active && !mouseClickHandler.pressed onTextChanged: root.selectedText = text focus: true @@ -575,7 +531,7 @@ Item { ? Qt.point((tableView.contentX - currentItem.x + tableView.jumpLocationMargin.x) / root.renderScale, (tableView.contentY - currentItem.y + tableView.jumpLocationMargin.y) / root.renderScale) : Qt.point(0, 0) // maybe the delegate wasn't loaded yet - navigationStack.jump(cell.y, currentLocation, root.renderScale) + pageNavigator.jump(cell.y, currentLocation, root.renderScale) } onActiveChanged: if (!active ) { // When the scrollbar stops moving, tell navstack where we are, so as to update currentPage etc. @@ -585,64 +541,75 @@ Item { ? Qt.point((tableView.contentX - currentItem.x + tableView.jumpLocationMargin.x) / root.renderScale, (tableView.contentY - currentItem.y + tableView.jumpLocationMargin.y) / root.renderScale) : Qt.point(0, 0) // maybe the delegate wasn't loaded yet - navigationStack.update(cell.y, currentLocation, root.renderScale) + pageNavigator.update(cell.y, currentLocation, root.renderScale) } } ScrollBar.horizontal: ScrollBar { } } onRenderScaleChanged: { - // if navigationStack.jumped changes the scale, don't turn around and update the stack again; + // if pageNavigator.jumped changes the scale, don't turn around and update the stack again; // and don't force layout either, because positionViewAtCell() will do that - if (navigationStack.jumping) + if (pageNavigator.jumping) return - // make TableView rebuild from scratch, because otherwise it doesn't know the delegates are changing size - tableView.rebuild() + // page size changed: TableView needs to redo layout to avoid overlapping delegates or gaps between them + tableView.forceLayout() const cell = tableView.cellAtPos(root.width / 2, root.height / 2) const currentItem = tableView.itemAtCell(cell) if (currentItem) { const currentLocation = Qt.point((tableView.contentX - currentItem.x + tableView.jumpLocationMargin.x) / root.renderScale, (tableView.contentY - currentItem.y + tableView.jumpLocationMargin.y) / root.renderScale) - navigationStack.update(cell.y, currentLocation, renderScale) + pageNavigator.update(cell.y, currentLocation, renderScale) } } - PdfNavigationStack { - id: navigationStack + PdfPageNavigator { + id: pageNavigator property bool jumping: false property int previousPage: 0 - onJumped: function(page, location, zoom) { + onJumped: function(current) { jumping = true - root.renderScale = zoom - if (location.y < 0) { + if (current.zoom > 0) + root.renderScale = current.zoom + const pageSize = root.document.pagePointSize(current.page) + if (current.location.y < 0) { // invalid to indicate that a specific location was not needed, // so attempt to position the new page just as the current page is const previousPageDelegate = tableView.itemAtCell(0, previousPage) const currentYOffset = previousPageDelegate ? tableView.contentY - previousPageDelegate.y : 0 - tableView.positionViewAtRow(page, Qt.AlignTop, currentYOffset) - console.log(lcMPV, "going from page", previousPage, "to", page, "offset", currentYOffset, + tableView.positionViewAtRow(current.page, Qt.AlignTop, currentYOffset) + console.log(lcMPV, "going from page", previousPage, "to", current.page, "offset", currentYOffset, + "ended up @", tableView.contentX.toFixed(1) + ", " + tableView.contentY.toFixed(1)) + } else if (current.rectangles.length > 0) { + // jump to a search result and position the covered area within the viewport + pageSize.width *= root.renderScale + pageSize.height *= root.renderScale + const rectPts = current.rectangles[0] + const rectPx = Qt.rect(rectPts.x * root.renderScale - tableView.jumpLocationMargin.x, + rectPts.y * root.renderScale - tableView.jumpLocationMargin.y, + rectPts.width * root.renderScale + tableView.jumpLocationMargin.x * 2, + rectPts.height * root.renderScale + tableView.jumpLocationMargin.y * 2) + tableView.positionViewAtCell(0, current.page, TableView.Contain, Qt.point(0, 0), rectPx) + console.log(lcMPV, "going to zoom", root.renderScale, "rect", rectPx, "on page", current.page, "ended up @", tableView.contentX.toFixed(1) + ", " + tableView.contentY.toFixed(1)) } else { // jump to a page and position the given location relative to the top-left corner of the viewport - var pageSize = root.document.pagePointSize(page) pageSize.width *= root.renderScale pageSize.height *= root.renderScale - const xOffsetLimit = Math.max(0, pageSize.width - root.width) - const offset = Qt.point(Math.max(-xOffsetLimit, Math.min(xOffsetLimit, - location.x * root.renderScale - tableView.jumpLocationMargin.x)), - Math.max(0, location.y * root.renderScale - tableView.jumpLocationMargin.y)) - tableView.positionViewAtCell(0, page, Qt.AlignLeft | Qt.AlignTop, offset) - console.log(lcMPV, "going to zoom", zoom, "loc", location, "on page", page, + const rectPx = Qt.rect(current.location.x * root.renderScale - tableView.jumpLocationMargin.x, + current.location.y * root.renderScale - tableView.jumpLocationMargin.y, + tableView.jumpLocationMargin.x * 2, tableView.jumpLocationMargin.y * 2) + tableView.positionViewAtCell(0, current.page, TableView.AlignLeft | TableView.AlignTop, Qt.point(0, 0), rectPx) + console.log(lcMPV, "going to zoom", root.renderScale, "loc", current.location, "on page", current.page, "ended up @", tableView.contentX.toFixed(1) + ", " + tableView.contentY.toFixed(1)) } jumping = false - previousPage = page + previousPage = current.page } - onCurrentPageChanged: searchModel.currentPage = currentPage property url documentSource: root.document.source onDocumentSourceChanged: { - navigationStack.clear() + pageNavigator.clear() root.resetScale() tableView.contentX = 0 tableView.contentY = 0 @@ -651,8 +618,6 @@ Item { PdfSearchModel { id: searchModel document: root.document === undefined ? null : root.document - // TODO maybe avoid jumping if the result is already fully visible in the viewport - onCurrentResultBoundingRectChanged: root.goToLocation(currentPage, - Qt.point(currentResultBoundingRect.x, currentResultBoundingRect.y), 0) + onCurrentResultChanged: pageNavigator.jump(currentResultLink) } } |