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 0000000000000000000000000000000000000000..ae17ab93b5f5481b8cd19e5135fac2a8ea98987c --- /dev/null +++ b/examples/pdf/multipage/resources/go-down-search.svg @@ -0,0 +1,13 @@ +<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> 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 0000000000000000000000000000000000000000..5cc1558731a56fbd07f192d703e57027ac1ace21 --- /dev/null +++ b/examples/pdf/multipage/resources/go-up-search.svg @@ -0,0 +1,8 @@ +<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> diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index bbc28cd8dbacd727e8890644661a9c9920064caf..3d9cd371ad86ad531c47157a43bea97117e32777 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 fa3561caf0a857175c02157a2262294c43c32b61..9698a2689322a587a90bf65f5909d5ed708e68f3 100644 --- a/examples/pdf/multipage/viewer.qrc +++ b/examples/pdf/multipage/viewer.qrc @@ -3,8 +3,10 @@ <file>viewer.qml</file> <file>resources/edit-clear.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-previous-view-page.svg</file> + <file>resources/go-up-search.svg</file> <file>resources/rotate-left.svg</file> <file>resources/rotate-right.svg</file> <file>resources/zoom-in.svg</file> diff --git a/src/pdf/quick/qml/PdfMultiPageView.qml b/src/pdf/quick/qml/PdfMultiPageView.qml index acef9fbea789f593514f28e076c4a85aea5eab74..bc5134267bcdb539ac7a6022f5d3a6de902ff51c 100644 --- a/src/pdf/quick/qml/PdfMultiPageView.qml +++ b/src/pdf/quick/qml/PdfMultiPageView.qml @@ -58,32 +58,33 @@ Item { // public API // TODO 5.15: required property property var document: undefined - property real renderScale: 1 - property real pageRotation: 0 - property string searchString + property string selectedText - property alias currentPage: navigationStack.currentPage function copySelectionToClipboard() { if (listView.currentItem !== null) listView.currentItem.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 resetScale() { - root.renderScale = 1 + function back() { navigationStack.back() } + function forward() { navigationStack.forward() } + function goToPage(page) { goToLocation(page, Qt.point(0, 0), 0) } + function goToLocation(page, location, zoom) { + if (zoom > 0) + root.renderScale = zoom + navigationStack.push(page, location, zoom) } + // page scaling + property real renderScale: 1 + property real pageRotation: 0 + function resetScale() { root.renderScale = 1 } function scaleToWidth(width, height) { root.renderScale = width / (listView.rot90 ? listView.firstPagePointSize.height : listView.firstPagePointSize.width) } - function scaleToPage(width, height) { var windowAspect = width / height var pageAspect = listView.firstPagePointSize.width / listView.firstPagePointSize.height @@ -102,14 +103,39 @@ Item { } } - function goToPage(page) { - goToLocation(page, Qt.point(0, 0), 0) + // text search + 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 goToLocation(page, location, zoom) { - if (zoom > 0) - root.renderScale = zoom - navigationStack.push(page, location, zoom) + function searchForward() { + if (searchModel.currentResult < searchModel.matchGeometry.length - 1) { + ++searchModel.currentResult + } else { + 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 @@ -133,7 +159,7 @@ Item { property real pageScale: image.paintedWidth / pagePointSize.width Image { id: image - source: document.source + source: searchModel.deferRendering ? "" : document.source currentFrame: index asynchronous: true fillMode: Image.PreserveAspectFit @@ -152,17 +178,25 @@ Item { Shape { anchors.fill: parent opacity: 0.25 - visible: image.status === Image.Ready + visible: image.status === Image.Ready && searchModel.page == index ShapePath { strokeWidth: 1 - strokeColor: "blue" - fillColor: "cyan" + strokeColor: "steelblue" + fillColor: "lightsteelblue" scale: Qt.size(paper.pageScale, paper.pageScale) PathMultiline { - id: searchResultBoundaries paths: searchModel.matchGeometry } } + ShapePath { + strokeWidth: 1 + strokeColor: "blue" + fillColor: "cyan" + scale: Qt.size(paper.pageScale, paper.pageScale) + PathPolyline { + path: searchModel.matchGeometry[searchModel.currentResult] + } + } ShapePath { fillColor: "orange" scale: Qt.size(paper.pageScale, paper.pageScale) @@ -172,12 +206,6 @@ Item { } } } - PdfSearchModel { - id: searchModel - document: root.document - page: image.currentFrame - searchString: root.searchString - } PdfSelection { id: selection document: root.document @@ -274,4 +302,12 @@ Item { onCurrentZoomChanged: root.renderScale = currentZoom // 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 + } }