File indexing completed on 2025-01-19 04:52:00
0001 /* 0002 Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsystems.com> 0003 0004 This library is free software; you can redistribute it and/or modify it 0005 under the terms of the GNU Library General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or (at your 0007 option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, but WITHOUT 0010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 0012 License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to the 0016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 02110-1301, USA. 0018 */ 0019 #include "textdocumenthandler.h" 0020 0021 #include <QQuickTextDocument> 0022 #include <QTextCharFormat> 0023 #include <QTextDocument> 0024 #include <QDebug> 0025 #include "spellcheckhighlighter.h" 0026 0027 TextDocumentHandler::TextDocumentHandler(QObject *parent) 0028 : QObject(parent), 0029 mDocument(nullptr), 0030 mCursorPosition(-1), 0031 mSelectionStart(0), 0032 mSelectionEnd(0) 0033 { 0034 } 0035 0036 bool TextDocumentHandler::containsFormatting() const 0037 { 0038 if (mDocument) { 0039 for (const auto &format : mDocument->textDocument()->allFormats()) { 0040 switch(format.type()) { 0041 case QTextFormat::CharFormat: { 0042 const auto charFormat = format.toCharFormat(); 0043 if (charFormat.fontWeight() != QFont::Normal) { 0044 return true; 0045 } 0046 if (charFormat.fontItalic()) { 0047 return true; 0048 } 0049 if (charFormat.fontUnderline()) { 0050 return true; 0051 } 0052 break; 0053 } 0054 case QTextFormat::BlockFormat: 0055 case QTextFormat::FrameFormat: 0056 default: 0057 break; 0058 } 0059 } 0060 } 0061 return false; 0062 } 0063 0064 void TextDocumentHandler::resetFormat() 0065 { 0066 if (mDocument) { 0067 mDocument->textDocument()->setPlainText(mDocument->textDocument()->toPlainText()); 0068 } 0069 mCachedTextFormat = {}; 0070 reset(); 0071 } 0072 0073 QQuickTextDocument *TextDocumentHandler::document() const 0074 { 0075 return mDocument; 0076 } 0077 0078 void TextDocumentHandler::setDocument(QQuickTextDocument *document) 0079 { 0080 if (document != mDocument) { 0081 mDocument = document; 0082 connect(mDocument->textDocument(), &QTextDocument::contentsChange, this, &TextDocumentHandler::contentsChange); 0083 #ifdef KUBE_EXPERIMENTAL 0084 new SpellcheckHighlighter{mDocument->textDocument()}; 0085 #endif 0086 emit documentChanged(); 0087 emit textChanged(); 0088 } 0089 } 0090 0091 QString TextDocumentHandler::text() const 0092 { 0093 if (containsFormatting()) { 0094 return htmlText(); 0095 } 0096 return plainText(); 0097 } 0098 0099 QString TextDocumentHandler::plainText() const 0100 { 0101 if (mDocument) { 0102 return mDocument->textDocument()->toPlainText(); 0103 } 0104 return {}; 0105 } 0106 0107 QString TextDocumentHandler::htmlText() const 0108 { 0109 if (mDocument) { 0110 return mDocument->textDocument()->toHtml(); 0111 } 0112 return {}; 0113 } 0114 0115 int TextDocumentHandler::cursorPosition() const 0116 { 0117 return mCursorPosition; 0118 } 0119 0120 void TextDocumentHandler::setCursorPosition(int position) 0121 { 0122 if (position != mCursorPosition) { 0123 mCursorPosition = position; 0124 reset(); 0125 emit cursorPositionChanged(); 0126 } 0127 } 0128 0129 int TextDocumentHandler::selectionStart() const 0130 { 0131 return mSelectionStart; 0132 } 0133 0134 void TextDocumentHandler::setSelectionStart(int position) 0135 { 0136 if (position != mSelectionStart) { 0137 mSelectionStart = position; 0138 emit selectionStartChanged(); 0139 } 0140 } 0141 0142 int TextDocumentHandler::selectionEnd() const 0143 { 0144 return mSelectionEnd; 0145 } 0146 0147 void TextDocumentHandler::setSelectionEnd(int position) 0148 { 0149 if (position != mSelectionEnd) { 0150 mSelectionEnd = position; 0151 emit selectionEndChanged(); 0152 } 0153 } 0154 0155 QTextCharFormat TextDocumentHandler::charFormat() const 0156 { 0157 if (mCachedTextFormat) { 0158 return *mCachedTextFormat; 0159 } 0160 auto cursor = textCursor(); 0161 if (cursor.isNull()) { 0162 return {}; 0163 } 0164 return cursor.charFormat(); 0165 } 0166 0167 QString TextDocumentHandler::fontFamily() const 0168 { 0169 return charFormat().font().family(); 0170 } 0171 0172 void TextDocumentHandler::setFontFamily(const QString &family) 0173 { 0174 QTextCharFormat format; 0175 format.setFontFamily(family); 0176 mergeFormatOnWordOrSelection(format); 0177 emit fontFamilyChanged(); 0178 } 0179 0180 QColor TextDocumentHandler::textColor() const 0181 { 0182 return charFormat().foreground().color(); 0183 } 0184 0185 void TextDocumentHandler::setTextColor(const QColor &color) 0186 { 0187 QTextCharFormat format; 0188 format.setForeground(QBrush(color)); 0189 mergeFormatOnWordOrSelection(format); 0190 emit textColorChanged(); 0191 } 0192 0193 Qt::Alignment TextDocumentHandler::alignment() const 0194 { 0195 auto cursor = textCursor(); 0196 if (cursor.isNull()) { 0197 return Qt::AlignLeft; 0198 } 0199 return cursor.blockFormat().alignment(); 0200 } 0201 0202 void TextDocumentHandler::setAlignment(Qt::Alignment alignment) 0203 { 0204 QTextBlockFormat format; 0205 format.setAlignment(alignment); 0206 QTextCursor cursor = textCursor(); 0207 cursor.mergeBlockFormat(format); 0208 emit alignmentChanged(); 0209 } 0210 0211 bool TextDocumentHandler::bold() const 0212 { 0213 return charFormat().fontWeight() == QFont::Bold; 0214 } 0215 0216 void TextDocumentHandler::setBold(bool bold) 0217 { 0218 QTextCharFormat format; 0219 format.setFontWeight(bold ? QFont::Bold : QFont::Normal); 0220 mergeFormatOnWordOrSelection(format); 0221 emit boldChanged(); 0222 } 0223 0224 bool TextDocumentHandler::italic() const 0225 { 0226 return charFormat().fontItalic(); 0227 } 0228 0229 void TextDocumentHandler::setItalic(bool italic) 0230 { 0231 QTextCharFormat format; 0232 format.setFontItalic(italic); 0233 mergeFormatOnWordOrSelection(format); 0234 emit italicChanged(); 0235 } 0236 0237 bool TextDocumentHandler::underline() const 0238 { 0239 return charFormat().fontUnderline(); 0240 } 0241 0242 void TextDocumentHandler::setUnderline(bool underline) 0243 { 0244 QTextCharFormat format; 0245 format.setFontUnderline(underline); 0246 mergeFormatOnWordOrSelection(format); 0247 emit underlineChanged(); 0248 } 0249 0250 int TextDocumentHandler::fontSize() const 0251 { 0252 return charFormat().font().pointSize(); 0253 } 0254 0255 void TextDocumentHandler::setFontSize(int size) 0256 { 0257 if (size <= 0) 0258 return; 0259 0260 if (charFormat().property(QTextFormat::FontPointSize).toInt() == size) 0261 return; 0262 0263 QTextCharFormat format; 0264 format.setFontPointSize(size); 0265 mergeFormatOnWordOrSelection(format); 0266 emit fontSizeChanged(); 0267 } 0268 0269 void TextDocumentHandler::reset() 0270 { 0271 emit fontFamilyChanged(); 0272 emit alignmentChanged(); 0273 emit boldChanged(); 0274 emit italicChanged(); 0275 emit underlineChanged(); 0276 emit fontSizeChanged(); 0277 emit textColorChanged(); 0278 } 0279 0280 QTextCursor TextDocumentHandler::textCursor() const 0281 { 0282 if (mDocument) { 0283 if (QTextDocument *doc = mDocument->textDocument()) { 0284 QTextCursor cursor = QTextCursor(doc); 0285 if (mSelectionStart != mSelectionEnd) { 0286 cursor.setPosition(mSelectionStart); 0287 cursor.setPosition(mSelectionEnd, QTextCursor::KeepAnchor); 0288 } else { 0289 cursor.setPosition(mCursorPosition); 0290 } 0291 return cursor; 0292 } 0293 } 0294 return QTextCursor(); 0295 } 0296 0297 void TextDocumentHandler::contentsChange(int position, int charsRemoved, int charsAdded) 0298 { 0299 Q_UNUSED(charsRemoved) 0300 if (mCachedTextFormat) { 0301 if (charsAdded) { 0302 //Apply cached formatting 0303 QTextCursor cursor = textCursor(); 0304 cursor.setPosition(position + charsAdded, QTextCursor::KeepAnchor); 0305 cursor.mergeCharFormat(*mCachedTextFormat); 0306 //This is somehow necessary, otherwise space can break in the editor. 0307 cursor.setPosition(position + charsAdded, QTextCursor::MoveAnchor); 0308 } 0309 mCachedTextFormat = {}; 0310 } 0311 emit textChanged(); 0312 } 0313 0314 void TextDocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format) 0315 { 0316 QTextCursor cursor = textCursor(); 0317 0318 if (cursor.hasSelection()) { 0319 cursor.mergeCharFormat(format); 0320 } else { 0321 if (mCachedTextFormat) { 0322 mCachedTextFormat->merge(format); 0323 } else { 0324 //If we have nothing to format right now we cache the result until the next char is inserted. 0325 mCachedTextFormat = QSharedPointer<QTextCharFormat>::create(format); 0326 } 0327 } 0328 } 0329 0330 0331 bool TextDocumentHandler::isHtml(const QString &text) 0332 { 0333 return Qt::mightBeRichText(text); 0334 }