Messagelib

richtextcomposersignatures.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "richtextcomposersignatures.h"
8#include "richtextcomposerng.h"
9
10#include <KIdentityManagementCore/Signature>
11
12#include <QRegularExpression>
13#include <QTextBlock>
14using namespace MessageComposer;
15
16class RichTextComposerSignatures::RichTextComposerSignaturesPrivate
17{
18public:
19 RichTextComposerSignaturesPrivate(RichTextComposerNg *composer)
20 : richTextComposer(composer)
21 {
22 }
23
24 void cleanWhitespaceHelper(const QRegularExpression &regExp, const QString &newText, const KIdentityManagementCore::Signature &sig);
25 [[nodiscard]] QList<QPair<int, int>> signaturePositions(const KIdentityManagementCore::Signature &sig) const;
26 RichTextComposerNg *const richTextComposer;
27};
28
29RichTextComposerSignatures::RichTextComposerSignatures(MessageComposer::RichTextComposerNg *composer, QObject *parent)
30 : QObject(parent)
31 , d(new RichTextComposerSignaturesPrivate(composer))
32{
33}
34
35RichTextComposerSignatures::~RichTextComposerSignatures() = default;
36
37void RichTextComposerSignatures::RichTextComposerSignaturesPrivate::cleanWhitespaceHelper(const QRegularExpression &regExp,
38 const QString &newText,
40{
41 int currentSearchPosition = 0;
42
43 for (;;) {
44 // Find the text
45 const QString text = richTextComposer->document()->toPlainText();
46 const auto currentMatch = regExp.match(text, currentSearchPosition);
47 if (!currentMatch.hasMatch()) {
48 break;
49 }
50 currentSearchPosition = currentMatch.capturedStart();
51
52 // Select the text
53 QTextCursor cursor(richTextComposer->document());
54 cursor.setPosition(currentSearchPosition);
55 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, currentMatch.capturedLength());
56 // Skip quoted text
57 if (richTextComposer->isLineQuoted(cursor.block().text())) {
58 currentSearchPosition += currentMatch.capturedLength();
59 continue;
60 }
61 // Skip text inside signatures
62 bool insideSignature = false;
63 const QList<QPair<int, int>> sigPositions = signaturePositions(sig);
64 for (const QPair<int, int> &position : sigPositions) {
65 if (cursor.position() >= position.first && cursor.position() <= position.second) {
66 insideSignature = true;
67 }
68 }
69 if (insideSignature) {
70 currentSearchPosition += currentMatch.capturedLength();
71 continue;
72 }
73
74 // Replace the text
75 cursor.removeSelectedText();
76 cursor.insertText(newText);
77 currentSearchPosition += newText.length();
78 }
79}
80
81void RichTextComposerSignatures::cleanWhitespace(const KIdentityManagementCore::Signature &sig)
82{
83 QTextCursor cursor(d->richTextComposer->document());
84 cursor.beginEditBlock();
85
86 // Squeeze tabs and spaces
87 d->cleanWhitespaceHelper(QRegularExpression(QLatin1StringView("[\t ]+")), QStringLiteral(" "), sig);
88
89 // Remove trailing whitespace
90 d->cleanWhitespaceHelper(QRegularExpression(QLatin1StringView("[\t ][\n]")), QStringLiteral("\n"), sig);
91
92 // Single space lines
93 d->cleanWhitespaceHelper(QRegularExpression(QLatin1StringView("[\n]{3,}")), QStringLiteral("\n\n"), sig);
94
95 if (!d->richTextComposer->textCursor().hasSelection()) {
96 d->richTextComposer->textCursor().clearSelection();
97 }
98
99 cursor.endEditBlock();
100}
101
102QList<QPair<int, int>> RichTextComposerSignatures::RichTextComposerSignaturesPrivate::signaturePositions(const KIdentityManagementCore::Signature &sig) const
103{
104 QList<QPair<int, int>> signaturePositions;
105 if (!sig.rawText().isEmpty()) {
106 QString sigText = sig.toPlainText();
107
108 int currentSearchPosition = 0;
109 for (;;) {
110 // Find the next occurrence of the signature text
111 const QString text = richTextComposer->document()->toPlainText();
112 const int currentMatch = text.indexOf(sigText, currentSearchPosition);
113 currentSearchPosition = currentMatch + sigText.length();
114 if (currentMatch == -1) {
115 break;
116 }
117
118 signaturePositions.append(QPair<int, int>(currentMatch, currentMatch + sigText.length()));
119 }
120 }
121 return signaturePositions;
122}
123
124bool RichTextComposerSignatures::replaceSignature(const KIdentityManagementCore::Signature &oldSig, const KIdentityManagementCore::Signature &newSig)
125{
126 bool found = false;
127 if (oldSig == newSig) {
128 return false;
129 }
130 QString oldSigText = oldSig.toPlainText();
131 if (oldSigText.isEmpty()) {
132 return false;
133 }
134 QTextCursor cursor(d->richTextComposer->document());
135 cursor.beginEditBlock();
136 int currentSearchPosition = 0;
137 for (;;) {
138 // Find the next occurrence of the signature text
139 const QString text = d->richTextComposer->document()->toPlainText();
140 const int currentMatch = text.indexOf(oldSigText, currentSearchPosition);
141 currentSearchPosition = currentMatch;
142 if (currentMatch == -1) {
143 break;
144 }
145
146 // Select the signature
147 cursor.setPosition(currentMatch);
148
149 // If the new signature is completely empty, we also want to remove the
150 // signature separator, so include it in the selection
151 int additionalMove = 0;
152 if (newSig.rawText().isEmpty() && text.mid(currentMatch - 4, 4) == QLatin1StringView("-- \n")) {
154 additionalMove = 4;
155 } else if (newSig.rawText().isEmpty() && text.mid(currentMatch - 1, 1) == QLatin1Char('\n')) {
157 additionalMove = 1;
158 }
159 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, oldSigText.length() + additionalMove);
160 // Skip quoted signatures
161 if (d->richTextComposer->isLineQuoted(cursor.block().text())) {
162 currentSearchPosition += oldSig.toPlainText().length();
163 continue;
164 }
165 // Remove the old and insert the new signature
166 cursor.removeSelectedText();
167 d->richTextComposer->setTextCursor(cursor);
169 found = true;
170
171 currentSearchPosition += newSig.toPlainText().length();
172 }
173
174 cursor.endEditBlock();
175 return found;
176}
177
178#include "moc_richtextcomposersignatures.cpp"
QString rawText(bool *ok=nullptr, QString *errorMessage=nullptr) const
The RichTextComposerNg class.
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
void append(QList< T > &&value)
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
bool isEmpty() const const
qsizetype length() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 19 2024 11:56:56 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.