-
Shawn Rutledge authored
Similar to f467edc9 : NavigationStack is kept up-to-date when the scroll position changes, and can go back to a previous position; and links can in theory jump to specific positions and zoom levels, scrolling such that a specific x coordinate is visible. Change-Id: I2add617914d89b0dc5389e7c3d12d11580a1f82f Reviewed-by:
Shawn Rutledge <shawn.rutledge@qt.io>
c0aa9d79
/****************************************************************************
**
** 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) {
if (page === navigationStack.currentPage)
return
goToLocation(page, Qt.point(0, 0), 0)
}
function goToLocation(page, location, zoom) {
if (zoom > 0)
root.renderScale = zoom
navigationStack.push(page, location, zoom)
}
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
// 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 {
onActiveChanged:
if (!active ) {
var currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale,
(root.contentY + root.height / 2) / root.renderScale)
navigationStack.update(navigationStack.currentPage, currentLocation, root.renderScale)
}
}
ScrollBar.horizontal: ScrollBar {
onActiveChanged:
if (!active ) {
var currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale,
(root.contentY + root.height / 2) / root.renderScale)
navigationStack.update(navigationStack.currentPage, currentLocation, root.renderScale)
}
}
onRenderScaleChanged: {
image.sourceSize.width = document.pagePointSize(navigationStack.currentPage).width * renderScale
image.sourceSize.height = 0
paper.scale = 1
var currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale,
(root.contentY + root.height / 2) / root.renderScale)
navigationStack.update(navigationStack.currentPage, currentLocation, root.renderScale)
}
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
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
}
PdfSearchModel {
id: searchModel
document: root.document === undefined ? null : root.document
onCurrentPageChanged: root.goToPage(currentPage)
}
PdfNavigationStack {
id: navigationStack
onJumped: {
root.renderScale = zoom
root.contentX = Math.max(0, location.x * root.renderScale - root.width / 2)
root.contentY = Math.max(0, location.y * root.renderScale - root.height / 2)
if (root.debug)
console.log("going to zoom", zoom, "loc", location,
"on page", page, "ended up @", root.contentX + ", " + root.contentY)
}
onCurrentPageChanged: searchModel.currentPage = currentPage
}
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
}
}
}
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
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
}
}
}