Commit e5a33355 authored by Shawn Rutledge's avatar Shawn Rutledge
Browse files

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: default avatarShawn Rutledge <shawn.rutledge@qt.io>
Showing with 148 additions and 56 deletions
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 4.7070312 8 L 4 8.7070312 L 10.125 14.832031 L 12 16.707031 L 13.875 14.832031 L 20 8.7070312 L 19.292969 8 L 13.167969 14.125 L 12 15.292969 L 10.832031 14.125 L 4.7070312 8 z "
class="ColorScheme-Text"
/>
</svg>
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
<path d="M4.707 16L4 15.293l8-8 8 8-.707.707L12 8.707" class="ColorScheme-Text" fill="currentColor"/>
</svg>
...@@ -167,30 +167,6 @@ ApplicationWindow { ...@@ -167,30 +167,6 @@ ApplicationWindow {
onTriggered: view.copySelectionToClipboard() 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 { Shortcut {
sequence: StandardKey.Quit sequence: StandardKey.Quit
onActivated: Qt.quit() onActivated: Qt.quit()
...@@ -259,6 +235,63 @@ ApplicationWindow { ...@@ -259,6 +235,63 @@ ApplicationWindow {
onCurrentPageChanged: currentPageSB.value = view.currentPage + 1 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 { footer: ToolBar {
height: statusLabel.implicitHeight * 1.5 height: statusLabel.implicitHeight * 1.5
Label { Label {
......
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
<file>viewer.qml</file> <file>viewer.qml</file>
<file>resources/edit-clear.svg</file> <file>resources/edit-clear.svg</file>
<file>resources/edit-copy.svg</file> <file>resources/edit-copy.svg</file>
<file>resources/go-down-search.svg</file>
<file>resources/go-next-view-page.svg</file> <file>resources/go-next-view-page.svg</file>
<file>resources/go-previous-view-page.svg</file> <file>resources/go-previous-view-page.svg</file>
<file>resources/go-up-search.svg</file>
<file>resources/rotate-left.svg</file> <file>resources/rotate-left.svg</file>
<file>resources/rotate-right.svg</file> <file>resources/rotate-right.svg</file>
<file>resources/zoom-in.svg</file> <file>resources/zoom-in.svg</file>
......
...@@ -58,32 +58,33 @@ Item { ...@@ -58,32 +58,33 @@ Item {
// public API // public API
// TODO 5.15: required property // TODO 5.15: required property
property var document: undefined property var document: undefined
property real renderScale: 1
property real pageRotation: 0
property string searchString
property string selectedText property string selectedText
property alias currentPage: navigationStack.currentPage
function copySelectionToClipboard() { function copySelectionToClipboard() {
if (listView.currentItem !== null) if (listView.currentItem !== null)
listView.currentItem.selection.copyToClipboard() listView.currentItem.selection.copyToClipboard()
} }
// page navigation
property alias currentPage: navigationStack.currentPage
property alias backEnabled: navigationStack.backAvailable property alias backEnabled: navigationStack.backAvailable
property alias forwardEnabled: navigationStack.forwardAvailable property alias forwardEnabled: navigationStack.forwardAvailable
function back() { function back() { navigationStack.back() }
navigationStack.back() function forward() { navigationStack.forward() }
} function goToPage(page) { goToLocation(page, Qt.point(0, 0), 0) }
function forward() { function goToLocation(page, location, zoom) {
navigationStack.forward() if (zoom > 0)
} root.renderScale = zoom
navigationStack.push(page, location, zoom)
function resetScale() {
root.renderScale = 1
} }
// page scaling
property real renderScale: 1
property real pageRotation: 0
function resetScale() { root.renderScale = 1 }
function scaleToWidth(width, height) { function scaleToWidth(width, height) {
root.renderScale = width / (listView.rot90 ? listView.firstPagePointSize.height : listView.firstPagePointSize.width) root.renderScale = width / (listView.rot90 ? listView.firstPagePointSize.height : listView.firstPagePointSize.width)
} }
function scaleToPage(width, height) { function scaleToPage(width, height) {
var windowAspect = width / height var windowAspect = width / height
var pageAspect = listView.firstPagePointSize.width / listView.firstPagePointSize.height var pageAspect = listView.firstPagePointSize.width / listView.firstPagePointSize.height
...@@ -102,14 +103,39 @@ Item { ...@@ -102,14 +103,39 @@ Item {
} }
} }
function goToPage(page) { // text search
goToLocation(page, Qt.point(0, 0), 0) property alias searchString: searchModel.searchString
property bool searchBackEnabled: searchModel.currentResult > 0
property bool searchForwardEnabled: searchModel.currentResult < searchModel.matchGeometry.length - 1
function searchBack() {
if (searchModel.currentResult > 0) {
--searchModel.currentResult
} else {
searchModel.deferRendering = true // save time while we are searching
while (searchModel.currentResult <= 0) {
if (navigationStack.currentPage > 0)
goToPage(navigationStack.currentPage - 1)
else
goToPage(document.pageCount - 1)
searchModel.currentResult = searchModel.matchGeometry.length - 1
}
searchModel.deferRendering = false
}
} }
function searchForward() {
function goToLocation(page, location, zoom) { if (searchModel.currentResult < searchModel.matchGeometry.length - 1) {
if (zoom > 0) ++searchModel.currentResult
root.renderScale = zoom } else {
navigationStack.push(page, location, zoom) searchModel.deferRendering = true // save time while we are searching
while (searchModel.currentResult >= searchModel.matchGeometry.length - 1) {
searchModel.currentResult = 0
if (navigationStack.currentPage < document.pageCount - 1)
goToPage(navigationStack.currentPage + 1)
else
goToPage(0)
}
searchModel.deferRendering = false
}
} }
id: root id: root
...@@ -133,7 +159,7 @@ Item { ...@@ -133,7 +159,7 @@ Item {
property real pageScale: image.paintedWidth / pagePointSize.width property real pageScale: image.paintedWidth / pagePointSize.width
Image { Image {
id: image id: image
source: document.source source: searchModel.deferRendering ? "" : document.source
currentFrame: index currentFrame: index
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
...@@ -152,17 +178,25 @@ Item { ...@@ -152,17 +178,25 @@ Item {
Shape { Shape {
anchors.fill: parent anchors.fill: parent
opacity: 0.25 opacity: 0.25
visible: image.status === Image.Ready visible: image.status === Image.Ready && searchModel.page == index
ShapePath { ShapePath {
strokeWidth: 1 strokeWidth: 1
strokeColor: "blue" strokeColor: "steelblue"
fillColor: "cyan" fillColor: "lightsteelblue"
scale: Qt.size(paper.pageScale, paper.pageScale) scale: Qt.size(paper.pageScale, paper.pageScale)
PathMultiline { PathMultiline {
id: searchResultBoundaries
paths: searchModel.matchGeometry paths: searchModel.matchGeometry
} }
} }
ShapePath {
strokeWidth: 1
strokeColor: "blue"
fillColor: "cyan"
scale: Qt.size(paper.pageScale, paper.pageScale)
PathPolyline {
path: searchModel.matchGeometry[searchModel.currentResult]
}
}
ShapePath { ShapePath {
fillColor: "orange" fillColor: "orange"
scale: Qt.size(paper.pageScale, paper.pageScale) scale: Qt.size(paper.pageScale, paper.pageScale)
...@@ -172,12 +206,6 @@ Item { ...@@ -172,12 +206,6 @@ Item {
} }
} }
} }
PdfSearchModel {
id: searchModel
document: root.document
page: image.currentFrame
searchString: root.searchString
}
PdfSelection { PdfSelection {
id: selection id: selection
document: root.document document: root.document
...@@ -274,4 +302,12 @@ Item { ...@@ -274,4 +302,12 @@ Item {
onCurrentZoomChanged: root.renderScale = currentZoom onCurrentZoomChanged: root.renderScale = currentZoom
// TODO deal with horizontal location (need another Flickable probably) // TODO deal with horizontal location (need another Flickable probably)
} }
PdfSearchModel {
id: searchModel
document: root.document === undefined ? null : root.document
page: navigationStack.currentPage
searchString: root.searchString
property int currentResult: 0
property bool deferRendering: false
}
} }
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment