summaryrefslogtreecommitdiffstats
path: root/examples/pdf/multipage/viewer.qml
diff options
context:
space:
mode:
Diffstat (limited to 'examples/pdf/multipage/viewer.qml')
-rw-r--r--examples/pdf/multipage/viewer.qml370
1 files changed, 233 insertions, 137 deletions
diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml
index f66c8d381..55ca2994a 100644
--- a/examples/pdf/multipage/viewer.qml
+++ b/examples/pdf/multipage/viewer.qml
@@ -1,67 +1,17 @@
-/****************************************************************************
-**
-** 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 QtQml 2.14 // workaround for QTBUG-82873
-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
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Layouts
+import QtQuick.Pdf
ApplicationWindow {
id: root
width: 800
height: 1024
color: "lightgrey"
- title: document.title
+ title: doc.title
visible: true
property string source // for main.cpp
@@ -72,7 +22,7 @@ ApplicationWindow {
ToolButton {
action: Action {
shortcut: StandardKey.Open
- icon.source: "resources/document-open.svg"
+ icon.source: "qrc:/multipage/resources/document-open.svg"
onTriggered: fileDialog.open()
}
}
@@ -80,7 +30,7 @@ ApplicationWindow {
action: Action {
shortcut: StandardKey.ZoomIn
enabled: view.renderScale < 10
- icon.source: "resources/zoom-in.svg"
+ icon.source: "qrc:/multipage/resources/zoom-in.svg"
onTriggered: view.renderScale *= Math.sqrt(2)
}
}
@@ -88,47 +38,47 @@ ApplicationWindow {
action: Action {
shortcut: StandardKey.ZoomOut
enabled: view.renderScale > 0.1
- icon.source: "resources/zoom-out.svg"
+ icon.source: "qrc:/multipage/resources/zoom-out.svg"
onTriggered: view.renderScale /= Math.sqrt(2)
}
}
ToolButton {
action: Action {
- icon.source: "resources/zoom-fit-width.svg"
+ icon.source: "qrc:/multipage/resources/zoom-fit-width.svg"
onTriggered: view.scaleToWidth(root.contentItem.width, root.contentItem.height)
}
}
ToolButton {
action: Action {
- icon.source: "resources/zoom-fit-best.svg"
+ icon.source: "qrc:/multipage/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"
+ icon.source: "qrc:/multipage/resources/zoom-original.svg"
onTriggered: view.resetScale()
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+L"
- icon.source: "resources/rotate-left.svg"
+ icon.source: "qrc:/multipage/resources/rotate-left.svg"
onTriggered: view.pageRotation -= 90
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+R"
- icon.source: "resources/rotate-right.svg"
+ icon.source: "qrc:/multipage/resources/rotate-right.svg"
onTriggered: view.pageRotation += 90
}
}
ToolButton {
action: Action {
- icon.source: "resources/go-previous-view-page.svg"
- enabled: view.backEnbled
+ icon.source: "qrc:/multipage/resources/go-previous-view-page.svg"
+ enabled: view.backEnabled
onTriggered: view.back()
}
ToolTip.visible: enabled && hovered
@@ -138,7 +88,7 @@ ApplicationWindow {
SpinBox {
id: currentPageSB
from: 1
- to: document.pageCount
+ to: doc.pageCount
editable: true
onValueModified: view.goToPage(value - 1)
Shortcut {
@@ -152,7 +102,7 @@ ApplicationWindow {
}
ToolButton {
action: Action {
- icon.source: "resources/go-next-view-page.svg"
+ icon.source: "qrc:/multipage/resources/go-next-view-page.svg"
enabled: view.forwardEnabled
onTriggered: view.forward()
}
@@ -163,21 +113,24 @@ ApplicationWindow {
ToolButton {
action: Action {
shortcut: StandardKey.SelectAll
- icon.source: "resources/edit-select-all.svg"
+ icon.source: "qrc:/multipage/resources/edit-select-all.svg"
onTriggered: view.selectAll()
}
}
ToolButton {
action: Action {
shortcut: StandardKey.Copy
- icon.source: "resources/edit-copy.svg"
+ icon.source: "qrc:/multipage/resources/edit-copy.svg"
enabled: view.selectedText !== ""
onTriggered: view.copySelectionToClipboard()
}
}
Shortcut {
sequence: StandardKey.Find
- onActivated: searchField.forceActiveFocus()
+ onActivated: {
+ searchField.forceActiveFocus()
+ searchField.selectAll()
+ }
}
Shortcut {
sequence: StandardKey.Quit
@@ -186,11 +139,11 @@ ApplicationWindow {
}
}
- Platform.FileDialog {
+ FileDialog {
id: fileDialog
title: "Open a PDF file"
nameFilters: [ "PDF files (*.pdf)" ]
- onAccepted: document.source = file
+ onAccepted: doc.source = selectedFile
}
Dialog {
@@ -202,55 +155,63 @@ ApplicationWindow {
anchors.centerIn: parent
width: 300
- TextField {
+ contentItem: TextField {
id: passwordField
placeholderText: qsTr("Please provide the password")
echoMode: TextInput.Password
width: parent.width
onAccepted: passwordDialog.accept()
}
- onAccepted: document.password = passwordField.text
+ onOpened: passwordField.forceActiveFocus()
+ onAccepted: doc.password = passwordField.text
}
Dialog {
id: errorDialog
- title: "Error loading " + document.source
- standardButtons: Dialog.Ok
+ title: "Error loading " + doc.source
+ standardButtons: Dialog.Close
modal: true
closePolicy: Popup.CloseOnEscape
anchors.centerIn: parent
width: 300
+ visible: doc.status === PdfDocument.Error
- Label {
+ contentItem: Label {
id: errorField
- text: document.error
+ text: doc.error
}
}
PdfDocument {
- id: document
+ id: doc
source: Qt.resolvedUrl(root.source)
- onStatusChanged: {
- if (status === PdfDocument.Error) errorDialog.open()
- view.document = (status === PdfDocument.Ready ? document : undefined)
- }
- onPasswordRequired: {
- passwordDialog.open()
- passwordField.forceActiveFocus()
- }
+ onPasswordRequired: passwordDialog.open()
}
PdfMultiPageView {
id: view
anchors.fill: parent
- anchors.leftMargin: searchDrawer.position * searchDrawer.width
- document: root.document
+ anchors.leftMargin: sidebar.position * sidebar.width
+ document: doc
searchString: searchField.text
onCurrentPageChanged: currentPageSB.value = view.currentPage + 1
}
+ DropArea {
+ anchors.fill: parent
+ keys: ["text/uri-list"]
+ onEntered: (drag) => {
+ drag.accepted = (drag.proposedAction === Qt.MoveAction || drag.proposedAction === Qt.CopyAction) &&
+ drag.hasUrls && drag.urls[0].endsWith("pdf")
+ }
+ onDropped: (drop) => {
+ doc.source = drop.urls[0]
+ drop.acceptProposedAction()
+ }
+ }
+
Drawer {
- id: searchDrawer
+ id: sidebar
edge: Qt.LeftEdge
modal: false
width: 300
@@ -258,58 +219,189 @@ ApplicationWindow {
height: view.height
dim: false
clip: true
- ListView {
- id: searchResultsList
+
+ TabBar {
+ id: sidebarTabs
+ x: -width
+ rotation: -90
+ transformOrigin: Item.TopRight
+ currentIndex: 2 // bookmarks by default
+ TabButton {
+ text: qsTr("Info")
+ }
+ TabButton {
+ text: qsTr("Search Results")
+ }
+ TabButton {
+ text: qsTr("Bookmarks")
+ }
+ TabButton {
+ text: qsTr("Pages")
+ }
+ }
+
+ GroupBox {
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
+ anchors.leftMargin: sidebarTabs.height
+
+ StackLayout {
+ anchors.fill: parent
+ currentIndex: sidebarTabs.currentIndex
+ component InfoField: TextInput {
+ width: parent.width
+ selectByMouse: true
+ readOnly: true
+ wrapMode: Text.WordWrap
+ }
+ Column {
+ spacing: 6
+ width: parent.width - 6
+ Label { font.bold: true; text: qsTr("Title") }
+ InfoField { text: doc.title }
+ Label { font.bold: true; text: qsTr("Author") }
+ InfoField { text: doc.author }
+ Label { font.bold: true; text: qsTr("Subject") }
+ InfoField { text: doc.subject }
+ Label { font.bold: true; text: qsTr("Keywords") }
+ InfoField { text: doc.keywords }
+ Label { font.bold: true; text: qsTr("Producer") }
+ InfoField { text: doc.producer }
+ Label { font.bold: true; text: qsTr("Creator") }
+ InfoField { text: doc.creator }
+ Label { font.bold: true; text: qsTr("Creation date") }
+ InfoField { text: doc.creationDate }
+ Label { font.bold: true; text: qsTr("Modification date") }
+ InfoField { text: doc.modificationDate }
+ }
+ ListView {
+ id: searchResultsList
+ implicitHeight: parent.height
+ model: view.searchModel
+ currentIndex: view.searchModel.currentResult
+ ScrollBar.vertical: ScrollBar { }
+ delegate: ItemDelegate {
+ id: resultDelegate
+ required property int index
+ required property int page
+ required property string contextBefore
+ required property string contextAfter
+ width: parent ? parent.width : 0
+ RowLayout {
+ anchors.fill: parent
+ spacing: 0
+ Label {
+ text: "Page " + (resultDelegate.page + 1) + ": "
+ }
+ Label {
+ text: resultDelegate.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: resultDelegate.contextAfter
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ Layout.preferredWidth: parent.width / 2
+ }
+ }
+ highlighted: ListView.isCurrentItem
+ onClicked: view.searchModel.currentResult = resultDelegate.index
}
- Label {
- font.bold: true
- text: view.searchString
- width: implicitWidth
+ }
+ TreeView {
+ id: bookmarksTree
+ implicitHeight: parent.height
+ implicitWidth: parent.width
+ columnWidthProvider: function() { return width }
+ delegate: TreeViewDelegate {
+ required property int page
+ required property point location
+ required property real zoom
+ onClicked: view.goToLocation(page, location, zoom)
}
- Label {
- text: contextAfter
- elide: Text.ElideRight
- Layout.fillWidth: true
- Layout.preferredWidth: parent.width / 2
+ model: PdfBookmarkModel {
+ document: doc
}
+ ScrollBar.vertical: ScrollBar { }
}
- highlighted: ListView.isCurrentItem
- onClicked: {
- searchResultsList.currentIndex = index
- view.goToLocation(page, location, 0)
- view.searchModel.currentResult = indexOnPage
+ GridView {
+ id: thumbnailsView
+ implicitWidth: parent.width
+ implicitHeight: parent.height
+ model: doc.pageModel
+ cellWidth: width / 2
+ cellHeight: cellWidth + 10
+ delegate: Item {
+ required property int index
+ required property string label
+ required property size pointSize
+ width: thumbnailsView.cellWidth
+ height: thumbnailsView.cellHeight
+ Rectangle {
+ id: paper
+ width: image.width
+ height: image.height
+ x: (parent.width - width) / 2
+ y: (parent.height - height - pageNumber.height) / 2
+ PdfPageImage {
+ id: image
+ document: doc
+ currentFrame: index
+ asynchronous: true
+ fillMode: Image.PreserveAspectFit
+ property bool landscape: pointSize.width > pointSize.height
+ width: landscape ? thumbnailsView.cellWidth - 6
+ : height * pointSize.width / pointSize.height
+ height: landscape ? width * pointSize.height / pointSize.width
+ : thumbnailsView.cellHeight - 14
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+ }
+ Text {
+ id: pageNumber
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: label
+ }
+ TapHandler {
+ onTapped: view.goToPage(index)
+ }
+ }
}
}
}
}
footer: ToolBar {
- height: footerRow.implicitHeight
+ height: footerRow.implicitHeight + 6
RowLayout {
id: footerRow
anchors.fill: parent
ToolButton {
action: Action {
- icon.source: "resources/go-up-search.svg"
+ id: sidebarOpenAction
+ checkable: true
+ checked: sidebar.opened
+ icon.source: checked ? "qrc:/multipage/resources/sidebar-collapse-left.svg" : "qrc:/multipage/resources/sidebar-expand-left.svg"
+ onTriggered: sidebar.open()
+ }
+ ToolTip.visible: enabled && hovered
+ ToolTip.delay: 2000
+ ToolTip.text: "open sidebar"
+ }
+ ToolButton {
+ action: Action {
+ icon.source: "qrc:/multipage/resources/go-up-search.svg"
shortcut: StandardKey.FindPrevious
+ enabled: view.searchModel.count > 0
onTriggered: view.searchBack()
}
ToolTip.visible: enabled && hovered
@@ -321,16 +413,19 @@ ApplicationWindow {
placeholderText: "search"
Layout.minimumWidth: 150
Layout.fillWidth: true
- onAccepted: searchDrawer.open()
+ Layout.bottomMargin: 3
+ onAccepted: {
+ sidebar.open()
+ sidebarTabs.setCurrentIndex(1)
+ }
Image {
visible: searchField.text !== ""
- source: "resources/edit-clear.svg"
+ source: "qrc:/multipage/resources/edit-clear.svg"
+ sourceSize.height: searchField.height - 6
anchors {
right: parent.right
- top: parent.top
- bottom: parent.bottom
+ verticalCenter: parent.verticalCenter
margins: 3
- rightMargin: 5
}
TapHandler {
onTapped: searchField.clear()
@@ -339,8 +434,9 @@ ApplicationWindow {
}
ToolButton {
action: Action {
- icon.source: "resources/go-down-search.svg"
+ icon.source: "qrc:/multipage/resources/go-down-search.svg"
shortcut: StandardKey.FindNext
+ enabled: view.searchModel.count > 0
onTriggered: view.searchForward()
}
ToolTip.visible: enabled && hovered
@@ -349,11 +445,11 @@ ApplicationWindow {
}
Label {
id: statusLabel
- property size implicitPointSize: document.pagePointSize(view.currentPage)
- text: "page " + (currentPageSB.value) + " of " + document.pageCount +
+ property size implicitPointSize: doc.pagePointSize(view.currentPage)
+ text: "page " + (currentPageSB.value) + " of " + doc.pageCount +
" scale " + view.renderScale.toFixed(2) +
" original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt"
- visible: document.pageCount > 0
+ visible: doc.pageCount > 0
}
}
}