File indexing completed on 2024-06-16 04:56:14

0001 /*
0002     view/anchorcache.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2022 g10 Code GmbH
0006     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-kleopatra.h>
0012 
0013 #include "anchorcache_p.h"
0014 
0015 #include <QTextBlock>
0016 #include <QTextCursor>
0017 #include <QTextDocument>
0018 
0019 void AnchorCache::setText(const QString &text)
0020 {
0021     mText = text;
0022     mAnchorsValid = false;
0023 }
0024 
0025 void AnchorCache::clear()
0026 {
0027     mText.clear();
0028     mAnchorsValid = false;
0029 }
0030 
0031 int AnchorCache::size() const
0032 {
0033     return anchors().size();
0034 }
0035 
0036 const AnchorData &AnchorCache::operator[](int index) const
0037 {
0038     return anchors()[index];
0039 }
0040 
0041 int AnchorCache::findAnchor(int start) const
0042 {
0043     anchors(); // ensure that the anchor cache is valid
0044     auto it = std::find_if(std::cbegin(mAnchors), std::cend(mAnchors), [start](const auto &anchor) {
0045         return anchor.start == start;
0046     });
0047     if (it != std::cend(mAnchors)) {
0048         return std::distance(std::cbegin(mAnchors), it);
0049     }
0050     return -1;
0051 }
0052 
0053 const std::vector<AnchorData> &AnchorCache::anchors() const
0054 {
0055     if (mAnchorsValid) {
0056         return mAnchors;
0057     }
0058 
0059     mAnchors.clear();
0060     if (mText.isEmpty()) {
0061         return mAnchors;
0062     }
0063 
0064     QTextDocument doc;
0065     doc.setHtml(mText);
0066 
0067     // taken from QWidgetTextControl::setFocusToNextOrPreviousAnchor and QWidgetTextControl::findNextPrevAnchor
0068     for (QTextBlock block = doc.begin(); block.isValid(); block = block.next()) {
0069         QTextBlock::Iterator it = block.begin();
0070 
0071         while (!it.atEnd()) {
0072             const QTextFragment fragment = it.fragment();
0073             const QTextCharFormat fmt = fragment.charFormat();
0074 
0075             if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) {
0076                 const int anchorStart = fragment.position();
0077                 const QString anchorHref = fmt.anchorHref();
0078                 int anchorEnd = -1;
0079 
0080                 // find next non-anchor fragment
0081                 for (; !it.atEnd(); ++it) {
0082                     const QTextFragment fragment = it.fragment();
0083                     const QTextCharFormat fmt = fragment.charFormat();
0084 
0085                     if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) {
0086                         anchorEnd = fragment.position();
0087                         break;
0088                     }
0089                 }
0090 
0091                 if (anchorEnd == -1) {
0092                     anchorEnd = block.position() + block.length() - 1;
0093                 }
0094 
0095                 QTextCursor cursor{&doc};
0096                 cursor.setPosition(anchorStart);
0097                 cursor.setPosition(anchorEnd, QTextCursor::KeepAnchor);
0098                 QString anchorText = cursor.selectedText();
0099                 mAnchors.push_back({anchorStart, anchorEnd, anchorText, anchorHref});
0100             } else {
0101                 ++it;
0102             }
0103         }
0104     }
0105 
0106     mAnchorsValid = true;
0107     return mAnchors;
0108 }