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 }