Messagelib

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

KDE's Doxygen guidelines are available online.