diff --git a/examples/pdf/pdfviewer/viewer.qml b/examples/pdf/pdfviewer/viewer.qml index a2bccab445ca1a5b83bd3cc94d67ab79fc720220..777a9660bb7e452f30c8ec3d9561682395b51895 100644 --- a/examples/pdf/pdfviewer/viewer.qml +++ b/examples/pdf/pdfviewer/viewer.qml @@ -80,57 +80,57 @@ ApplicationWindow { ToolButton { action: Action { shortcut: StandardKey.ZoomIn - enabled: pageView.sourceSize.width < 10000 + enabled: view.sourceSize.width < 10000 icon.source: "resources/zoom-in.svg" - onTriggered: pageView.renderScale *= root.scaleStep + onTriggered: view.renderScale *= root.scaleStep } } ToolButton { action: Action { shortcut: StandardKey.ZoomOut - enabled: pageView.sourceSize.width > 50 + enabled: view.sourceSize.width > 50 icon.source: "resources/zoom-out.svg" - onTriggered: pageView.renderScale /= root.scaleStep + onTriggered: view.renderScale /= root.scaleStep } } ToolButton { action: Action { icon.source: "resources/zoom-fit-width.svg" - onTriggered: pageView.scaleToWidth(root.contentItem.width, root.contentItem.height) + onTriggered: view.scaleToWidth(root.contentItem.width, root.contentItem.height) } } ToolButton { action: Action { icon.source: "resources/zoom-fit-best.svg" - onTriggered: pageView.scaleToPage(root.contentItem.width, root.contentItem.height) + onTriggered: view.scaleToPage(root.contentItem.width, root.contentItem.height) } } ToolButton { action: Action { shortcut: "Ctrl+0" icon.source: "resources/zoom-original.svg" - onTriggered: pageView.resetScale() + onTriggered: view.resetScale() } } ToolButton { action: Action { shortcut: "Ctrl+L" icon.source: "resources/rotate-left.svg" - onTriggered: pageView.rotation -= 90 + onTriggered: view.pageRotation -= 90 } } ToolButton { action: Action { shortcut: "Ctrl+R" icon.source: "resources/rotate-right.svg" - onTriggered: pageView.rotation += 90 + onTriggered: view.pageRotation += 90 } } ToolButton { action: Action { icon.source: "resources/go-previous-view-page.svg" - enabled: pageView.backEnabled - onTriggered: pageView.back() + enabled: view.backEnabled + onTriggered: view.back() } ToolTip.visible: enabled && hovered ToolTip.delay: 2000 @@ -141,22 +141,22 @@ ApplicationWindow { from: 1 to: document.pageCount editable: true - value: pageView.currentPage + 1 - onValueModified: pageView.goToPage(value - 1) + value: view.currentPage + 1 + onValueModified: view.goToPage(value - 1) Shortcut { sequence: StandardKey.MoveToPreviousPage - onActivated: pageView.goToPage(currentPageSB.value - 2) + onActivated: view.goToPage(currentPageSB.value - 2) } Shortcut { sequence: StandardKey.MoveToNextPage - onActivated: pageView.goToPage(currentPageSB.value) + onActivated: view.goToPage(currentPageSB.value) } } ToolButton { action: Action { icon.source: "resources/go-next-view-page.svg" - enabled: pageView.forwardEnabled - onTriggered: pageView.forward() + enabled: view.forwardEnabled + onTriggered: view.forward() } ToolTip.visible: enabled && hovered ToolTip.delay: 2000 @@ -166,8 +166,8 @@ ApplicationWindow { action: Action { shortcut: StandardKey.Copy icon.source: "resources/edit-copy.svg" - enabled: pageView.selectedText !== "" - onTriggered: pageView.copySelectionToClipboard() + enabled: view.selectedText !== "" + onTriggered: view.copySelectionToClipboard() } } Shortcut { @@ -199,9 +199,9 @@ ApplicationWindow { } } - PdfPageView { - id: pageView - x: searchDrawer.position * searchDrawer.width // TODO binding gets broken during centering + PdfScrollablePageView { + id: view + anchors.fill: parent document: PdfDocument { id: document source: Qt.resolvedUrl(root.source) @@ -210,21 +210,6 @@ ApplicationWindow { searchString: searchField.text } - WheelHandler { - rotationScale: 15 - target: pageView - property: "x" - orientation: Qt.Horizontal - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - WheelHandler { - rotationScale: 15 - target: pageView - property: "y" - orientation: Qt.Vertical - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - Drawer { id: searchDrawer edge: Qt.LeftEdge @@ -249,7 +234,7 @@ ApplicationWindow { action: Action { icon.source: "resources/go-up-search.svg" shortcut: StandardKey.FindPrevious - onTriggered: pageView.searchBack() + onTriggered: view.searchBack() } ToolTip.visible: enabled && hovered ToolTip.delay: 2000 @@ -279,7 +264,7 @@ ApplicationWindow { action: Action { icon.source: "resources/go-down-search.svg" shortcut: StandardKey.FindNext - onTriggered: pageView.searchForward() + onTriggered: view.searchForward() } ToolTip.visible: enabled && hovered ToolTip.delay: 2000 @@ -291,7 +276,7 @@ ApplicationWindow { Layout.fillWidth: true Layout.fillHeight: true clip: true - model: pageView.searchModel + model: view.searchModel ScrollBar.vertical: ScrollBar { } delegate: ItemDelegate { width: parent ? parent.width : 0 @@ -299,8 +284,8 @@ ApplicationWindow { highlighted: ListView.isCurrentItem onClicked: { searchResultsList.currentIndex = index - pageView.goToLocation(page, location, 0) - pageView.searchModel.currentResult = indexOnPage + view.goToLocation(page, location, 0) + view.searchModel.currentResult = indexOnPage } } } @@ -308,9 +293,9 @@ ApplicationWindow { } footer: Label { - property size implicitPointSize: document.pagePointSize(pageView.currentPage) - text: "page " + (pageView.currentPage + 1) + " of " + document.pageCount + - " scale " + pageView.renderScale.toFixed(2) + + property size implicitPointSize: document.pagePointSize(view.currentPage) + text: "page " + (view.currentPage + 1) + " of " + document.pageCount + + " scale " + view.renderScale.toFixed(2) + " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + "pts" visible: document.status === PdfDocument.Ready } diff --git a/src/pdf/quick/plugin.cpp b/src/pdf/quick/plugin.cpp index a831a09b63d280083643d49a49d92bf02f4d2ec4..bb68a817ea874670594e54ac826636bd3e69440d 100644 --- a/src/pdf/quick/plugin.cpp +++ b/src/pdf/quick/plugin.cpp @@ -90,6 +90,7 @@ public: qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfPageView.qml"), uri, 5, 15, "PdfPageView"); qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfMultiPageView.qml"), uri, 5, 15, "PdfMultiPageView"); + qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfScrollablePageView.qml"), uri, 5, 15, "PdfScrollablePageView"); } }; diff --git a/src/pdf/quick/qml/PdfScrollablePageView.qml b/src/pdf/quick/qml/PdfScrollablePageView.qml new file mode 100644 index 0000000000000000000000000000000000000000..59bec04a23d75b199717a2d1d5b0dccab3c2fb59 --- /dev/null +++ b/src/pdf/quick/qml/PdfScrollablePageView.qml @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Pdf 5.15 +import QtQuick.Shapes 1.14 +import Qt.labs.animation 1.0 + +Flickable { + // public API + // TODO 5.15: required property + property var document: undefined + property bool debug: false + property alias status: image.status + + property alias selectedText: selection.text + function copySelectionToClipboard() { + selection.copyToClipboard() + } + + // page navigation + property alias currentPage: navigationStack.currentPage + property alias backEnabled: navigationStack.backAvailable + property alias forwardEnabled: navigationStack.forwardAvailable + function back() { navigationStack.back() } + function forward() { navigationStack.forward() } + function goToPage(page) { goToLocation(page, Qt.point(0, 0), 0) } + function goToLocation(page, location, zoom) { + if (zoom > 0) + root.renderScale = zoom + navigationStack.push(page, location, zoom) + } + + // page scaling + property real renderScale: 1 + property real pageRotation: 0 + property alias sourceSize: image.sourceSize + function resetScale() { + paper.scale = 1 + root.renderScale = 1 + } + function scaleToWidth(width, height) { + var pagePointSize = document.pagePointSize(navigationStack.currentPage) + root.renderScale = root.width / (paper.rot90 ? pagePointSize.height : pagePointSize.width) + if (debug) + console.log("scaling", pagePointSize, "to fit", root.width, "rotated?", paper.rot90, "scale", root.renderScale) + root.contentX = 0 + root.contentY = 0 + } + function scaleToPage(width, height) { + + var pagePointSize = document.pagePointSize(navigationStack.currentPage) + root.renderScale = Math.min( + root.width / (paper.rot90 ? pagePointSize.height : pagePointSize.width), + root.height / (paper.rot90 ? pagePointSize.width : pagePointSize.height) ) + root.contentX = 0 + root.contentY = 0 + } + + // text search + property alias searchModel: searchModel + property alias searchString: searchModel.searchString + function searchBack() { --searchModel.currentResult } + function searchForward() { ++searchModel.currentResult } + + // implementation + id: root + contentWidth: paper.width + contentHeight: paper.height + ScrollBar.vertical: ScrollBar { } + ScrollBar.horizontal: ScrollBar { } + + onRenderScaleChanged: { + image.sourceSize.width = document.pagePointSize(navigationStack.currentPage).width * renderScale + image.sourceSize.height = 0 + paper.scale = 1 + } + + PdfSelection { + id: selection + document: root.document + page: navigationStack.currentPage + fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / image.pageScale, + textSelectionDrag.centroid.pressPosition.y / image.pageScale) + toPoint: Qt.point(textSelectionDrag.centroid.position.x / image.pageScale, + textSelectionDrag.centroid.position.y / image.pageScale) + hold: !textSelectionDrag.active && !tapHandler.pressed + } + + PdfSearchModel { + id: searchModel + document: root.document === undefined ? null : root.document + onCurrentPageChanged: root.goToPage(currentPage) + } + + PdfNavigationStack { + id: navigationStack + onCurrentPageChanged: searchModel.currentPage = currentPage + // TODO onCurrentLocationChanged: position currentLocation.x and .y in middle // currentPageChanged() MUST occur first! + onCurrentZoomChanged: root.renderScale = currentZoom + // TODO deal with horizontal location (need WheelHandler or Flickable probably) + } + + Rectangle { + id: paper + width: rot90 ? image.height : image.width + height: rot90 ? image.width : image.height + property real rotationModulus: Math.abs(root.pageRotation % 180) + property bool rot90: rotationModulus > 45 && rotationModulus < 135 + + Image { + id: image + currentFrame: navigationStack.currentPage + source: document.status === PdfDocument.Ready ? document.source : "" + asynchronous: true + fillMode: Image.PreserveAspectFit + rotation: root.pageRotation + anchors.centerIn: parent + property real pageScale: image.paintedWidth / document.pagePointSize(navigationStack.currentPage).width + } + + Shape { + anchors.fill: parent + opacity: 0.25 + visible: image.status === Image.Ready + ShapePath { + strokeWidth: 1 + strokeColor: "cyan" + fillColor: "steelblue" + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: searchModel.currentPageBoundingPolygons + } + } + ShapePath { + strokeWidth: 1 + strokeColor: "orange" + fillColor: "cyan" + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: searchModel.currentResultBoundingPolygons + } + } + ShapePath { + fillColor: "orange" + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: selection.geometry + } + } + } + + Repeater { + model: PdfLinkModel { + id: linkModel + document: root.document + page: navigationStack.currentPage + } + delegate: Rectangle { + color: "transparent" + border.color: "lightgrey" + x: rect.x * image.pageScale + y: rect.y * image.pageScale + width: rect.width * image.pageScale + height: rect.height * image.pageScale + MouseArea { // TODO switch to TapHandler / HoverHandler in 5.15 + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (page >= 0) + navigationStack.push(page, Qt.point(0, 0), root.renderScale) + else + Qt.openUrlExternally(url) + } + } + } + } + + PinchHandler { + id: pinch + minimumScale: 0.1 + maximumScale: root.renderScale < 4 ? 2 : 1 + minimumRotation: 0 + maximumRotation: 0 + enabled: image.sourceSize.width < 5000 + onActiveChanged: + if (!active) { + var newSourceWidth = image.sourceSize.width * paper.scale + var ratio = newSourceWidth / image.sourceSize.width + if (ratio > 1.1 || ratio < 0.9) { + paper.scale = 1 + root.renderScale *= ratio + } + // TODO adjust contentX/Y to position the page so the same region is visible + paper.x = 0 + paper.y = 0 + } + grabPermissions: PointerHandler.CanTakeOverFromAnything + } + DragHandler { + id: pageMovingMiddleMouseDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + acceptedButtons: Qt.MiddleButton + snapMode: DragHandler.NoSnap + } + DragHandler { + id: textSelectionDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + target: null + } + TapHandler { + id: tapHandler + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + } + } +} diff --git a/src/pdf/quick/qquickpdflinkmodel.cpp b/src/pdf/quick/qquickpdflinkmodel.cpp index a3f552d17df537ecdf213cf901e7a3588f8554ee..f2ff3fd22cf330589228260fd6c6b4cbbe83ab1b 100644 --- a/src/pdf/quick/qquickpdflinkmodel.cpp +++ b/src/pdf/quick/qquickpdflinkmodel.cpp @@ -96,7 +96,7 @@ QT_BEGIN_NAMESPACE \endqml \note General-purpose PDF viewing capabilities are provided by - \l PdfPageView and \l PdfMultiPageView. PdfLinkModel is only needed + \l PdfScrollablePageView and \l PdfMultiPageView. PdfLinkModel is only needed when building PDF view components from scratch. */ diff --git a/src/pdf/quick/quick.pro b/src/pdf/quick/quick.pro index a0a39d41459df004533d712c780b2641205a71fc..b62b80346ec62176f9a45084093d2bb579095d68 100644 --- a/src/pdf/quick/quick.pro +++ b/src/pdf/quick/quick.pro @@ -8,6 +8,7 @@ IMPORT_VERSION = 1.0 PDF_QML_FILES = \ qml/PdfMultiPageView.qml \ qml/PdfPageView.qml \ + qml/PdfScrollablePageView.qml \ QML_FILES += $$PDF_QML_FILES qmldir diff --git a/src/pdf/quick/resources.qrc b/src/pdf/quick/resources.qrc index 282610d4c21659fa6f3d2a60aea3c73acad13404..20cac48270f51830e1aa419a5d34191c7831ddd7 100644 --- a/src/pdf/quick/resources.qrc +++ b/src/pdf/quick/resources.qrc @@ -2,5 +2,6 @@ <qresource prefix="/qt-project.org/qtpdf"> <file>qml/PdfMultiPageView.qml</file> <file>qml/PdfPageView.qml</file> + <file>qml/PdfScrollablePageView.qml</file> </qresource> </RCC>