diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-10-06 11:43:33 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-10-06 11:46:58 +0200 |
commit | 737770bfa4bb91ed96412d18d6490db3450bcb1c (patch) | |
tree | 9b4bd7a6252c687b4cb5ac3d91ebe786e7891377 | |
parent | 41e3f3544f3ec7ae3c14f18010005e3e55c003e7 (diff) | |
download | kube-737770bfa4bb91ed96412d18d6490db3450bcb1c.tar.gz kube-737770bfa4bb91ed96412d18d6490db3450bcb1c.zip |
TextDocumentHandler to deal with HTML formatting
-rw-r--r-- | framework/qml/TextEditor.qml | 2 | ||||
-rw-r--r-- | framework/qml/tests/tst_texteditor.qml | 2 | ||||
-rw-r--r-- | framework/src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | framework/src/domain/textdocumenthandler.cpp | 280 | ||||
-rw-r--r-- | framework/src/domain/textdocumenthandler.h | 121 | ||||
-rw-r--r-- | framework/src/frameworkplugin.cpp | 4 |
6 files changed, 407 insertions, 4 deletions
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 { | |||
52 | } | 52 | } |
53 | } | 53 | } |
54 | 54 | ||
55 | Kube.DocumentHandler { | 55 | Kube.TextDocumentHandler { |
56 | id: document | 56 | id: document |
57 | document: edit.textDocument | 57 | document: edit.textDocument |
58 | selectionStart: edit.selectionStart | 58 | 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 { | |||
40 | 40 | ||
41 | function test_2htmlConversion() { | 41 | function test_2htmlConversion() { |
42 | editor.htmlEnabled = true | 42 | editor.htmlEnabled = true |
43 | verify(editor.text.indexOf("<html>") !== -1) | ||
44 | verify(editor.text.indexOf(editor.initialText) !== -1) | ||
43 | editor.htmlEnabled = false | 45 | editor.htmlEnabled = false |
44 | compare(editor.text, editor.initialText) | 46 | compare(editor.text, editor.initialText) |
45 | } | 47 | } |
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 | |||
31 | domain/contactcontroller.cpp | 31 | domain/contactcontroller.cpp |
32 | domain/controller.cpp | 32 | domain/controller.cpp |
33 | domain/peoplemodel.cpp | 33 | domain/peoplemodel.cpp |
34 | domain/documenthandler.cpp | 34 | domain/textdocumenthandler.cpp |
35 | domain/mime/htmlutils.cpp | 35 | domain/mime/htmlutils.cpp |
36 | domain/mime/messageparser.cpp | 36 | domain/mime/messageparser.cpp |
37 | domain/mime/attachmentmodel.cpp | 37 | 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 @@ | |||
1 | /* | ||
2 | Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "textdocumenthandler.h" | ||
20 | |||
21 | #include <QQuickTextDocument> | ||
22 | #include <QTextCharFormat> | ||
23 | #include <QTextDocument> | ||
24 | #include <QDebug> | ||
25 | |||
26 | TextDocumentHandler::TextDocumentHandler(QObject *parent) | ||
27 | : QObject(parent), | ||
28 | mDocument(nullptr), | ||
29 | mCursorPosition(-1), | ||
30 | mSelectionStart(0), | ||
31 | mSelectionEnd(0) | ||
32 | { | ||
33 | } | ||
34 | |||
35 | QQuickTextDocument *TextDocumentHandler::document() const | ||
36 | { | ||
37 | return mDocument; | ||
38 | } | ||
39 | |||
40 | void TextDocumentHandler::setDocument(QQuickTextDocument *document) | ||
41 | { | ||
42 | if (document != mDocument) { | ||
43 | mDocument = document; | ||
44 | connect(mDocument->textDocument(), &QTextDocument::contentsChanged, this, [this] () { | ||
45 | emit textChanged(); | ||
46 | }); | ||
47 | connect(mDocument->textDocument(), &QTextDocument::contentsChange, this, &TextDocumentHandler::contentsChange); | ||
48 | emit documentChanged(); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | QString TextDocumentHandler::plainText() const | ||
53 | { | ||
54 | if (mDocument) { | ||
55 | return mDocument->textDocument()->toPlainText(); | ||
56 | } | ||
57 | return {}; | ||
58 | } | ||
59 | |||
60 | QString TextDocumentHandler::htmlText() const | ||
61 | { | ||
62 | if (mDocument) { | ||
63 | return mDocument->textDocument()->toHtml(); | ||
64 | } | ||
65 | return {}; | ||
66 | } | ||
67 | |||
68 | int TextDocumentHandler::cursorPosition() const | ||
69 | { | ||
70 | return mCursorPosition; | ||
71 | } | ||
72 | |||
73 | void TextDocumentHandler::setCursorPosition(int position) | ||
74 | { | ||
75 | if (position != mCursorPosition) { | ||
76 | mCursorPosition = position; | ||
77 | reset(); | ||
78 | emit cursorPositionChanged(); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | int TextDocumentHandler::selectionStart() const | ||
83 | { | ||
84 | return mSelectionStart; | ||
85 | } | ||
86 | |||
87 | void TextDocumentHandler::setSelectionStart(int position) | ||
88 | { | ||
89 | if (position != mSelectionStart) { | ||
90 | mSelectionStart = position; | ||
91 | emit selectionStartChanged(); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | int TextDocumentHandler::selectionEnd() const | ||
96 | { | ||
97 | return mSelectionEnd; | ||
98 | } | ||
99 | |||
100 | void TextDocumentHandler::setSelectionEnd(int position) | ||
101 | { | ||
102 | if (position != mSelectionEnd) { | ||
103 | mSelectionEnd = position; | ||
104 | emit selectionEndChanged(); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | QTextCharFormat TextDocumentHandler::charFormat() const | ||
109 | { | ||
110 | if (mCachedTextFormat) { | ||
111 | return *mCachedTextFormat; | ||
112 | } | ||
113 | auto cursor = textCursor(); | ||
114 | if (cursor.isNull()) { | ||
115 | return {}; | ||
116 | } | ||
117 | return cursor.charFormat(); | ||
118 | } | ||
119 | |||
120 | QString TextDocumentHandler::fontFamily() const | ||
121 | { | ||
122 | return charFormat().font().family(); | ||
123 | } | ||
124 | |||
125 | void TextDocumentHandler::setFontFamily(const QString &family) | ||
126 | { | ||
127 | QTextCharFormat format; | ||
128 | format.setFontFamily(family); | ||
129 | mergeFormatOnWordOrSelection(format); | ||
130 | emit fontFamilyChanged(); | ||
131 | } | ||
132 | |||
133 | QColor TextDocumentHandler::textColor() const | ||
134 | { | ||
135 | return charFormat().foreground().color(); | ||
136 | } | ||
137 | |||
138 | void TextDocumentHandler::setTextColor(const QColor &color) | ||
139 | { | ||
140 | QTextCharFormat format; | ||
141 | format.setForeground(QBrush(color)); | ||
142 | mergeFormatOnWordOrSelection(format); | ||
143 | emit textColorChanged(); | ||
144 | } | ||
145 | |||
146 | Qt::Alignment TextDocumentHandler::alignment() const | ||
147 | { | ||
148 | auto cursor = textCursor(); | ||
149 | if (cursor.isNull()) { | ||
150 | return Qt::AlignLeft; | ||
151 | } | ||
152 | return cursor.blockFormat().alignment(); | ||
153 | } | ||
154 | |||
155 | void TextDocumentHandler::setAlignment(Qt::Alignment alignment) | ||
156 | { | ||
157 | QTextBlockFormat format; | ||
158 | format.setAlignment(alignment); | ||
159 | QTextCursor cursor = textCursor(); | ||
160 | cursor.mergeBlockFormat(format); | ||
161 | emit alignmentChanged(); | ||
162 | } | ||
163 | |||
164 | bool TextDocumentHandler::bold() const | ||
165 | { | ||
166 | return charFormat().fontWeight() == QFont::Bold; | ||
167 | } | ||
168 | |||
169 | void TextDocumentHandler::setBold(bool bold) | ||
170 | { | ||
171 | QTextCharFormat format; | ||
172 | format.setFontWeight(bold ? QFont::Bold : QFont::Normal); | ||
173 | mergeFormatOnWordOrSelection(format); | ||
174 | emit boldChanged(); | ||
175 | } | ||
176 | |||
177 | bool TextDocumentHandler::italic() const | ||
178 | { | ||
179 | return charFormat().fontItalic(); | ||
180 | } | ||
181 | |||
182 | void TextDocumentHandler::setItalic(bool italic) | ||
183 | { | ||
184 | QTextCharFormat format; | ||
185 | format.setFontItalic(italic); | ||
186 | mergeFormatOnWordOrSelection(format); | ||
187 | emit italicChanged(); | ||
188 | } | ||
189 | |||
190 | bool TextDocumentHandler::underline() const | ||
191 | { | ||
192 | return charFormat().fontUnderline(); | ||
193 | } | ||
194 | |||
195 | void TextDocumentHandler::setUnderline(bool underline) | ||
196 | { | ||
197 | QTextCharFormat format; | ||
198 | format.setFontUnderline(underline); | ||
199 | mergeFormatOnWordOrSelection(format); | ||
200 | emit underlineChanged(); | ||
201 | } | ||
202 | |||
203 | int TextDocumentHandler::fontSize() const | ||
204 | { | ||
205 | return charFormat().font().pointSize(); | ||
206 | } | ||
207 | |||
208 | void TextDocumentHandler::setFontSize(int size) | ||
209 | { | ||
210 | if (size <= 0) | ||
211 | return; | ||
212 | |||
213 | if (charFormat().property(QTextFormat::FontPointSize).toInt() == size) | ||
214 | return; | ||
215 | |||
216 | QTextCharFormat format; | ||
217 | format.setFontPointSize(size); | ||
218 | mergeFormatOnWordOrSelection(format); | ||
219 | emit fontSizeChanged(); | ||
220 | } | ||
221 | |||
222 | void TextDocumentHandler::reset() | ||
223 | { | ||
224 | emit fontFamilyChanged(); | ||
225 | emit alignmentChanged(); | ||
226 | emit boldChanged(); | ||
227 | emit italicChanged(); | ||
228 | emit underlineChanged(); | ||
229 | emit fontSizeChanged(); | ||
230 | emit textColorChanged(); | ||
231 | } | ||
232 | |||
233 | QTextCursor TextDocumentHandler::textCursor() const | ||
234 | { | ||
235 | if (mDocument) { | ||
236 | if (QTextDocument *doc = mDocument->textDocument()) { | ||
237 | QTextCursor cursor = QTextCursor(doc); | ||
238 | if (mSelectionStart != mSelectionEnd) { | ||
239 | cursor.setPosition(mSelectionStart); | ||
240 | cursor.setPosition(mSelectionEnd, QTextCursor::KeepAnchor); | ||
241 | } else { | ||
242 | cursor.setPosition(mCursorPosition); | ||
243 | } | ||
244 | return cursor; | ||
245 | } | ||
246 | } | ||
247 | return QTextCursor(); | ||
248 | } | ||
249 | |||
250 | void TextDocumentHandler::contentsChange(int position, int charsRemoved, int charsAdded) | ||
251 | { | ||
252 | Q_UNUSED(charsRemoved) | ||
253 | if (mCachedTextFormat) { | ||
254 | if (charsAdded) { | ||
255 | //Apply cached formatting | ||
256 | QTextCursor cursor = textCursor(); | ||
257 | cursor.setPosition(position + charsAdded, QTextCursor::KeepAnchor); | ||
258 | cursor.mergeCharFormat(*mCachedTextFormat); | ||
259 | //This is somehow necessary, otherwise space can break in the editor. | ||
260 | cursor.setPosition(position + charsAdded, QTextCursor::MoveAnchor); | ||
261 | } | ||
262 | mCachedTextFormat = {}; | ||
263 | } | ||
264 | } | ||
265 | |||
266 | void TextDocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format) | ||
267 | { | ||
268 | QTextCursor cursor = textCursor(); | ||
269 | |||
270 | if (cursor.hasSelection()) { | ||
271 | cursor.mergeCharFormat(format); | ||
272 | } else { | ||
273 | if (mCachedTextFormat) { | ||
274 | mCachedTextFormat->merge(format); | ||
275 | } else { | ||
276 | //If we have nothing to format right now we cache the result until the next char is inserted. | ||
277 | mCachedTextFormat = QSharedPointer<QTextCharFormat>::create(format); | ||
278 | } | ||
279 | } | ||
280 | } | ||
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 @@ | |||
1 | /* | ||
2 | Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QFont> | ||
23 | #include <QTextCursor> | ||
24 | |||
25 | class QTextDocument; | ||
26 | class QQuickTextDocument; | ||
27 | |||
28 | class TextDocumentHandler : public QObject | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | |||
32 | Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged) | ||
33 | Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) | ||
34 | Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) | ||
35 | Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) | ||
36 | |||
37 | Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged) | ||
38 | Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) | ||
39 | Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) | ||
40 | |||
41 | Q_PROPERTY(bool bold READ bold WRITE setBold NOTIFY boldChanged) | ||
42 | Q_PROPERTY(bool italic READ italic WRITE setItalic NOTIFY italicChanged) | ||
43 | Q_PROPERTY(bool underline READ underline WRITE setUnderline NOTIFY underlineChanged) | ||
44 | |||
45 | Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) | ||
46 | |||
47 | Q_PROPERTY(QString plainText READ plainText NOTIFY textChanged) | ||
48 | Q_PROPERTY(QString htmlText READ htmlText NOTIFY textChanged) | ||
49 | |||
50 | public: | ||
51 | explicit TextDocumentHandler(QObject *parent = nullptr); | ||
52 | |||
53 | QQuickTextDocument *document() const; | ||
54 | void setDocument(QQuickTextDocument *document); | ||
55 | |||
56 | QString plainText() const; | ||
57 | QString htmlText() const; | ||
58 | |||
59 | int cursorPosition() const; | ||
60 | void setCursorPosition(int position); | ||
61 | |||
62 | int selectionStart() const; | ||
63 | void setSelectionStart(int position); | ||
64 | |||
65 | int selectionEnd() const; | ||
66 | void setSelectionEnd(int position); | ||
67 | |||
68 | QString fontFamily() const; | ||
69 | void setFontFamily(const QString &family); | ||
70 | |||
71 | QColor textColor() const; | ||
72 | void setTextColor(const QColor &color); | ||
73 | |||
74 | Qt::Alignment alignment() const; | ||
75 | void setAlignment(Qt::Alignment alignment); | ||
76 | |||
77 | bool bold() const; | ||
78 | void setBold(bool bold); | ||
79 | |||
80 | bool italic() const; | ||
81 | void setItalic(bool italic); | ||
82 | |||
83 | bool underline() const; | ||
84 | void setUnderline(bool underline); | ||
85 | |||
86 | int fontSize() const; | ||
87 | void setFontSize(int size); | ||
88 | |||
89 | Q_SIGNALS: | ||
90 | void documentChanged(); | ||
91 | void cursorPositionChanged(); | ||
92 | void selectionStartChanged(); | ||
93 | void selectionEndChanged(); | ||
94 | |||
95 | void fontFamilyChanged(); | ||
96 | void textColorChanged(); | ||
97 | void alignmentChanged(); | ||
98 | |||
99 | void boldChanged(); | ||
100 | void italicChanged(); | ||
101 | void underlineChanged(); | ||
102 | |||
103 | void fontSizeChanged(); | ||
104 | |||
105 | void textChanged(); | ||
106 | |||
107 | private Q_SLOTS: | ||
108 | void contentsChange(int position, int charsRemoved, int charsAdded); | ||
109 | |||
110 | private: | ||
111 | void reset(); | ||
112 | QTextCharFormat charFormat() const; | ||
113 | QTextCursor textCursor() const; | ||
114 | void mergeFormatOnWordOrSelection(const QTextCharFormat &format); | ||
115 | |||
116 | QQuickTextDocument *mDocument; | ||
117 | int mCursorPosition; | ||
118 | int mSelectionStart; | ||
119 | int mSelectionEnd; | ||
120 | QSharedPointer<QTextCharFormat> mCachedTextFormat; | ||
121 | }; | ||
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 @@ | |||
29 | #include "domain/mouseproxy.h" | 29 | #include "domain/mouseproxy.h" |
30 | #include "domain/contactcontroller.h" | 30 | #include "domain/contactcontroller.h" |
31 | #include "domain/peoplemodel.h" | 31 | #include "domain/peoplemodel.h" |
32 | #include "domain/documenthandler.h" | 32 | #include "domain/textdocumenthandler.h" |
33 | #include "accounts/accountsmodel.h" | 33 | #include "accounts/accountsmodel.h" |
34 | #include "accounts/accountfactory.h" | 34 | #include "accounts/accountfactory.h" |
35 | #include "settings/settings.h" | 35 | #include "settings/settings.h" |
@@ -76,7 +76,7 @@ void FrameworkPlugin::registerTypes (const char *uri) | |||
76 | qmlRegisterType<MouseProxy>(uri, 1, 0, "MouseProxy"); | 76 | qmlRegisterType<MouseProxy>(uri, 1, 0, "MouseProxy"); |
77 | qmlRegisterType<ContactController>(uri, 1, 0,"ContactController"); | 77 | qmlRegisterType<ContactController>(uri, 1, 0,"ContactController"); |
78 | qmlRegisterType<PeopleModel>(uri, 1, 0,"PeopleModel"); | 78 | qmlRegisterType<PeopleModel>(uri, 1, 0,"PeopleModel"); |
79 | qmlRegisterType<DocumentHandler>(uri, 1, 0, "DocumentHandler"); | 79 | qmlRegisterType<TextDocumentHandler>(uri, 1, 0, "TextDocumentHandler"); |
80 | 80 | ||
81 | qmlRegisterType<AccountFactory>(uri, 1, 0, "AccountFactory"); | 81 | qmlRegisterType<AccountFactory>(uri, 1, 0, "AccountFactory"); |
82 | qmlRegisterType<AccountsModel>(uri, 1, 0, "AccountsModel"); | 82 | qmlRegisterType<AccountsModel>(uri, 1, 0, "AccountsModel"); |