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>
14 using namespace MessageComposer;
15 
16 class RichTextComposerSignatures::RichTextComposerSignaturesPrivate
17 {
18 public:
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 
29 RichTextComposerSignatures::RichTextComposerSignatures(MessageComposer::RichTextComposerNg *composer, QObject *parent)
30  : QObject(parent)
31  , d(new RichTextComposerSignaturesPrivate(composer))
32 {
33 }
34 
35 RichTextComposerSignatures::~RichTextComposerSignatures() = default;
36 
37 void 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 
81 void 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 
102 QList<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 
124 bool 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"
void append(const T &value)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
bool isEmpty() const const
int length() const const
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
The RichTextComposerNg class.
QString rawText(bool *ok=nullptr, QString *errorMessage=nullptr) const
int capturedStart(int nth) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:55:21 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.