File indexing completed on 2024-06-23 05:18:25
0001 /* 0002 SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "richtextcomposersignatures.h" 0008 #include "richtextcomposerng.h" 0009 0010 #include <KIdentityManagementCore/Signature> 0011 0012 #include <QRegularExpression> 0013 #include <QTextBlock> 0014 using namespace MessageComposer; 0015 0016 class RichTextComposerSignatures::RichTextComposerSignaturesPrivate 0017 { 0018 public: 0019 RichTextComposerSignaturesPrivate(RichTextComposerNg *composer) 0020 : richTextComposer(composer) 0021 { 0022 } 0023 0024 void cleanWhitespaceHelper(const QRegularExpression ®Exp, const QString &newText, const KIdentityManagementCore::Signature &sig); 0025 [[nodiscard]] QList<QPair<int, int>> signaturePositions(const KIdentityManagementCore::Signature &sig) const; 0026 RichTextComposerNg *const richTextComposer; 0027 }; 0028 0029 RichTextComposerSignatures::RichTextComposerSignatures(MessageComposer::RichTextComposerNg *composer, QObject *parent) 0030 : QObject(parent) 0031 , d(new RichTextComposerSignaturesPrivate(composer)) 0032 { 0033 } 0034 0035 RichTextComposerSignatures::~RichTextComposerSignatures() = default; 0036 0037 void RichTextComposerSignatures::RichTextComposerSignaturesPrivate::cleanWhitespaceHelper(const QRegularExpression ®Exp, 0038 const QString &newText, 0039 const KIdentityManagementCore::Signature &sig) 0040 { 0041 int currentSearchPosition = 0; 0042 0043 for (;;) { 0044 // Find the text 0045 const QString text = richTextComposer->document()->toPlainText(); 0046 const auto currentMatch = regExp.match(text, currentSearchPosition); 0047 if (!currentMatch.hasMatch()) { 0048 break; 0049 } 0050 currentSearchPosition = currentMatch.capturedStart(); 0051 0052 // Select the text 0053 QTextCursor cursor(richTextComposer->document()); 0054 cursor.setPosition(currentSearchPosition); 0055 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, currentMatch.capturedLength()); 0056 // Skip quoted text 0057 if (richTextComposer->isLineQuoted(cursor.block().text())) { 0058 currentSearchPosition += currentMatch.capturedLength(); 0059 continue; 0060 } 0061 // Skip text inside signatures 0062 bool insideSignature = false; 0063 const QList<QPair<int, int>> sigPositions = signaturePositions(sig); 0064 for (const QPair<int, int> &position : sigPositions) { 0065 if (cursor.position() >= position.first && cursor.position() <= position.second) { 0066 insideSignature = true; 0067 } 0068 } 0069 if (insideSignature) { 0070 currentSearchPosition += currentMatch.capturedLength(); 0071 continue; 0072 } 0073 0074 // Replace the text 0075 cursor.removeSelectedText(); 0076 cursor.insertText(newText); 0077 currentSearchPosition += newText.length(); 0078 } 0079 } 0080 0081 void RichTextComposerSignatures::cleanWhitespace(const KIdentityManagementCore::Signature &sig) 0082 { 0083 QTextCursor cursor(d->richTextComposer->document()); 0084 cursor.beginEditBlock(); 0085 0086 // Squeeze tabs and spaces 0087 d->cleanWhitespaceHelper(QRegularExpression(QLatin1StringView("[\t ]+")), QStringLiteral(" "), sig); 0088 0089 // Remove trailing whitespace 0090 d->cleanWhitespaceHelper(QRegularExpression(QLatin1StringView("[\t ][\n]")), QStringLiteral("\n"), sig); 0091 0092 // Single space lines 0093 d->cleanWhitespaceHelper(QRegularExpression(QLatin1StringView("[\n]{3,}")), QStringLiteral("\n\n"), sig); 0094 0095 if (!d->richTextComposer->textCursor().hasSelection()) { 0096 d->richTextComposer->textCursor().clearSelection(); 0097 } 0098 0099 cursor.endEditBlock(); 0100 } 0101 0102 QList<QPair<int, int>> RichTextComposerSignatures::RichTextComposerSignaturesPrivate::signaturePositions(const KIdentityManagementCore::Signature &sig) const 0103 { 0104 QList<QPair<int, int>> signaturePositions; 0105 if (!sig.rawText().isEmpty()) { 0106 QString sigText = sig.toPlainText(); 0107 0108 int currentSearchPosition = 0; 0109 for (;;) { 0110 // Find the next occurrence of the signature text 0111 const QString text = richTextComposer->document()->toPlainText(); 0112 const int currentMatch = text.indexOf(sigText, currentSearchPosition); 0113 currentSearchPosition = currentMatch + sigText.length(); 0114 if (currentMatch == -1) { 0115 break; 0116 } 0117 0118 signaturePositions.append(QPair<int, int>(currentMatch, currentMatch + sigText.length())); 0119 } 0120 } 0121 return signaturePositions; 0122 } 0123 0124 bool RichTextComposerSignatures::replaceSignature(const KIdentityManagementCore::Signature &oldSig, const KIdentityManagementCore::Signature &newSig) 0125 { 0126 bool found = false; 0127 if (oldSig == newSig) { 0128 return false; 0129 } 0130 QString oldSigText = oldSig.toPlainText(); 0131 if (oldSigText.isEmpty()) { 0132 return false; 0133 } 0134 QTextCursor cursor(d->richTextComposer->document()); 0135 cursor.beginEditBlock(); 0136 int currentSearchPosition = 0; 0137 for (;;) { 0138 // Find the next occurrence of the signature text 0139 const QString text = d->richTextComposer->document()->toPlainText(); 0140 const int currentMatch = text.indexOf(oldSigText, currentSearchPosition); 0141 currentSearchPosition = currentMatch; 0142 if (currentMatch == -1) { 0143 break; 0144 } 0145 0146 // Select the signature 0147 cursor.setPosition(currentMatch); 0148 0149 // If the new signature is completely empty, we also want to remove the 0150 // signature separator, so include it in the selection 0151 int additionalMove = 0; 0152 if (newSig.rawText().isEmpty() && text.mid(currentMatch - 4, 4) == QLatin1StringView("-- \n")) { 0153 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 4); 0154 additionalMove = 4; 0155 } else if (newSig.rawText().isEmpty() && text.mid(currentMatch - 1, 1) == QLatin1Char('\n')) { 0156 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 1); 0157 additionalMove = 1; 0158 } 0159 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, oldSigText.length() + additionalMove); 0160 // Skip quoted signatures 0161 if (d->richTextComposer->isLineQuoted(cursor.block().text())) { 0162 currentSearchPosition += oldSig.toPlainText().length(); 0163 continue; 0164 } 0165 // Remove the old and insert the new signature 0166 cursor.removeSelectedText(); 0167 d->richTextComposer->setTextCursor(cursor); 0168 d->richTextComposer->insertSignature(newSig, KIdentityManagementCore::Signature::AtCursor, KIdentityManagementCore::Signature::AddNothing); 0169 found = true; 0170 0171 currentSearchPosition += newSig.toPlainText().length(); 0172 } 0173 0174 cursor.endEditBlock(); 0175 return found; 0176 } 0177 0178 #include "moc_richtextcomposersignatures.cpp"