diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/pdf/api/qpdfdocument.h | 1 | ||||
-rw-r--r-- | src/pdf/api/qpdfdocument_p.h | 1 | ||||
-rw-r--r-- | src/pdf/api/qpdfsearchmodel.h | 3 | ||||
-rw-r--r-- | src/pdf/api/qpdfsearchresult.h | 8 | ||||
-rw-r--r-- | src/pdf/api/qpdfsearchresult_p.h | 8 | ||||
-rw-r--r-- | src/pdf/qpdfdocument.cpp | 43 | ||||
-rw-r--r-- | src/pdf/qpdfsearchmodel.cpp | 42 | ||||
-rw-r--r-- | src/pdf/qpdfsearchresult.cpp | 16 | ||||
-rw-r--r-- | src/pdf/quick/plugin.cpp | 4 | ||||
-rw-r--r-- | src/pdf/quick/qml/+material/PdfStyle.qml | 54 | ||||
-rw-r--r-- | src/pdf/quick/qml/+universal/PdfStyle.qml | 55 | ||||
-rw-r--r-- | src/pdf/quick/qml/PdfMultiPageView.qml | 147 | ||||
-rw-r--r-- | src/pdf/quick/qml/PdfPageView.qml | 3 | ||||
-rw-r--r-- | src/pdf/quick/qml/PdfScrollablePageView.qml | 150 | ||||
-rw-r--r-- | src/pdf/quick/qml/PdfStyle.qml | 54 | ||||
-rw-r--r-- | src/pdf/quick/qquickpdfselection.cpp | 16 | ||||
-rw-r--r-- | src/pdf/quick/qquickpdfselection_p.h | 1 | ||||
-rw-r--r-- | src/pdf/quick/resources.qrc | 3 |
18 files changed, 454 insertions, 155 deletions
diff --git a/src/pdf/api/qpdfdocument.h b/src/pdf/api/qpdfdocument.h index 40df181f4..027cdeb47 100644 --- a/src/pdf/api/qpdfdocument.h +++ b/src/pdf/api/qpdfdocument.h @@ -113,6 +113,7 @@ public: QImage render(int page, QSize imageSize, QPdfDocumentRenderOptions options = QPdfDocumentRenderOptions()); Q_INVOKABLE QPdfSelection getSelection(int page, QPointF start, QPointF end); + Q_INVOKABLE QPdfSelection getAllText(int page); Q_SIGNALS: void passwordChanged(); diff --git a/src/pdf/api/qpdfdocument_p.h b/src/pdf/api/qpdfdocument_p.h index 15d8b8259..2dcb70407 100644 --- a/src/pdf/api/qpdfdocument_p.h +++ b/src/pdf/api/qpdfdocument_p.h @@ -105,6 +105,7 @@ public: static int fpdf_GetBlock(void* param, unsigned long position, unsigned char* pBuf, unsigned long size); static void fpdf_AddSegment(struct _FX_DOWNLOADHINTS* pThis, size_t offset, size_t size); void updateLastError(); + QString getText(FPDF_TEXTPAGE textPage, int startIndex, int count); }; QT_END_NAMESPACE diff --git a/src/pdf/api/qpdfsearchmodel.h b/src/pdf/api/qpdfsearchmodel.h index cc91e214a..f1593b64b 100644 --- a/src/pdf/api/qpdfsearchmodel.h +++ b/src/pdf/api/qpdfsearchmodel.h @@ -58,7 +58,8 @@ public: Page = Qt::UserRole, IndexOnPage, Location, - Context, + ContextBefore, + ContextAfter, _Count }; Q_ENUM(Role) diff --git a/src/pdf/api/qpdfsearchresult.h b/src/pdf/api/qpdfsearchresult.h index db7af3dd9..84a2f2343 100644 --- a/src/pdf/api/qpdfsearchresult.h +++ b/src/pdf/api/qpdfsearchresult.h @@ -50,18 +50,20 @@ class QPdfSearchResultPrivate; class Q_PDF_EXPORT QPdfSearchResult : public QPdfDestination { Q_GADGET - Q_PROPERTY(QString context READ context) + Q_PROPERTY(QString contextBefore READ contextBefore) + Q_PROPERTY(QString contextAfter READ contextAfter) Q_PROPERTY(QVector<QRectF> rectangles READ rectangles) public: QPdfSearchResult(); ~QPdfSearchResult() {} - QString context() const; + QString contextBefore() const; + QString contextAfter() const; QVector<QRectF> rectangles() const; private: - QPdfSearchResult(int page, QVector<QRectF> rects, QString context); + QPdfSearchResult(int page, QVector<QRectF> rects, QString contextBefore, QString contextAfter); QPdfSearchResult(QPdfSearchResultPrivate *d); friend class QPdfDocument; friend class QPdfSearchModelPrivate; diff --git a/src/pdf/api/qpdfsearchresult_p.h b/src/pdf/api/qpdfsearchresult_p.h index a0f8e4457..615dce4e0 100644 --- a/src/pdf/api/qpdfsearchresult_p.h +++ b/src/pdf/api/qpdfsearchresult_p.h @@ -56,12 +56,14 @@ class QPdfSearchResultPrivate : public QPdfDestinationPrivate { public: QPdfSearchResultPrivate() = default; - QPdfSearchResultPrivate(int page, QVector<QRectF> rects, QString context) : + QPdfSearchResultPrivate(int page, QVector<QRectF> rects, QString contextBefore, QString contextAfter) : QPdfDestinationPrivate(page, rects.first().topLeft(), 0), - context(context), + contextBefore(contextBefore), + contextAfter(contextAfter), rects(rects) {} - QString context; + QString contextBefore; + QString contextAfter; QVector<QRectF> rects; }; diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp index 1e8a0f527..d56edf4e9 100644 --- a/src/pdf/qpdfdocument.cpp +++ b/src/pdf/qpdfdocument.cpp @@ -393,6 +393,15 @@ void QPdfDocumentPrivate::fpdf_AddSegment(_FX_DOWNLOADHINTS *pThis, size_t offse Q_UNUSED(size); } +QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage, int startIndex, int count) +{ + QVector<ushort> buf(count + 1); + // TODO is that enough space in case one unicode character is more than one in utf-16? + int len = FPDFText_GetText(textPage, startIndex, count, buf.data()); + Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator + return QString::fromUtf16(buf.constData(), len - 1); +} + /*! \class QPdfDocument \since 5.10 @@ -737,15 +746,10 @@ QPdfSelection QPdfDocument::getSelection(int page, QPointF start, QPointF end) int endIndex = FPDFText_GetCharIndexAtPos(textPage, end.x(), pageHeight - end.y(), CharacterHitTolerance, CharacterHitTolerance); if (startIndex >= 0 && endIndex != startIndex) { - QString text; if (startIndex > endIndex) qSwap(startIndex, endIndex); int count = endIndex - startIndex + 1; - QVector<ushort> buf(count + 1); - // TODO is that enough space in case one unicode character is more than one in utf-16? - int len = FPDFText_GetText(textPage, startIndex, count, buf.data()); - Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator - text = QString::fromUtf16(buf.constData(), len - 1); + QString text = d->getText(textPage, startIndex, count); QVector<QPolygonF> bounds; int rectCount = FPDFText_CountRects(textPage, startIndex, endIndex - startIndex); for (int i = 0; i < rectCount; ++i) { @@ -767,6 +771,33 @@ QPdfSelection QPdfDocument::getSelection(int page, QPointF start, QPointF end) return QPdfSelection(); } +QPdfSelection QPdfDocument::getAllText(int page) +{ + const QPdfMutexLocker lock; + FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); + double pageHeight = FPDF_GetPageHeight(pdfPage); + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + int count = FPDFText_CountChars(textPage); + if (count < 1) + return QPdfSelection(); + QString text = d->getText(textPage, 0, count); + QVector<QPolygonF> bounds; + int rectCount = FPDFText_CountRects(textPage, 0, count); + for (int i = 0; i < rectCount; ++i) { + double l, r, b, t; + FPDFText_GetRect(textPage, i, &l, &t, &r, &b); + QPolygonF poly; + poly << QPointF(l, pageHeight - t); + poly << QPointF(r, pageHeight - t); + poly << QPointF(r, pageHeight - b); + poly << QPointF(l, pageHeight - b); + poly << QPointF(l, pageHeight - t); + bounds << poly; + } + qCDebug(qLcDoc) << "on page" << page << "got" << count << "chars" << rectCount << "rects"; + return QPdfSelection(text, bounds); +} + QT_END_NAMESPACE #include "moc_qpdfdocument.cpp" diff --git a/src/pdf/qpdfsearchmodel.cpp b/src/pdf/qpdfsearchmodel.cpp index 4129c7cb7..27b7833fc 100644 --- a/src/pdf/qpdfsearchmodel.cpp +++ b/src/pdf/qpdfsearchmodel.cpp @@ -52,7 +52,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcS, "qt.pdf.search") static const int UpdateTimerInterval = 100; -static const int ContextChars = 20; +static const int ContextChars = 64; static const double CharacterHitTolerance = 6.0; QPdfSearchModel::QPdfSearchModel(QObject *parent) @@ -95,13 +95,19 @@ QVariant QPdfSearchModel::data(const QModelIndex &index, int role) const return pi.index; case Role::Location: return d->searchResults[pi.page][pi.index].location(); - case Role::Context: - return d->searchResults[pi.page][pi.index].context(); + case Role::ContextBefore: + return d->searchResults[pi.page][pi.index].contextBefore(); + case Role::ContextAfter: + return d->searchResults[pi.page][pi.index].contextAfter(); case Role::_Count: break; } - if (role == Qt::DisplayRole) - return d->searchResults[pi.page][pi.index].context(); + if (role == Qt::DisplayRole) { + const QString ret = d->searchResults[pi.page][pi.index].contextBefore() + + QLatin1String("<b>") + d->searchString + QLatin1String("</b>") + + d->searchResults[pi.page][pi.index].contextAfter(); + return ret; + } return QVariant(); } @@ -124,9 +130,9 @@ void QPdfSearchModel::setSearchString(QString searchString) return; d->searchString = searchString; - emit searchStringChanged(); beginResetModel(); d->clearResults(); + emit searchStringChanged(); endResetModel(); } @@ -161,8 +167,8 @@ void QPdfSearchModel::setDocument(QPdfDocument *document) return; d->document = document; - emit documentChanged(); d->clearResults(); + emit documentChanged(); } void QPdfSearchModel::timerEvent(QTimerEvent *event) @@ -179,7 +185,7 @@ void QPdfSearchModel::timerEvent(QTimerEvent *event) d->doSearch(d->nextPageToUpdate++); } -QPdfSearchModelPrivate::QPdfSearchModelPrivate() +QPdfSearchModelPrivate::QPdfSearchModelPrivate() : QAbstractItemModelPrivate() { } @@ -246,7 +252,7 @@ bool QPdfSearchModelPrivate::doSearch(int page) } qCDebug(qLcS) << rects.last() << "char idx" << startIndex << "->" << endIndex; } - QString context; + QString contextBefore, contextAfter; if (startIndex >= 0 || endIndex >= 0) { startIndex = qMax(0, startIndex - ContextChars); endIndex += ContextChars; @@ -255,13 +261,21 @@ bool QPdfSearchModelPrivate::doSearch(int page) QVector<ushort> buf(count + 1); int len = FPDFText_GetText(textPage, startIndex, count, buf.data()); Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator - context = QString::fromUtf16(buf.constData(), len - 1); - context = context.replace(QLatin1Char('\n'), QLatin1Char(' ')); - context = context.replace(searchString, - QLatin1String("<b>") + searchString + QLatin1String("</b>")); + QString context = QString::fromUtf16(buf.constData(), len - 1); + context = context.replace(QLatin1Char('\n'), QStringLiteral("\u23CE")); + context = context.remove(QLatin1Char('\r')); + // try to find the search string near the middle of the context if possible + int si = context.indexOf(searchString, ContextChars - 5, Qt::CaseInsensitive); + if (si < 0) + si = context.indexOf(searchString, Qt::CaseInsensitive); + if (si < 0) + qWarning() << "search string" << searchString << "not found in context" << context; + contextBefore = context.mid(0, si); + contextAfter = context.mid(si + searchString.length()); } } - newSearchResults << QPdfSearchResult(page, rects, context); + if (!rects.isEmpty()) + newSearchResults << QPdfSearchResult(page, rects, contextBefore, contextAfter); } FPDFText_FindClose(sh); FPDFText_ClosePage(textPage); diff --git a/src/pdf/qpdfsearchresult.cpp b/src/pdf/qpdfsearchresult.cpp index 1164a1d43..53da1c165 100644 --- a/src/pdf/qpdfsearchresult.cpp +++ b/src/pdf/qpdfsearchresult.cpp @@ -42,15 +42,20 @@ QT_BEGIN_NAMESPACE QPdfSearchResult::QPdfSearchResult() : QPdfSearchResult(new QPdfSearchResultPrivate()) { } -QPdfSearchResult::QPdfSearchResult(int page, QVector<QRectF> rects, QString context) : - QPdfSearchResult(new QPdfSearchResultPrivate(page, rects, context)) { } +QPdfSearchResult::QPdfSearchResult(int page, QVector<QRectF> rects, QString contextBefore, QString contextAfter) : + QPdfSearchResult(new QPdfSearchResultPrivate(page, rects, contextBefore, contextAfter)) { } QPdfSearchResult::QPdfSearchResult(QPdfSearchResultPrivate *d) : QPdfDestination(static_cast<QPdfDestinationPrivate *>(d)) { } -QString QPdfSearchResult::context() const +QString QPdfSearchResult::contextBefore() const { - return static_cast<QPdfSearchResultPrivate *>(d.data())->context; + return static_cast<QPdfSearchResultPrivate *>(d.data())->contextBefore; +} + +QString QPdfSearchResult::contextAfter() const +{ + return static_cast<QPdfSearchResultPrivate *>(d.data())->contextAfter; } QVector<QRectF> QPdfSearchResult::rectangles() const @@ -63,7 +68,8 @@ QDebug operator<<(QDebug dbg, const QPdfSearchResult &searchResult) QDebugStateSaver saver(dbg); dbg.nospace(); dbg << "QPdfSearchResult(page=" << searchResult.page() - << " context=" << searchResult.context() + << " contextBefore=" << searchResult.contextBefore() + << " contextAfter=" << searchResult.contextAfter() << " rects=" << searchResult.rectangles(); dbg << ')'; return dbg; diff --git a/src/pdf/quick/plugin.cpp b/src/pdf/quick/plugin.cpp index bb68a817e..670fe0bf9 100644 --- a/src/pdf/quick/plugin.cpp +++ b/src/pdf/quick/plugin.cpp @@ -71,7 +71,9 @@ public: void initializeEngine(QQmlEngine *engine, const char *uri) override { Q_UNUSED(uri); -#ifndef QT_STATIC +#ifdef QT_STATIC + Q_UNUSED(engine); +#else engine->addImportPath(QStringLiteral("qrc:/")); #endif } diff --git a/src/pdf/quick/qml/+material/PdfStyle.qml b/src/pdf/quick/qml/+material/PdfStyle.qml new file mode 100644 index 000000000..12df30466 --- /dev/null +++ b/src/pdf/quick/qml/+material/PdfStyle.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 QtQml 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Controls.Material 2.14 +import QtQuick.Shapes 1.14 + +QtObject { + property Control prototypeControl: Control { } + function withAlpha(color, alpha) { + return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha) + } + property color selectionColor: withAlpha(prototypeControl.palette.highlight, 0.5) + property color pageSearchResultsColor: withAlpha(Qt.lighter(Material.accentColor, 1.5), 0.5) + property color currentSearchResultStrokeColor: Material.accentColor + property real currentSearchResultStrokeWidth: 2 + property color linkUnderscoreColor: prototypeControl.palette.link + property real linkUnderscoreStrokeWidth: 1 + property var linkUnderscoreStrokeStyle: ShapePath.DashLine + property var linkUnderscoreDashPattern: [ 1, 4 ] +} diff --git a/src/pdf/quick/qml/+universal/PdfStyle.qml b/src/pdf/quick/qml/+universal/PdfStyle.qml new file mode 100644 index 000000000..e92f2a080 --- /dev/null +++ b/src/pdf/quick/qml/+universal/PdfStyle.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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 QtQml 2.14 +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Controls.Universal 2.14 +import QtQuick.Shapes 1.14 + +QtObject { + property Control prototypeControl: Control { } + function withAlpha(color, alpha) { + return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha) + } + property color selectionColor: withAlpha(prototypeControl.palette.highlight, 0.5) + property color pageSearchResultsColor: withAlpha(Qt.lighter(Universal.accent, 1.5), 0.5) + property color currentSearchResultStrokeColor: Universal.accent + property real currentSearchResultStrokeWidth: 2 + property color linkUnderscoreColor: prototypeControl.palette.link + property real linkUnderscoreStrokeWidth: 1 + property var linkUnderscoreStrokeStyle: ShapePath.DashLine + property var linkUnderscoreDashPattern: [ 1, 4 ] +} diff --git a/src/pdf/quick/qml/PdfMultiPageView.qml b/src/pdf/quick/qml/PdfMultiPageView.qml index e8eccaf3b..70bb5454f 100644 --- a/src/pdf/quick/qml/PdfMultiPageView.qml +++ b/src/pdf/quick/qml/PdfMultiPageView.qml @@ -1,48 +1,34 @@ /**************************************************************************** ** ** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ +** Contact: http://www.qt.io/licensing/ ** -** This file is part of the examples of the Qt Toolkit. +** This file is part of the QtPDF module of the Qt Toolkit. ** -** $QT_BEGIN_LICENSE:BSD$ +** $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 https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: +** 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. ** -** "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." +** 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$ ** @@ -61,6 +47,11 @@ Item { property bool debug: false property string selectedText + function selectAll() { + var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) + if (currentItem !== null) + currentItem.selection.selectAll() + } function copySelectionToClipboard() { var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) if (debug) @@ -84,6 +75,7 @@ Item { if (zoom > 0) root.renderScale = zoom navigationStack.push(page, location, zoom) + searchModel.currentPage = page } // page scaling @@ -118,16 +110,18 @@ Item { function searchForward() { ++searchModel.currentResult } id: root + PdfStyle { id: style } TableView { id: tableView anchors.fill: parent + anchors.leftMargin: 2 model: root.document === undefined ? 0 : root.document.pageCount rowSpacing: 6 - property real rotationModulus: Math.abs(root.pageRotation % 180) - property bool rot90: rotationModulus > 45 && rotationModulus < 135 + property real rotationNorm: Math.round((360 + (root.pageRotation % 360)) % 360) + property bool rot90: rotationNorm == 90 || rotationNorm == 270 onRot90Changed: forceLayout() property size firstPagePointSize: document === undefined ? Qt.size(0, 0) : document.pagePointSize(0) - contentWidth: document === undefined ? 0 : document.maxPageWidth * root.renderScale + contentWidth: document === undefined ? 0 : (rot90 ? document.maxPageHeight : document.maxPageWidth) * root.renderScale + vscroll.width + 2 // workaround for missing function (see https://codereview.qt-project.org/c/qt/qtdeclarative/+/248464) function itemAtPos(x, y, includeSpacing) { // we don't care about x (assume col 0), and assume includeSpacing is true @@ -139,7 +133,7 @@ Item { if (child.y < y && (!ret || child.y > ret.y)) ret = child } - if (root.debug) + if (root.debug && ret !== null) console.log("given y", y, "found", ret, "@", ret.y) return ret // the delegate with the largest y that is less than the given y } @@ -164,7 +158,7 @@ Item { width: image.width height: image.height rotation: root.pageRotation - anchors.centerIn: parent + anchors.centerIn: pinch.active ? undefined : parent property size pagePointSize: document.pagePointSize(index) property real pageScale: image.paintedWidth / pagePointSize.width Image { @@ -181,38 +175,47 @@ Item { image.sourceSize.width = paper.pagePointSize.width * renderScale image.sourceSize.height = 0 paper.scale = 1 + searchHighlights.update() } } Shape { anchors.fill: parent - opacity: 0.25 visible: image.status === Image.Ready + onVisibleChanged: searchHighlights.update() ShapePath { - strokeWidth: 1 - strokeColor: "cyan" - fillColor: "steelblue" + strokeWidth: -1 + fillColor: style.pageSearchResultsColor scale: Qt.size(paper.pageScale, paper.pageScale) PathMultiline { - paths: searchModel.boundingPolygonsOnPage(index) + id: searchHighlights + function update() { + // paths could be a binding, but we need to be able to "kick" it sometimes + paths = searchModel.boundingPolygonsOnPage(index) + } } } + Connections { + target: searchModel + // whenever the highlights on the _current_ page change, they actually need to change on _all_ pages + // (usually because the search string has changed) + function onCurrentPageBoundingPolygonsChanged() { searchHighlights.update() } + } ShapePath { - fillColor: "orange" + strokeWidth: -1 + fillColor: style.selectionColor scale: Qt.size(paper.pageScale, paper.pageScale) PathMultiline { - id: selectionBoundaries paths: selection.geometry } } } Shape { anchors.fill: parent - opacity: 0.5 visible: image.status === Image.Ready && searchModel.currentPage === index ShapePath { - strokeWidth: 1 - strokeColor: "blue" - fillColor: "cyan" + strokeWidth: style.currentSearchResultStrokeWidth + strokeColor: style.currentSearchResultStrokeColor + fillColor: "transparent" scale: Qt.size(paper.pageScale, paper.pageScale) PathMultiline { paths: searchModel.currentResultBoundingPolygons @@ -223,19 +226,46 @@ Item { id: pinch minimumScale: 0.1 maximumScale: root.renderScale < 4 ? 2 : 1 - minimumRotation: 0 - maximumRotation: 0 + minimumRotation: root.pageRotation + maximumRotation: root.pageRotation enabled: image.sourceSize.width < 5000 onActiveChanged: if (active) { paper.z = 10 } else { paper.z = 0 + var centroidInPoints = Qt.point(pinch.centroid.position.x / root.renderScale, + pinch.centroid.position.y / root.renderScale) + var centroidInFlickable = tableView.mapFromItem(paper, pinch.centroid.position.x, pinch.centroid.position.y) var newSourceWidth = image.sourceSize.width * paper.scale var ratio = newSourceWidth / image.sourceSize.width + if (root.debug) + console.log("pinch ended on page", index, "with centroid", pinch.centroid.position, centroidInPoints, "wrt flickable", centroidInFlickable, + "page at", pageHolder.x.toFixed(2), pageHolder.y.toFixed(2), + "contentX/Y were", tableView.contentX.toFixed(2), tableView.contentY.toFixed(2)) if (ratio > 1.1 || ratio < 0.9) { + var centroidOnPage = Qt.point(centroidInPoints.x * root.renderScale * ratio, centroidInPoints.y * root.renderScale * ratio) paper.scale = 1 + paper.x = 0 + paper.y = 0 root.renderScale *= ratio + tableView.forceLayout() + if (tableView.rotationNorm == 0) { + tableView.contentX = pageHolder.x + tableView.originX + centroidOnPage.x - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + centroidOnPage.y - centroidInFlickable.y + } else if (tableView.rotationNorm == 90) { + tableView.contentX = pageHolder.x + tableView.originX + image.height - centroidOnPage.y - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + centroidOnPage.x - centroidInFlickable.y + } else if (tableView.rotationNorm == 180) { + tableView.contentX = pageHolder.x + tableView.originX + image.width - centroidOnPage.x - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + image.height - centroidOnPage.y - centroidInFlickable.y + } else if (tableView.rotationNorm == 270) { + tableView.contentX = pageHolder.x + tableView.originX + centroidOnPage.y - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + image.width - centroidOnPage.x - centroidInFlickable.y + } + if (root.debug) + console.log("contentX/Y adjusted to", tableView.contentX.toFixed(2), tableView.contentY.toFixed(2), "y @top", pageHolder.y) + tableView.returnToBounds() } } grabPermissions: PointerHandler.CanTakeOverFromAnything @@ -255,13 +285,19 @@ Item { document: root.document page: image.currentFrame } - delegate: Rectangle { - color: "transparent" - border.color: "lightgrey" + delegate: Shape { x: rect.x * paper.pageScale y: rect.y * paper.pageScale width: rect.width * paper.pageScale height: rect.height * paper.pageScale + ShapePath { + strokeWidth: style.linkUnderscoreStrokeWidth + strokeColor: style.linkUnderscoreColor + strokeStyle: style.linkUnderscoreStrokeStyle + dashPattern: style.linkUnderscoreDashPattern + startX: 0; startY: height + PathLine { x: width; y: height } + } MouseArea { // TODO switch to TapHandler / HoverHandler in 5.15 id: linkMA anchors.fill: parent @@ -298,6 +334,7 @@ Item { } } ScrollBar.vertical: ScrollBar { + id: vscroll property bool moved: false onPositionChanged: moved = true onActiveChanged: { @@ -337,6 +374,6 @@ Item { PdfSearchModel { id: searchModel document: root.document === undefined ? null : root.document - onCurrentPageChanged: root.goToPage(currentPage) + onCurrentPageChanged: if (currentPage != navigationStack.currentPage) root.goToPage(currentPage) } } diff --git a/src/pdf/quick/qml/PdfPageView.qml b/src/pdf/quick/qml/PdfPageView.qml index dfd00a1a8..b90ad2d7f 100644 --- a/src/pdf/quick/qml/PdfPageView.qml +++ b/src/pdf/quick/qml/PdfPageView.qml @@ -46,6 +46,9 @@ Rectangle { property alias status: image.status property alias selectedText: selection.text + function selectAll() { + selection.selectAll() + } function copySelectionToClipboard() { selection.copyToClipboard() } diff --git a/src/pdf/quick/qml/PdfScrollablePageView.qml b/src/pdf/quick/qml/PdfScrollablePageView.qml index 55aa44bbf..6076e57df 100644 --- a/src/pdf/quick/qml/PdfScrollablePageView.qml +++ b/src/pdf/quick/qml/PdfScrollablePageView.qml @@ -47,6 +47,9 @@ Flickable { property alias status: image.status property alias selectedText: selection.text + function selectAll() { + selection.selectAll() + } function copySelectionToClipboard() { selection.copyToClipboard() } @@ -101,6 +104,7 @@ Flickable { // implementation id: root + PdfStyle { id: style } contentWidth: paper.width contentHeight: paper.height ScrollBar.vertical: ScrollBar { @@ -175,63 +179,76 @@ Flickable { 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 + Shape { + anchors.fill: parent + visible: image.status === Image.Ready + ShapePath { + strokeWidth: -1 + fillColor: style.pageSearchResultsColor + 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 { + strokeWidth: style.currentSearchResultStrokeWidth + strokeColor: style.currentSearchResultStrokeColor + fillColor: "transparent" + 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 + ShapePath { + fillColor: style.selectionColor + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: selection.geometry + } } } - } - 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) + Repeater { + model: PdfLinkModel { + id: linkModel + document: root.document + page: navigationStack.currentPage + } + delegate: Shape { + x: rect.x * image.pageScale + y: rect.y * image.pageScale + width: rect.width * image.pageScale + height: rect.height * image.pageScale + ShapePath { + strokeWidth: style.linkUnderscoreStrokeWidth + strokeColor: style.linkUnderscoreColor + strokeStyle: style.linkUnderscoreStrokeStyle + dashPattern: style.linkUnderscoreDashPattern + startX: 0; startY: height + PathLine { x: width; y: height } + } + 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) + } } } } + DragHandler { + id: textSelectionDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + target: null + } + TapHandler { + id: tapHandler + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + } } PinchHandler { @@ -243,32 +260,31 @@ Flickable { enabled: image.sourceSize.width < 5000 onActiveChanged: if (!active) { + var centroidInPoints = Qt.point(pinch.centroid.position.x / root.renderScale, + pinch.centroid.position.y / root.renderScale) + var centroidInFlickable = root.mapFromItem(paper, pinch.centroid.position.x, pinch.centroid.position.y) var newSourceWidth = image.sourceSize.width * paper.scale var ratio = newSourceWidth / image.sourceSize.width + if (root.debug) + console.log("pinch ended with centroid", pinch.centroid.position, centroidInPoints, "wrt flickable", centroidInFlickable, + "page at", paper.x.toFixed(2), paper.y.toFixed(2), + "contentX/Y were", root.contentX.toFixed(2), root.contentY.toFixed(2)) if (ratio > 1.1 || ratio < 0.9) { + var centroidOnPage = Qt.point(centroidInPoints.x * root.renderScale * ratio, centroidInPoints.y * root.renderScale * ratio) paper.scale = 1 - root.renderScale *= ratio + paper.x = 0 + paper.y = 0 + root.contentX = centroidOnPage.x - centroidInFlickable.x + root.contentY = centroidOnPage.y - centroidInFlickable.y + root.renderScale *= ratio // onRenderScaleChanged calls navigationStack.update() so we don't need to here + if (root.debug) + console.log("contentX/Y adjusted to", root.contentX.toFixed(2), root.contentY.toFixed(2)) + } else { + paper.x = 0 + paper.y = 0 } - // 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 - } } } diff --git a/src/pdf/quick/qml/PdfStyle.qml b/src/pdf/quick/qml/PdfStyle.qml new file mode 100644 index 000000000..090465ce6 --- /dev/null +++ b/src/pdf/quick/qml/PdfStyle.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 QtQml 2.14 +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Shapes 1.14 + +QtObject { + property Control prototypeControl: Control { } + function withAlpha(color, alpha) { + return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha) + } + property color selectionColor: withAlpha(prototypeControl.palette.highlight, 0.5) + property color pageSearchResultsColor: "#80B0C4DE" + property color currentSearchResultStrokeColor: "cyan" + property real currentSearchResultStrokeWidth: 2 + property color linkUnderscoreColor: prototypeControl.palette.link + property real linkUnderscoreStrokeWidth: 1 + property var linkUnderscoreStrokeStyle: ShapePath.DashLine + property var linkUnderscoreDashPattern: [ 1, 4 ] +} diff --git a/src/pdf/quick/qquickpdfselection.cpp b/src/pdf/quick/qquickpdfselection.cpp index d313820ba..5371e85e5 100644 --- a/src/pdf/quick/qquickpdfselection.cpp +++ b/src/pdf/quick/qquickpdfselection.cpp @@ -124,6 +124,22 @@ QVector<QPolygonF> QQuickPdfSelection::geometry() const return m_geometry; } +void QQuickPdfSelection::selectAll() +{ + QPdfSelection sel = m_document->m_doc.getAllText(m_page); + if (sel.text() != m_text) { + m_text = sel.text(); + if (QGuiApplication::clipboard()->supportsSelection()) + sel.copyToClipboard(QClipboard::Selection); + emit textChanged(); + } + + if (sel.bounds() != m_geometry) { + m_geometry = sel.bounds(); + emit geometryChanged(); + } +} + void QQuickPdfSelection::resetPoints() { bool wasHolding = m_hold; diff --git a/src/pdf/quick/qquickpdfselection_p.h b/src/pdf/quick/qquickpdfselection_p.h index a0e6d1a8d..bb4a50fed 100644 --- a/src/pdf/quick/qquickpdfselection_p.h +++ b/src/pdf/quick/qquickpdfselection_p.h @@ -86,6 +86,7 @@ public: QString text() const; QVector<QPolygonF> geometry() const; + Q_INVOKABLE void selectAll(); #if QT_CONFIG(clipboard) Q_INVOKABLE void copyToClipboard() const; #endif diff --git a/src/pdf/quick/resources.qrc b/src/pdf/quick/resources.qrc index 20cac4827..8270a2028 100644 --- a/src/pdf/quick/resources.qrc +++ b/src/pdf/quick/resources.qrc @@ -1,5 +1,8 @@ <RCC> <qresource prefix="/qt-project.org/qtpdf"> + <file>qml/+material/PdfStyle.qml</file> + <file>qml/+universal/PdfStyle.qml</file> + <file>qml/PdfStyle.qml</file> <file>qml/PdfMultiPageView.qml</file> <file>qml/PdfPageView.qml</file> <file>qml/PdfScrollablePageView.qml</file> |