From 737770bfa4bb91ed96412d18d6490db3450bcb1c Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Fri, 6 Oct 2017 11:43:33 +0200 Subject: TextDocumentHandler to deal with HTML formatting --- framework/qml/TextEditor.qml | 2 +- framework/qml/tests/tst_texteditor.qml | 2 + framework/src/CMakeLists.txt | 2 +- framework/src/domain/textdocumenthandler.cpp | 280 +++++++++++++++++++++++++++ framework/src/domain/textdocumenthandler.h | 121 ++++++++++++ framework/src/frameworkplugin.cpp | 4 +- 6 files changed, 407 insertions(+), 4 deletions(-) create mode 100644 framework/src/domain/textdocumenthandler.cpp create mode 100644 framework/src/domain/textdocumenthandler.h diff --git a/framework/qml/TextEditor.qml b/framework/qml/TextEditor.qml index 609b85db..8a5c2bb9 100644 --- a/framework/qml/TextEditor.qml +++ b/framework/qml/TextEditor.qml @@ -52,7 +52,7 @@ FocusScope { } } - Kube.DocumentHandler { + Kube.TextDocumentHandler { id: document document: edit.textDocument selectionStart: edit.selectionStart diff --git a/framework/qml/tests/tst_texteditor.qml b/framework/qml/tests/tst_texteditor.qml index 21ebe642..e6773aaa 100644 --- a/framework/qml/tests/tst_texteditor.qml +++ b/framework/qml/tests/tst_texteditor.qml @@ -40,6 +40,8 @@ TestCase { function test_2htmlConversion() { editor.htmlEnabled = true + verify(editor.text.indexOf("") !== -1) + verify(editor.text.indexOf(editor.initialText) !== -1) editor.htmlEnabled = false compare(editor.text, editor.initialText) } diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt index b66f45ed..7b188a49 100644 --- a/framework/src/CMakeLists.txt +++ b/framework/src/CMakeLists.txt @@ -31,7 +31,7 @@ set(SRCS domain/contactcontroller.cpp domain/controller.cpp domain/peoplemodel.cpp - domain/documenthandler.cpp + domain/textdocumenthandler.cpp domain/mime/htmlutils.cpp domain/mime/messageparser.cpp domain/mime/attachmentmodel.cpp diff --git a/framework/src/domain/textdocumenthandler.cpp b/framework/src/domain/textdocumenthandler.cpp new file mode 100644 index 00000000..729d39e8 --- /dev/null +++ b/framework/src/domain/textdocumenthandler.cpp @@ -0,0 +1,280 @@ +/* + Copyright (c) 2017 Christian Mollekopf + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +#include "textdocumenthandler.h" + +#include +#include +#include +#include + +TextDocumentHandler::TextDocumentHandler(QObject *parent) + : QObject(parent), + mDocument(nullptr), + mCursorPosition(-1), + mSelectionStart(0), + mSelectionEnd(0) +{ +} + +QQuickTextDocument *TextDocumentHandler::document() const +{ + return mDocument; +} + +void TextDocumentHandler::setDocument(QQuickTextDocument *document) +{ + if (document != mDocument) { + mDocument = document; + connect(mDocument->textDocument(), &QTextDocument::contentsChanged, this, [this] () { + emit textChanged(); + }); + connect(mDocument->textDocument(), &QTextDocument::contentsChange, this, &TextDocumentHandler::contentsChange); + emit documentChanged(); + } +} + +QString TextDocumentHandler::plainText() const +{ + if (mDocument) { + return mDocument->textDocument()->toPlainText(); + } + return {}; +} + +QString TextDocumentHandler::htmlText() const +{ + if (mDocument) { + return mDocument->textDocument()->toHtml(); + } + return {}; +} + +int TextDocumentHandler::cursorPosition() const +{ + return mCursorPosition; +} + +void TextDocumentHandler::setCursorPosition(int position) +{ + if (position != mCursorPosition) { + mCursorPosition = position; + reset(); + emit cursorPositionChanged(); + } +} + +int TextDocumentHandler::selectionStart() const +{ + return mSelectionStart; +} + +void TextDocumentHandler::setSelectionStart(int position) +{ + if (position != mSelectionStart) { + mSelectionStart = position; + emit selectionStartChanged(); + } +} + +int TextDocumentHandler::selectionEnd() const +{ + return mSelectionEnd; +} + +void TextDocumentHandler::setSelectionEnd(int position) +{ + if (position != mSelectionEnd) { + mSelectionEnd = position; + emit selectionEndChanged(); + } +} + +QTextCharFormat TextDocumentHandler::charFormat() const +{ + if (mCachedTextFormat) { + return *mCachedTextFormat; + } + auto cursor = textCursor(); + if (cursor.isNull()) { + return {}; + } + return cursor.charFormat(); +} + +QString TextDocumentHandler::fontFamily() const +{ + return charFormat().font().family(); +} + +void TextDocumentHandler::setFontFamily(const QString &family) +{ + QTextCharFormat format; + format.setFontFamily(family); + mergeFormatOnWordOrSelection(format); + emit fontFamilyChanged(); +} + +QColor TextDocumentHandler::textColor() const +{ + return charFormat().foreground().color(); +} + +void TextDocumentHandler::setTextColor(const QColor &color) +{ + QTextCharFormat format; + format.setForeground(QBrush(color)); + mergeFormatOnWordOrSelection(format); + emit textColorChanged(); +} + +Qt::Alignment TextDocumentHandler::alignment() const +{ + auto cursor = textCursor(); + if (cursor.isNull()) { + return Qt::AlignLeft; + } + return cursor.blockFormat().alignment(); +} + +void TextDocumentHandler::setAlignment(Qt::Alignment alignment) +{ + QTextBlockFormat format; + format.setAlignment(alignment); + QTextCursor cursor = textCursor(); + cursor.mergeBlockFormat(format); + emit alignmentChanged(); +} + +bool TextDocumentHandler::bold() const +{ + return charFormat().fontWeight() == QFont::Bold; +} + +void TextDocumentHandler::setBold(bool bold) +{ + QTextCharFormat format; + format.setFontWeight(bold ? QFont::Bold : QFont::Normal); + mergeFormatOnWordOrSelection(format); + emit boldChanged(); +} + +bool TextDocumentHandler::italic() const +{ + return charFormat().fontItalic(); +} + +void TextDocumentHandler::setItalic(bool italic) +{ + QTextCharFormat format; + format.setFontItalic(italic); + mergeFormatOnWordOrSelection(format); + emit italicChanged(); +} + +bool TextDocumentHandler::underline() const +{ + return charFormat().fontUnderline(); +} + +void TextDocumentHandler::setUnderline(bool underline) +{ + QTextCharFormat format; + format.setFontUnderline(underline); + mergeFormatOnWordOrSelection(format); + emit underlineChanged(); +} + +int TextDocumentHandler::fontSize() const +{ + return charFormat().font().pointSize(); +} + +void TextDocumentHandler::setFontSize(int size) +{ + if (size <= 0) + return; + + if (charFormat().property(QTextFormat::FontPointSize).toInt() == size) + return; + + QTextCharFormat format; + format.setFontPointSize(size); + mergeFormatOnWordOrSelection(format); + emit fontSizeChanged(); +} + +void TextDocumentHandler::reset() +{ + emit fontFamilyChanged(); + emit alignmentChanged(); + emit boldChanged(); + emit italicChanged(); + emit underlineChanged(); + emit fontSizeChanged(); + emit textColorChanged(); +} + +QTextCursor TextDocumentHandler::textCursor() const +{ + if (mDocument) { + if (QTextDocument *doc = mDocument->textDocument()) { + QTextCursor cursor = QTextCursor(doc); + if (mSelectionStart != mSelectionEnd) { + cursor.setPosition(mSelectionStart); + cursor.setPosition(mSelectionEnd, QTextCursor::KeepAnchor); + } else { + cursor.setPosition(mCursorPosition); + } + return cursor; + } + } + return QTextCursor(); +} + +void TextDocumentHandler::contentsChange(int position, int charsRemoved, int charsAdded) +{ + Q_UNUSED(charsRemoved) + if (mCachedTextFormat) { + if (charsAdded) { + //Apply cached formatting + QTextCursor cursor = textCursor(); + cursor.setPosition(position + charsAdded, QTextCursor::KeepAnchor); + cursor.mergeCharFormat(*mCachedTextFormat); + //This is somehow necessary, otherwise space can break in the editor. + cursor.setPosition(position + charsAdded, QTextCursor::MoveAnchor); + } + mCachedTextFormat = {}; + } +} + +void TextDocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format) +{ + QTextCursor cursor = textCursor(); + + if (cursor.hasSelection()) { + cursor.mergeCharFormat(format); + } else { + if (mCachedTextFormat) { + mCachedTextFormat->merge(format); + } else { + //If we have nothing to format right now we cache the result until the next char is inserted. + mCachedTextFormat = QSharedPointer::create(format); + } + } +} diff --git a/framework/src/domain/textdocumenthandler.h b/framework/src/domain/textdocumenthandler.h new file mode 100644 index 00000000..b8ae5bdb --- /dev/null +++ b/framework/src/domain/textdocumenthandler.h @@ -0,0 +1,121 @@ +/* + Copyright (c) 2017 Christian Mollekopf + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +#pragma once + +#include +#include +#include + +class QTextDocument; +class QQuickTextDocument; + +class TextDocumentHandler : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) + Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) + Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) + + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged) + Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) + + Q_PROPERTY(bool bold READ bold WRITE setBold NOTIFY boldChanged) + Q_PROPERTY(bool italic READ italic WRITE setItalic NOTIFY italicChanged) + Q_PROPERTY(bool underline READ underline WRITE setUnderline NOTIFY underlineChanged) + + Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) + + Q_PROPERTY(QString plainText READ plainText NOTIFY textChanged) + Q_PROPERTY(QString htmlText READ htmlText NOTIFY textChanged) + +public: + explicit TextDocumentHandler(QObject *parent = nullptr); + + QQuickTextDocument *document() const; + void setDocument(QQuickTextDocument *document); + + QString plainText() const; + QString htmlText() const; + + int cursorPosition() const; + void setCursorPosition(int position); + + int selectionStart() const; + void setSelectionStart(int position); + + int selectionEnd() const; + void setSelectionEnd(int position); + + QString fontFamily() const; + void setFontFamily(const QString &family); + + QColor textColor() const; + void setTextColor(const QColor &color); + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment alignment); + + bool bold() const; + void setBold(bool bold); + + bool italic() const; + void setItalic(bool italic); + + bool underline() const; + void setUnderline(bool underline); + + int fontSize() const; + void setFontSize(int size); + +Q_SIGNALS: + void documentChanged(); + void cursorPositionChanged(); + void selectionStartChanged(); + void selectionEndChanged(); + + void fontFamilyChanged(); + void textColorChanged(); + void alignmentChanged(); + + void boldChanged(); + void italicChanged(); + void underlineChanged(); + + void fontSizeChanged(); + + void textChanged(); + +private Q_SLOTS: + void contentsChange(int position, int charsRemoved, int charsAdded); + +private: + void reset(); + QTextCharFormat charFormat() const; + QTextCursor textCursor() const; + void mergeFormatOnWordOrSelection(const QTextCharFormat &format); + + QQuickTextDocument *mDocument; + int mCursorPosition; + int mSelectionStart; + int mSelectionEnd; + QSharedPointer mCachedTextFormat; +}; diff --git a/framework/src/frameworkplugin.cpp b/framework/src/frameworkplugin.cpp index c4f7e85d..9f7b03c1 100644 --- a/framework/src/frameworkplugin.cpp +++ b/framework/src/frameworkplugin.cpp @@ -29,7 +29,7 @@ #include "domain/mouseproxy.h" #include "domain/contactcontroller.h" #include "domain/peoplemodel.h" -#include "domain/documenthandler.h" +#include "domain/textdocumenthandler.h" #include "accounts/accountsmodel.h" #include "accounts/accountfactory.h" #include "settings/settings.h" @@ -76,7 +76,7 @@ void FrameworkPlugin::registerTypes (const char *uri) qmlRegisterType(uri, 1, 0, "MouseProxy"); qmlRegisterType(uri, 1, 0,"ContactController"); qmlRegisterType(uri, 1, 0,"PeopleModel"); - qmlRegisterType(uri, 1, 0, "DocumentHandler"); + qmlRegisterType(uri, 1, 0, "TextDocumentHandler"); qmlRegisterType(uri, 1, 0, "AccountFactory"); qmlRegisterType(uri, 1, 0, "AccountsModel"); -- cgit v1.2.3