From bf3133033236afb34974fec63ac21e1749d503ad Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 20 Jan 2020 15:12:01 +0100 Subject: Add PdfMultiPageView So far it's a ListView with a page per delegate. Many features are working, but zooming and rotation are not working yet. Change-Id: I9ee7aa60ad4411bd8734fe2cd987a68906a5cf57 Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/main.cpp | 69 ++++++ examples/pdf/multipage/multipage.pro | 14 ++ examples/pdf/multipage/resources/document-open.svg | 13 + examples/pdf/multipage/resources/edit-clear.svg | 15 ++ examples/pdf/multipage/resources/edit-copy.svg | 15 ++ .../pdf/multipage/resources/go-next-view-page.svg | 13 + .../multipage/resources/go-previous-view-page.svg | 13 + examples/pdf/multipage/resources/rotate-left.svg | 6 + examples/pdf/multipage/resources/rotate-right.svg | 6 + examples/pdf/multipage/resources/zoom-fit-best.svg | 13 + .../pdf/multipage/resources/zoom-fit-width.svg | 13 + examples/pdf/multipage/resources/zoom-in.svg | 13 + examples/pdf/multipage/resources/zoom-original.svg | 13 + examples/pdf/multipage/resources/zoom-out.svg | 13 + examples/pdf/multipage/viewer.qml | 276 +++++++++++++++++++++ examples/pdf/multipage/viewer.qrc | 17 ++ 16 files changed, 522 insertions(+) create mode 100644 examples/pdf/multipage/main.cpp create mode 100644 examples/pdf/multipage/multipage.pro create mode 100644 examples/pdf/multipage/resources/document-open.svg create mode 100644 examples/pdf/multipage/resources/edit-clear.svg create mode 100644 examples/pdf/multipage/resources/edit-copy.svg create mode 100644 examples/pdf/multipage/resources/go-next-view-page.svg create mode 100644 examples/pdf/multipage/resources/go-previous-view-page.svg create mode 100644 examples/pdf/multipage/resources/rotate-left.svg create mode 100644 examples/pdf/multipage/resources/rotate-right.svg create mode 100644 examples/pdf/multipage/resources/zoom-fit-best.svg create mode 100644 examples/pdf/multipage/resources/zoom-fit-width.svg create mode 100644 examples/pdf/multipage/resources/zoom-in.svg create mode 100644 examples/pdf/multipage/resources/zoom-original.svg create mode 100644 examples/pdf/multipage/resources/zoom-out.svg create mode 100644 examples/pdf/multipage/viewer.qml create mode 100644 examples/pdf/multipage/viewer.qrc (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/main.cpp b/examples/pdf/multipage/main.cpp new file mode 100644 index 000000000..451521f80 --- /dev/null +++ b/examples/pdf/multipage/main.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + QCoreApplication::setApplicationName("Qt Quick Multi-page PDF Viewer Example"); + QCoreApplication::setOrganizationName("QtProject"); + QCoreApplication::setApplicationVersion(QT_VERSION_STR); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:///pdfviewer/viewer.qml"))); + if (app.arguments().count() > 1) { + QUrl toLoad = QUrl::fromUserInput(app.arguments().at(1)); + engine.rootObjects().first()->setProperty("source", toLoad); + } + + return app.exec(); +} diff --git a/examples/pdf/multipage/multipage.pro b/examples/pdf/multipage/multipage.pro new file mode 100644 index 000000000..5fff58fe1 --- /dev/null +++ b/examples/pdf/multipage/multipage.pro @@ -0,0 +1,14 @@ +TEMPLATE = app + +QT += qml quick pdf widgets + +SOURCES += main.cpp + +RESOURCES += \ + viewer.qrc +EXAMPLE_FILES = \ + viewer.qml + +target.path = $$[QT_INSTALL_EXAMPLES]/pdf/multipage +INSTALLS += target + diff --git a/examples/pdf/multipage/resources/document-open.svg b/examples/pdf/multipage/resources/document-open.svg new file mode 100644 index 000000000..bf23123a3 --- /dev/null +++ b/examples/pdf/multipage/resources/document-open.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/edit-clear.svg b/examples/pdf/multipage/resources/edit-clear.svg new file mode 100644 index 000000000..1c35aaf04 --- /dev/null +++ b/examples/pdf/multipage/resources/edit-clear.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/edit-copy.svg b/examples/pdf/multipage/resources/edit-copy.svg new file mode 100644 index 000000000..9dd16877d --- /dev/null +++ b/examples/pdf/multipage/resources/edit-copy.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/go-next-view-page.svg b/examples/pdf/multipage/resources/go-next-view-page.svg new file mode 100644 index 000000000..e453ddbec --- /dev/null +++ b/examples/pdf/multipage/resources/go-next-view-page.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/go-previous-view-page.svg b/examples/pdf/multipage/resources/go-previous-view-page.svg new file mode 100644 index 000000000..b032309e9 --- /dev/null +++ b/examples/pdf/multipage/resources/go-previous-view-page.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/rotate-left.svg b/examples/pdf/multipage/resources/rotate-left.svg new file mode 100644 index 000000000..90ce53c9d --- /dev/null +++ b/examples/pdf/multipage/resources/rotate-left.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/rotate-right.svg b/examples/pdf/multipage/resources/rotate-right.svg new file mode 100644 index 000000000..7383d1c84 --- /dev/null +++ b/examples/pdf/multipage/resources/rotate-right.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/zoom-fit-best.svg b/examples/pdf/multipage/resources/zoom-fit-best.svg new file mode 100644 index 000000000..adf302621 --- /dev/null +++ b/examples/pdf/multipage/resources/zoom-fit-best.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/zoom-fit-width.svg b/examples/pdf/multipage/resources/zoom-fit-width.svg new file mode 100644 index 000000000..985ee5205 --- /dev/null +++ b/examples/pdf/multipage/resources/zoom-fit-width.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/zoom-in.svg b/examples/pdf/multipage/resources/zoom-in.svg new file mode 100644 index 000000000..efdc9f17d --- /dev/null +++ b/examples/pdf/multipage/resources/zoom-in.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/zoom-original.svg b/examples/pdf/multipage/resources/zoom-original.svg new file mode 100644 index 000000000..1b4080a03 --- /dev/null +++ b/examples/pdf/multipage/resources/zoom-original.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/zoom-out.svg b/examples/pdf/multipage/resources/zoom-out.svg new file mode 100644 index 000000000..fcde9e526 --- /dev/null +++ b/examples/pdf/multipage/resources/zoom-out.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml new file mode 100644 index 000000000..d20ad4a5b --- /dev/null +++ b/examples/pdf/multipage/viewer.qml @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Pdf 5.15 +import QtQuick.Shapes 1.14 +import QtQuick.Window 2.14 +import Qt.labs.platform 1.1 as Platform + +ApplicationWindow { + id: root + width: 800 + height: 1024 + color: "lightgrey" + title: document.title + visible: true + property alias source: document.source // for main.cpp + + header: ToolBar { + RowLayout { + anchors.fill: parent + anchors.rightMargin: 6 + ToolButton { + action: Action { + shortcut: StandardKey.Open + icon.source: "resources/document-open.svg" + onTriggered: fileDialog.open() + } + } + /* TODO zoom & rotation + ToolButton { + action: Action { + shortcut: StandardKey.ZoomIn + enabled: view.sourceSize.width < 10000 + icon.source: "resources/zoom-in.svg" + onTriggered: view.renderScale *= Math.sqrt(2) + } + } + ToolButton { + action: Action { + shortcut: StandardKey.ZoomOut + enabled: view.sourceSize.width > 50 + icon.source: "resources/zoom-out.svg" + onTriggered: view.renderScale /= Math.sqrt(2) + } + } + ToolButton { + action: Action { + icon.source: "resources/zoom-fit-width.svg" + onTriggered: view.scaleToWidth(root.contentItem.width, root.contentItem.height) + } + } + ToolButton { + action: Action { + icon.source: "resources/zoom-fit-best.svg" + onTriggered: view.scaleToPage(root.contentItem.width, root.contentItem.height) + } + } + ToolButton { + action: Action { + shortcut: "Ctrl+0" + icon.source: "resources/zoom-original.svg" + onTriggered: view.resetScale() + } + } + ToolButton { + action: Action { + shortcut: "Ctrl+L" + icon.source: "resources/rotate-left.svg" + onTriggered: view.rotation -= 90 + } + } + ToolButton { + action: Action { + shortcut: "Ctrl+R" + icon.source: "resources/rotate-right.svg" + onTriggered: view.rotation += 90 + } + } + */ + ToolButton { + action: Action { + icon.source: "resources/go-previous-view-page.svg" + enabled: view.backEnbled + onTriggered: view.back() + } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "go back" + } + SpinBox { + id: currentPageSB + from: 1 + to: document.pageCount + editable: true + onValueChanged: view.currentPage = value - 1 + Shortcut { + sequence: StandardKey.MoveToPreviousPage + onActivated: currentPageSB.value-- + } + Shortcut { + sequence: StandardKey.MoveToNextPage + onActivated: currentPageSB.value++ + } + } + ToolButton { + action: Action { + icon.source: "resources/go-next-view-page.svg" + enabled: view.forwardEnabled + onTriggered: view.forward() + } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "go forward" + } + ToolButton { + action: Action { + shortcut: StandardKey.Copy + icon.source: "resources/edit-copy.svg" + enabled: view.selectedText !== "" + onTriggered: view.copySelectionToClipboard() + } + } + TextField { + id: searchField + placeholderText: "search" + Layout.minimumWidth: 200 + Layout.fillWidth: true + Image { + visible: searchField.text !== "" + source: "resources/edit-clear.svg" + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + margins: 3 + rightMargin: 5 + } + TapHandler { + onTapped: searchField.clear() + } + } + } + Shortcut { + sequence: StandardKey.Find + onActivated: searchField.forceActiveFocus() + } + Shortcut { + sequence: StandardKey.Quit + onActivated: Qt.quit() + } + } + } + + Platform.FileDialog { + id: fileDialog + title: "Open a PDF file" + nameFilters: [ "PDF files (*.pdf)" ] + onAccepted: document.source = file + } + + Dialog { + id: passwordDialog + title: "Password" + standardButtons: Dialog.Ok | Dialog.Cancel + modal: true + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + width: 300 + + TextField { + id: passwordField + placeholderText: qsTr("Please provide the password") + echoMode: TextInput.Password + width: parent.width + onAccepted: passwordDialog.accept() + } + onAccepted: document.password = passwordField.text + } + + Dialog { + id: errorDialog + title: "Error loading " + document.source + standardButtons: Dialog.Ok + modal: true + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + width: 300 + + Label { + id: errorField + text: document.error + } + } + + PdfDocument { + id: document + onStatusChanged: { + if (status === PdfDocument.Error) errorDialog.open() + view.document = (status === PdfDocument.Ready ? document : undefined) + } + onPasswordRequired: { + passwordDialog.open() + passwordField.forceActiveFocus() + } + } + + PdfMultiPageView { + id: view + anchors.fill: parent + document: root.document + searchString: searchField.text + onCurrentPageReallyChanged: currentPageSB.value = page + 1 + } + + footer: ToolBar { + height: statusLabel.implicitHeight * 1.5 + Label { + id: statusLabel + anchors.verticalCenter: parent.verticalCenter + x: 6 + property size implicitPointSize: document.pagePointSize(view.currentPage) + text: "page " + (currentPageSB.value) + " of " + document.pageCount + + " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + visible: document.pageCount > 0 + } + } +} diff --git a/examples/pdf/multipage/viewer.qrc b/examples/pdf/multipage/viewer.qrc new file mode 100644 index 000000000..fa3561caf --- /dev/null +++ b/examples/pdf/multipage/viewer.qrc @@ -0,0 +1,17 @@ + + + viewer.qml + resources/edit-clear.svg + resources/edit-copy.svg + resources/go-next-view-page.svg + resources/go-previous-view-page.svg + resources/rotate-left.svg + resources/rotate-right.svg + resources/zoom-in.svg + resources/zoom-fit-best.svg + resources/zoom-fit-width.svg + resources/zoom-original.svg + resources/zoom-out.svg + resources/document-open.svg + + -- cgit v1.2.3 From 130b058352077b88f8839871f88c226e3e1fa705 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Tue, 4 Feb 2020 16:08:42 +0100 Subject: Add multipage example to pdf.pro; work with 5.14; fix main.cpp - build the multipage example by default - don't import anything that won't work with Qt 5.14 - set application attributes before creating an instance (to fix the warning about that) Change-Id: I265f49ca75cae1908d4c23848cba8c42e5e3824b Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/main.cpp b/examples/pdf/multipage/main.cpp index 451521f80..7b766d77e 100644 --- a/examples/pdf/multipage/main.cpp +++ b/examples/pdf/multipage/main.cpp @@ -53,10 +53,11 @@ int main(int argc, char* argv[]) { - QApplication app(argc, argv); QCoreApplication::setApplicationName("Qt Quick Multi-page PDF Viewer Example"); QCoreApplication::setOrganizationName("QtProject"); QCoreApplication::setApplicationVersion(QT_VERSION_STR); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:///pdfviewer/viewer.qml"))); -- cgit v1.2.3 From 25a371caa376c513f22d5c01e425a18629657fdc Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 3 Feb 2020 18:11:16 +0100 Subject: Add zoom and rotation to PdfMultiPageView Currently, scaleToWidth() and scaleToPage() choose the scale of the first page to fit the given viewport size, and as long as all pages are the same size, it works. On the other hand, the PinchHandler only affects the scale of the page on which the pinch gesture occurs. Calling resetScale(), scaleToWidth() or scaleToPage() undoes the effect of any previous pinch gesture or any other kind of scaling change. Task-number: QTBUG-77513 Change-Id: Ia3227ca9c4af263eb8505dbd6336657984c66ab0 Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/viewer.qml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index d20ad4a5b..77c06f80f 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -75,11 +75,10 @@ ApplicationWindow { onTriggered: fileDialog.open() } } - /* TODO zoom & rotation ToolButton { action: Action { shortcut: StandardKey.ZoomIn - enabled: view.sourceSize.width < 10000 + enabled: view.renderScale < 10 icon.source: "resources/zoom-in.svg" onTriggered: view.renderScale *= Math.sqrt(2) } @@ -87,7 +86,7 @@ ApplicationWindow { ToolButton { action: Action { shortcut: StandardKey.ZoomOut - enabled: view.sourceSize.width > 50 + enabled: view.renderScale > 0.1 icon.source: "resources/zoom-out.svg" onTriggered: view.renderScale /= Math.sqrt(2) } @@ -115,17 +114,16 @@ ApplicationWindow { action: Action { shortcut: "Ctrl+L" icon.source: "resources/rotate-left.svg" - onTriggered: view.rotation -= 90 + onTriggered: view.pageRotation -= 90 } } ToolButton { action: Action { shortcut: "Ctrl+R" icon.source: "resources/rotate-right.svg" - onTriggered: view.rotation += 90 + onTriggered: view.pageRotation += 90 } } - */ ToolButton { action: Action { icon.source: "resources/go-previous-view-page.svg" @@ -269,7 +267,8 @@ ApplicationWindow { x: 6 property size implicitPointSize: document.pagePointSize(view.currentPage) text: "page " + (currentPageSB.value) + " of " + document.pageCount + - " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " scale " + view.renderScale.toFixed(2) + + " original size " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt" visible: document.pageCount > 0 } } -- cgit v1.2.3 From 7afe95f14d7d048a73baa12b3bd5f6a9bcea2ccb Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 5 Feb 2020 10:56:48 +0100 Subject: PDF multipage view: track specific link and navigation destinations Unfortunately it's getting harder to do things declaratively, because we have to avoid circular bindings, and because of needing to use imperative APIs. The current-page spinbox provides onValueModified() to detect when the user modifies it, distinct from the simple fact that the value changed. We shouldn't make bindings to set ListView.currentIndex anyway, because that results in slow animation (and loading pages in all delegates along the way) rather than quick jumping to the correct page. Instead we need to use ListView.positionViewAtIndex(), another imperative API, to get quick jumps without having to calculate and set contentY in some other way. Now we move toward the NavigationStack providing storage for the current destination at all times. Changes there will trigger programmatically moving the ListView. When the user scrolls manually, that generates a "destination" in the navigation stack, such that the back button can jump back to the previous location, and then the forward button can return to the destination where manual scrolling ended up. Fixes: QTBUG-77510 Change-Id: I47544210d2e0f9aa790f3d2594839678374e463d Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/viewer.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index 77c06f80f..bbc28cd8d 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -139,7 +139,7 @@ ApplicationWindow { from: 1 to: document.pageCount editable: true - onValueChanged: view.currentPage = value - 1 + onValueModified: view.goToPage(value - 1) Shortcut { sequence: StandardKey.MoveToPreviousPage onActivated: currentPageSB.value-- @@ -256,7 +256,7 @@ ApplicationWindow { anchors.fill: parent document: root.document searchString: searchField.text - onCurrentPageReallyChanged: currentPageSB.value = page + 1 + onCurrentPageChanged: currentPageSB.value = view.currentPage + 1 } footer: ToolBar { -- cgit v1.2.3 From e5a33355798d3277c631b0024f389cdca2f2c683 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 7 Feb 2020 14:54:31 +0100 Subject: PDF multipage viewer: iterate search results This version still has separate PdfSearchModel instances on each page, but now there are buttons to iterate and highlight the search results in order. When you come to the last result on one page, hitting the "Find Next" button will jump to the next page, and keep jumping forward from there until another result is found. Unfortunately this jumping takes time if it skips over a lot of pages because of empty search results. That seems to be another reason to make PdfSearchModel into a whole-document search model and use one instance. Also reorganize PdfMultiPageView.qml's public API into sections according to functionality rather than by type. Change-Id: I677a764fcbf231b2656aff8abe7240a27582a696 Reviewed-by: Shawn Rutledge --- .../pdf/multipage/resources/go-down-search.svg | 13 ++++ examples/pdf/multipage/resources/go-up-search.svg | 8 +++ examples/pdf/multipage/viewer.qml | 81 +++++++++++++++------- examples/pdf/multipage/viewer.qrc | 2 + 4 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 examples/pdf/multipage/resources/go-down-search.svg create mode 100644 examples/pdf/multipage/resources/go-up-search.svg (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/resources/go-down-search.svg b/examples/pdf/multipage/resources/go-down-search.svg new file mode 100644 index 000000000..ae17ab93b --- /dev/null +++ b/examples/pdf/multipage/resources/go-down-search.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/examples/pdf/multipage/resources/go-up-search.svg b/examples/pdf/multipage/resources/go-up-search.svg new file mode 100644 index 000000000..5cc155873 --- /dev/null +++ b/examples/pdf/multipage/resources/go-up-search.svg @@ -0,0 +1,8 @@ + + + + diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index bbc28cd8d..3d9cd371a 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -167,30 +167,6 @@ ApplicationWindow { onTriggered: view.copySelectionToClipboard() } } - TextField { - id: searchField - placeholderText: "search" - Layout.minimumWidth: 200 - Layout.fillWidth: true - Image { - visible: searchField.text !== "" - source: "resources/edit-clear.svg" - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - margins: 3 - rightMargin: 5 - } - TapHandler { - onTapped: searchField.clear() - } - } - } - Shortcut { - sequence: StandardKey.Find - onActivated: searchField.forceActiveFocus() - } Shortcut { sequence: StandardKey.Quit onActivated: Qt.quit() @@ -259,6 +235,63 @@ ApplicationWindow { onCurrentPageChanged: currentPageSB.value = view.currentPage + 1 } + Drawer { + id: searchDrawer + edge: Qt.BottomEdge + x: 20 + width: searchLayout.implicitWidth + height: searchLayout.implicitHeight + dim: false + Shortcut { + sequence: StandardKey.Find + onActivated: { + searchDrawer.open() + searchField.forceActiveFocus() + } + } + RowLayout { + id: searchLayout + ToolButton { + action: Action { + icon.source: "resources/go-up-search.svg" + onTriggered: view.searchBack() + } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "find previous" + } + TextField { + id: searchField + placeholderText: "search" + Layout.minimumWidth: 200 + Layout.fillWidth: true + Image { + visible: searchField.text !== "" + source: "resources/edit-clear.svg" + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + margins: 3 + rightMargin: 5 + } + TapHandler { + onTapped: searchField.clear() + } + } + } + ToolButton { + action: Action { + icon.source: "resources/go-down-search.svg" + onTriggered: view.searchForward() + } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "find next" + } + } + } + footer: ToolBar { height: statusLabel.implicitHeight * 1.5 Label { diff --git a/examples/pdf/multipage/viewer.qrc b/examples/pdf/multipage/viewer.qrc index fa3561caf..9698a2689 100644 --- a/examples/pdf/multipage/viewer.qrc +++ b/examples/pdf/multipage/viewer.qrc @@ -3,8 +3,10 @@ viewer.qml resources/edit-clear.svg resources/edit-copy.svg + resources/go-down-search.svg resources/go-next-view-page.svg resources/go-previous-view-page.svg + resources/go-up-search.svg resources/rotate-left.svg resources/rotate-right.svg resources/zoom-in.svg -- cgit v1.2.3 From 0b6a4d94945a975390b2574e6aff2568ebb7f061 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 10 Feb 2020 10:49:33 +0100 Subject: PdfSearchModel: be QALM and find search results on all pages It's a QAbstractListModel, so now PdfMultiPageView has a ListView in a left-side Drawer showing all results found so far. - In PdfMultiPageView, multiple pages exist at once, so it makes sense to use the same searchmodel for all. - It's faster and saves memory. - Search results on each page can be cached. - It's possible to show search results in a ListView or QListView. Change-Id: I66fba6975954a09a4d23262be87ff8cc25ee7478 Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/viewer.qml | 96 +++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 35 deletions(-) (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index 3d9cd371a..8f102a3c1 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -230,6 +230,7 @@ ApplicationWindow { PdfMultiPageView { id: view anchors.fill: parent + anchors.leftMargin: searchDrawer.position * searchDrawer.width document: root.document searchString: searchField.text onCurrentPageChanged: currentPageSB.value = view.currentPage + 1 @@ -237,10 +238,11 @@ ApplicationWindow { Drawer { id: searchDrawer - edge: Qt.BottomEdge - x: 20 + edge: Qt.LeftEdge + modal: false width: searchLayout.implicitWidth - height: searchLayout.implicitHeight + y: root.header.height + height: view.height dim: false Shortcut { sequence: StandardKey.Find @@ -249,45 +251,69 @@ ApplicationWindow { searchField.forceActiveFocus() } } - RowLayout { + ColumnLayout { id: searchLayout - ToolButton { - action: Action { - icon.source: "resources/go-up-search.svg" - onTriggered: view.searchBack() + anchors.fill: parent + anchors.margins: 2 + RowLayout { + ToolButton { + action: Action { + icon.source: "resources/go-up-search.svg" + shortcut: StandardKey.FindPrevious + onTriggered: view.searchBack() + } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "find previous" } - ToolTip.visible: enabled && hovered - ToolTip.delay: 2000 - ToolTip.text: "find previous" - } - TextField { - id: searchField - placeholderText: "search" - Layout.minimumWidth: 200 - Layout.fillWidth: true - Image { - visible: searchField.text !== "" - source: "resources/edit-clear.svg" - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - margins: 3 - rightMargin: 5 + TextField { + id: searchField + placeholderText: "search" + Layout.minimumWidth: 200 + Layout.fillWidth: true + Image { + visible: searchField.text !== "" + source: "resources/edit-clear.svg" + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + margins: 3 + rightMargin: 5 + } + TapHandler { + onTapped: searchField.clear() + } } - TapHandler { - onTapped: searchField.clear() + } + ToolButton { + action: Action { + icon.source: "resources/go-down-search.svg" + shortcut: StandardKey.FindNext + onTriggered: view.searchForward() } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "find next" } } - ToolButton { - action: Action { - icon.source: "resources/go-down-search.svg" - onTriggered: view.searchForward() + ListView { + id: searchResultsList + ColumnLayout.fillWidth: true + ColumnLayout.fillHeight: true + clip: true + model: view.searchModel + ScrollBar.vertical: ScrollBar { } + delegate: ItemDelegate { + width: parent ? parent.width : 0 + text: "page " + (page + 1) + ": " + context + highlighted: ListView.isCurrentItem + onClicked: { + searchResultsList.currentIndex = index + view.goToLocation(page, location, 0) + view.searchModel.currentResult = indexOnPage + } } - ToolTip.visible: enabled && hovered - ToolTip.delay: 2000 - ToolTip.text: "find next" } } } -- cgit v1.2.3 From 24cd9f79bf7cf21e275b73ded63ee46bcc706db3 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 17 Feb 2020 16:42:56 +0100 Subject: Rearrange PdfPageView.qml Now it looks more similar to PdfMultiPageView.qml: public properties and functions are grouped by functionality, implementation details are "below the fold", and properties and functions that we don't want to expose as API are nested inside inner items. Also fixed ColumnLayout attached properties. Change-Id: Iafe6f983a34ca6bac9b0d4f3a26aba5a426e0232 Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/viewer.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index 8f102a3c1..3a6347f5a 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -299,8 +299,8 @@ ApplicationWindow { } ListView { id: searchResultsList - ColumnLayout.fillWidth: true - ColumnLayout.fillHeight: true + Layout.fillWidth: true + Layout.fillHeight: true clip: true model: view.searchModel ScrollBar.vertical: ScrollBar { } -- cgit v1.2.3 From a2be3a7a79dc4fabe8675ea80a6ba550e0e4deec Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 19 Feb 2020 10:30:52 +0100 Subject: PDF multipage example: move search field to the footer Hiding the search feature in a closed Drawer might not have been sufficiently touch-friendly and discoverable, for example on iOS where the user is not going to press control-F to start searching. But the top toolbar is too full to put the search field back up there. It's familiar from Firefox to have the search field at the bottom, and we have enough space for it there. So now you can search and jump around the search results without opening the drawer; but pressing Enter in the search field opens the drawer. Hopefully swiping to open the drawer is also convenient enough on touch platforms; otherwise we could add another button for that, perhaps at the left of the footer. Change-Id: Iaec63bc22b03e29156fee817d197daae5b0cf9d5 Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/viewer.qml | 145 ++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 76 deletions(-) (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index 3a6347f5a..ba54065b7 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -167,6 +167,10 @@ ApplicationWindow { onTriggered: view.copySelectionToClipboard() } } + Shortcut { + sequence: StandardKey.Find + onActivated: searchField.forceActiveFocus() + } Shortcut { sequence: StandardKey.Quit onActivated: Qt.quit() @@ -240,95 +244,84 @@ ApplicationWindow { id: searchDrawer edge: Qt.LeftEdge modal: false - width: searchLayout.implicitWidth + width: 300 y: root.header.height height: view.height dim: false - Shortcut { - sequence: StandardKey.Find - onActivated: { - searchDrawer.open() - searchField.forceActiveFocus() + ListView { + id: searchResultsList + anchors.fill: parent + anchors.margins: 2 + model: view.searchModel + ScrollBar.vertical: ScrollBar { } + delegate: ItemDelegate { + width: parent ? parent.width : 0 + text: "page " + (page + 1) + ": " + context + highlighted: ListView.isCurrentItem + onClicked: { + searchResultsList.currentIndex = index + view.goToLocation(page, location, 0) + view.searchModel.currentResult = indexOnPage + } } } - ColumnLayout { - id: searchLayout + } + + footer: ToolBar { + height: footerRow.implicitHeight + RowLayout { + id: footerRow anchors.fill: parent - anchors.margins: 2 - RowLayout { - ToolButton { - action: Action { - icon.source: "resources/go-up-search.svg" - shortcut: StandardKey.FindPrevious - onTriggered: view.searchBack() - } - ToolTip.visible: enabled && hovered - ToolTip.delay: 2000 - ToolTip.text: "find previous" + ToolButton { + action: Action { + icon.source: "resources/go-up-search.svg" + shortcut: StandardKey.FindPrevious + onTriggered: view.searchBack() } - TextField { - id: searchField - placeholderText: "search" - Layout.minimumWidth: 200 - Layout.fillWidth: true - Image { - visible: searchField.text !== "" - source: "resources/edit-clear.svg" - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - margins: 3 - rightMargin: 5 - } - TapHandler { - onTapped: searchField.clear() - } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "find previous" + } + TextField { + id: searchField + placeholderText: "search" + Layout.minimumWidth: 200 + Layout.fillWidth: true + onAccepted: searchDrawer.open() + Image { + visible: searchField.text !== "" + source: "resources/edit-clear.svg" + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + margins: 3 + rightMargin: 5 } - } - ToolButton { - action: Action { - icon.source: "resources/go-down-search.svg" - shortcut: StandardKey.FindNext - onTriggered: view.searchForward() + TapHandler { + onTapped: searchField.clear() } - ToolTip.visible: enabled && hovered - ToolTip.delay: 2000 - ToolTip.text: "find next" } } - ListView { - id: searchResultsList - Layout.fillWidth: true - Layout.fillHeight: true - clip: true - model: view.searchModel - ScrollBar.vertical: ScrollBar { } - delegate: ItemDelegate { - width: parent ? parent.width : 0 - text: "page " + (page + 1) + ": " + context - highlighted: ListView.isCurrentItem - onClicked: { - searchResultsList.currentIndex = index - view.goToLocation(page, location, 0) - view.searchModel.currentResult = indexOnPage - } + ToolButton { + action: Action { + icon.source: "resources/go-down-search.svg" + shortcut: StandardKey.FindNext + onTriggered: view.searchForward() } + ToolTip.visible: enabled && hovered + ToolTip.delay: 2000 + ToolTip.text: "find next" + } + Label { + id: statusLabel + Layout.fillWidth: true + property size implicitPointSize: document.pagePointSize(view.currentPage) + text: "page " + (currentPageSB.value) + " of " + document.pageCount + + " scale " + view.renderScale.toFixed(2) + + " original size " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt" + visible: document.pageCount > 0 } - } - } - - footer: ToolBar { - height: statusLabel.implicitHeight * 1.5 - Label { - id: statusLabel - anchors.verticalCenter: parent.verticalCenter - x: 6 - property size implicitPointSize: document.pagePointSize(view.currentPage) - text: "page " + (currentPageSB.value) + " of " + document.pageCount + - " scale " + view.renderScale.toFixed(2) + - " original size " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt" - visible: document.pageCount > 0 } } } -- cgit v1.2.3 From 1acd9ad2bfa1c54f19fa8a71fb41e8a90233f76b Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 19 Feb 2020 17:47:01 +0100 Subject: QtPdf examples: use test.pdf from resources if no file given On iOS, the native FileDialog doesn't work, sharing doesn't work, and packaging files along with the application requires manual effort; so a PDF file in resources seems to be the easiest alternative to make the examples demo-able. QPdfDocument wants a file path, because it uses QFile; but we like to use URLs in Qt Quick. So it's a bit of an impedance mismatch, there are several choices about when and where to do the conversion, and it's hard to say which way is more correct. This way happens to work for now. Also do the rest of the things necessary to get this working on iOS. Change-Id: Icb8614d5eed2510f101aefba534ef80cf890518f Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/main.cpp | 3 +++ examples/pdf/multipage/multipage.pro | 2 +- examples/pdf/multipage/resources/test.pdf | Bin 0 -> 80045 bytes examples/pdf/multipage/viewer.qml | 3 ++- examples/pdf/multipage/viewer.qrc | 3 ++- 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 examples/pdf/multipage/resources/test.pdf (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/main.cpp b/examples/pdf/multipage/main.cpp index 7b766d77e..35aaa8c64 100644 --- a/examples/pdf/multipage/main.cpp +++ b/examples/pdf/multipage/main.cpp @@ -64,7 +64,10 @@ int main(int argc, char* argv[]) if (app.arguments().count() > 1) { QUrl toLoad = QUrl::fromUserInput(app.arguments().at(1)); engine.rootObjects().first()->setProperty("source", toLoad); + } else { + engine.rootObjects().first()->setProperty("source", QStringLiteral("resources/test.pdf")); } + return app.exec(); } diff --git a/examples/pdf/multipage/multipage.pro b/examples/pdf/multipage/multipage.pro index 5fff58fe1..5df9e653d 100644 --- a/examples/pdf/multipage/multipage.pro +++ b/examples/pdf/multipage/multipage.pro @@ -1,6 +1,6 @@ TEMPLATE = app -QT += qml quick pdf widgets +QT += qml quick pdf widgets svg SOURCES += main.cpp diff --git a/examples/pdf/multipage/resources/test.pdf b/examples/pdf/multipage/resources/test.pdf new file mode 100644 index 000000000..a9dc1bc29 Binary files /dev/null and b/examples/pdf/multipage/resources/test.pdf differ diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index ba54065b7..ac6d2cd9a 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -62,7 +62,7 @@ ApplicationWindow { color: "lightgrey" title: document.title visible: true - property alias source: document.source // for main.cpp + property string source // for main.cpp header: ToolBar { RowLayout { @@ -221,6 +221,7 @@ ApplicationWindow { PdfDocument { id: document + source: Qt.resolvedUrl(root.source) onStatusChanged: { if (status === PdfDocument.Error) errorDialog.open() view.document = (status === PdfDocument.Ready ? document : undefined) diff --git a/examples/pdf/multipage/viewer.qrc b/examples/pdf/multipage/viewer.qrc index 9698a2689..1b6fa52f7 100644 --- a/examples/pdf/multipage/viewer.qrc +++ b/examples/pdf/multipage/viewer.qrc @@ -1,6 +1,7 @@ viewer.qml + resources/document-open.svg resources/edit-clear.svg resources/edit-copy.svg resources/go-down-search.svg @@ -9,11 +10,11 @@ resources/go-up-search.svg resources/rotate-left.svg resources/rotate-right.svg + resources/test.pdf resources/zoom-in.svg resources/zoom-fit-best.svg resources/zoom-fit-width.svg resources/zoom-original.svg resources/zoom-out.svg - resources/document-open.svg -- cgit v1.2.3 From f467edc97e66727be7fa3747913e4e01672d4b71 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Tue, 18 Feb 2020 21:42:35 +0100 Subject: PdfMultiPageView: use TableView; horz. scroll; control page position TableView is missing some features compared to ListView; so finding out where we currently are (which row) and programmatic positioning on a specific y coordinate of a specific row require some workarounds for now, including helpers in PdfDocument. TableView also assumes (and sporadically enforces) that all cells in a column have the same width. So we need a placeholder Item for each page. This also helps with rotation: the placeholder is now as wide as the window or the image, whichever is wider, and the "paper" is centered within; thus there's always room to rotate it. There's still some problem with setting contentY in goToPage() after the page has been zoomed to a size larger than the window: the values look correct, but it scrolls too far. But on the plus side, horizontal scrolling works. So now we attempt to control the horizontal position too: NavigationStack tracks it, and can go back to a previous position; and links can in theory jump to specific positions and zoom levels, scrolling horizontally such that a specific x coordinate is visible. Includes minor UI tweaks to make it look better on iOS. Change-Id: I643d8ef48ef815aeb49cae77dcb84c3682563d56 Reviewed-by: Shawn Rutledge --- examples/pdf/multipage/viewer.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'examples/pdf/multipage') diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index ac6d2cd9a..9e5f92407 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -249,6 +249,7 @@ ApplicationWindow { y: root.header.height height: view.height dim: false + clip: true ListView { id: searchResultsList anchors.fill: parent @@ -286,7 +287,7 @@ ApplicationWindow { TextField { id: searchField placeholderText: "search" - Layout.minimumWidth: 200 + Layout.minimumWidth: 150 Layout.fillWidth: true onAccepted: searchDrawer.open() Image { @@ -316,11 +317,10 @@ ApplicationWindow { } Label { id: statusLabel - Layout.fillWidth: true property size implicitPointSize: document.pagePointSize(view.currentPage) text: "page " + (currentPageSB.value) + " of " + document.pageCount + " scale " + view.renderScale.toFixed(2) + - " original size " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt" + " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt" visible: document.pageCount > 0 } } -- cgit v1.2.3