PDF Multipage Viewer Example

/**************************************************************************** ** ** Copyright (C) 2022 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 QtQml // workaround for QTBUG-82873 import QtQuick import QtQuick.Controls import QtQuick.Dialogs import QtQuick.Layouts import QtQuick.Pdf import QtQuick.Shapes import QtQuick.Window ApplicationWindow { id: root width: 800 height: 1024 color: "lightgrey" title: document.title visible: true property string source // for main.cpp header: ToolBar { RowLayout { anchors.fill: parent anchors.rightMargin: 6 ToolButton { action: Action { shortcut: StandardKey.Open icon.source: "qrc:/pdfviewer/resources/document-open.svg" onTriggered: fileDialog.open() } } ToolButton { action: Action { shortcut: StandardKey.ZoomIn enabled: view.renderScale < 10 icon.source: "qrc:/pdfviewer/resources/zoom-in.svg" onTriggered: view.renderScale *= Math.sqrt(2) } } ToolButton { action: Action { shortcut: StandardKey.ZoomOut enabled: view.renderScale > 0.1 icon.source: "qrc:/pdfviewer/resources/zoom-out.svg" onTriggered: view.renderScale /= Math.sqrt(2) } } ToolButton { action: Action { icon.source: "qrc:/pdfviewer/resources/zoom-fit-width.svg" onTriggered: view.scaleToWidth(root.contentItem.width, root.contentItem.height) } } ToolButton { action: Action { icon.source: "qrc:/pdfviewer/resources/zoom-fit-best.svg" onTriggered: view.scaleToPage(root.contentItem.width, root.contentItem.height) } } ToolButton { action: Action { shortcut: "Ctrl+0" icon.source: "qrc:/pdfviewer/resources/zoom-original.svg" onTriggered: view.resetScale() } } ToolButton { action: Action { shortcut: "Ctrl+L" icon.source: "qrc:/pdfviewer/resources/rotate-left.svg" onTriggered: view.pageRotation -= 90 } } ToolButton { action: Action { shortcut: "Ctrl+R" icon.source: "qrc:/pdfviewer/resources/rotate-right.svg" onTriggered: view.pageRotation += 90 } } ToolButton { action: Action { icon.source: "qrc:/pdfviewer/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 onValueModified: view.goToPage(value - 1) Shortcut { sequence: StandardKey.MoveToPreviousPage onActivated: view.goToPage(currentPageSB.value - 2) } Shortcut { sequence: StandardKey.MoveToNextPage onActivated: view.goToPage(currentPageSB.value) } } ToolButton { action: Action { icon.source: "qrc:/pdfviewer/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.SelectAll icon.source: "qrc:/pdfviewer/resources/edit-select-all.svg" onTriggered: view.selectAll() } } ToolButton { action: Action { shortcut: StandardKey.Copy icon.source: "qrc:/pdfviewer/resources/edit-copy.svg" enabled: view.selectedText !== "" onTriggered: view.copySelectionToClipboard() } } Shortcut { sequence: StandardKey.Find onActivated: searchField.forceActiveFocus() } Shortcut { sequence: StandardKey.Quit onActivated: Qt.quit() } } } FileDialog { id: fileDialog title: "Open a PDF file" nameFilters: [ "PDF files (*.pdf)" ] onAccepted: document.source = selectedFile } 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 source: Qt.resolvedUrl(root.source) 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 anchors.leftMargin: searchDrawer.position * searchDrawer.width document: root.document searchString: searchField.text onCurrentPageChanged: currentPageSB.value = view.currentPage + 1 } Drawer { id: searchDrawer edge: Qt.LeftEdge modal: false width: 300 y: root.header.height height: view.height dim: false clip: true ListView { id: searchResultsList anchors.fill: parent anchors.margins: 2 model: view.searchModel ScrollBar.vertical: ScrollBar { } delegate: ItemDelegate { width: parent ? parent.width : 0 RowLayout { anchors.fill: parent spacing: 0 Label { text: "Page " + (page + 1) + ": " } Label { text: contextBefore elide: Text.ElideLeft horizontalAlignment: Text.AlignRight Layout.fillWidth: true Layout.preferredWidth: parent.width / 2 } Label { font.bold: true text: view.searchString width: implicitWidth } Label { text: contextAfter elide: Text.ElideRight Layout.fillWidth: true Layout.preferredWidth: parent.width / 2 } } highlighted: ListView.isCurrentItem onClicked: { searchResultsList.currentIndex = index view.goToLocation(page, location, 0) view.searchModel.currentResult = indexOnPage } } } } footer: ToolBar { height: footerRow.implicitHeight RowLayout { id: footerRow anchors.fill: parent ToolButton { action: Action { icon.source: "qrc:/pdfviewer/resources/go-up-search.svg" shortcut: StandardKey.FindPrevious onTriggered: view.searchBack() } ToolTip.visible: enabled && hovered ToolTip.delay: 2000 ToolTip.text: "find previous" } TextField { id: searchField placeholderText: "search" Layout.minimumWidth: 150 Layout.fillWidth: true onAccepted: searchDrawer.open() Image { visible: searchField.text !== "" source: "qrc:/pdfviewer/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: "qrc:/pdfviewer/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 property size implicitPointSize: document.pagePointSize(view.currentPage) text: "page " + (currentPageSB.value) + " of " + document.pageCount + " scale " + view.renderScale.toFixed(2) + " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt" visible: document.pageCount > 0 } } } }