1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "documenthandler.h"
52
53#include <QFile>
54#include <QFileInfo>
55#include <QFileSelector>
56#include <QMimeDatabase>
57#include <QQmlFile>
58#include <QQmlFileSelector>
59#include <QQuickTextDocument>
60#include <QTextCharFormat>
61#include <QTextCodec>
62#include <QTextDocument>
63#include <QDebug>
64
65DocumentHandler::DocumentHandler(QObject *parent)
66 : QObject(parent)
67 , m_document(nullptr)
68 , m_cursorPosition(-1)
69 , m_selectionStart(0)
70 , m_selectionEnd(0)
71{
72}
73
74QQuickTextDocument *DocumentHandler::document() const
75{
76 return m_document;
77}
78
79void DocumentHandler::setDocument(QQuickTextDocument *document)
80{
81 if (document == m_document)
82 return;
83
84 if (m_document)
85 m_document->textDocument()->disconnect(receiver: this);
86 m_document = document;
87 if (m_document)
88 connect(sender: m_document->textDocument(), signal: &QTextDocument::modificationChanged, receiver: this, slot: &DocumentHandler::modifiedChanged);
89 emit documentChanged();
90}
91
92int DocumentHandler::cursorPosition() const
93{
94 return m_cursorPosition;
95}
96
97void DocumentHandler::setCursorPosition(int position)
98{
99 if (position == m_cursorPosition)
100 return;
101
102 m_cursorPosition = position;
103 reset();
104 emit cursorPositionChanged();
105}
106
107int DocumentHandler::selectionStart() const
108{
109 return m_selectionStart;
110}
111
112void DocumentHandler::setSelectionStart(int position)
113{
114 if (position == m_selectionStart)
115 return;
116
117 m_selectionStart = position;
118 emit selectionStartChanged();
119}
120
121int DocumentHandler::selectionEnd() const
122{
123 return m_selectionEnd;
124}
125
126void DocumentHandler::setSelectionEnd(int position)
127{
128 if (position == m_selectionEnd)
129 return;
130
131 m_selectionEnd = position;
132 emit selectionEndChanged();
133}
134
135QString DocumentHandler::fontFamily() const
136{
137 QTextCursor cursor = textCursor();
138 if (cursor.isNull())
139 return QString();
140 QTextCharFormat format = cursor.charFormat();
141 return format.font().family();
142}
143
144void DocumentHandler::setFontFamily(const QString &family)
145{
146 QTextCharFormat format;
147 format.setFontFamily(family);
148 mergeFormatOnWordOrSelection(format);
149 emit fontFamilyChanged();
150}
151
152QColor DocumentHandler::textColor() const
153{
154 QTextCursor cursor = textCursor();
155 if (cursor.isNull())
156 return QColor(Qt::black);
157 QTextCharFormat format = cursor.charFormat();
158 return format.foreground().color();
159}
160
161void DocumentHandler::setTextColor(const QColor &color)
162{
163 QTextCharFormat format;
164 format.setForeground(QBrush(color));
165 mergeFormatOnWordOrSelection(format);
166 emit textColorChanged();
167}
168
169Qt::Alignment DocumentHandler::alignment() const
170{
171 QTextCursor cursor = textCursor();
172 if (cursor.isNull())
173 return Qt::AlignLeft;
174 return textCursor().blockFormat().alignment();
175}
176
177void DocumentHandler::setAlignment(Qt::Alignment alignment)
178{
179 QTextBlockFormat format;
180 format.setAlignment(alignment);
181 QTextCursor cursor = textCursor();
182 cursor.mergeBlockFormat(modifier: format);
183 emit alignmentChanged();
184}
185
186bool DocumentHandler::bold() const
187{
188 QTextCursor cursor = textCursor();
189 if (cursor.isNull())
190 return false;
191 return textCursor().charFormat().fontWeight() == QFont::Bold;
192}
193
194void DocumentHandler::setBold(bool bold)
195{
196 QTextCharFormat format;
197 format.setFontWeight(bold ? QFont::Bold : QFont::Normal);
198 mergeFormatOnWordOrSelection(format);
199 emit boldChanged();
200}
201
202bool DocumentHandler::italic() const
203{
204 QTextCursor cursor = textCursor();
205 if (cursor.isNull())
206 return false;
207 return textCursor().charFormat().fontItalic();
208}
209
210void DocumentHandler::setItalic(bool italic)
211{
212 QTextCharFormat format;
213 format.setFontItalic(italic);
214 mergeFormatOnWordOrSelection(format);
215 emit italicChanged();
216}
217
218bool DocumentHandler::underline() const
219{
220 QTextCursor cursor = textCursor();
221 if (cursor.isNull())
222 return false;
223 return textCursor().charFormat().fontUnderline();
224}
225
226void DocumentHandler::setUnderline(bool underline)
227{
228 QTextCharFormat format;
229 format.setFontUnderline(underline);
230 mergeFormatOnWordOrSelection(format);
231 emit underlineChanged();
232}
233
234int DocumentHandler::fontSize() const
235{
236 QTextCursor cursor = textCursor();
237 if (cursor.isNull())
238 return 0;
239 QTextCharFormat format = cursor.charFormat();
240 return format.font().pointSize();
241}
242
243void DocumentHandler::setFontSize(int size)
244{
245 if (size <= 0)
246 return;
247
248 QTextCursor cursor = textCursor();
249 if (cursor.isNull())
250 return;
251
252 if (!cursor.hasSelection())
253 cursor.select(selection: QTextCursor::WordUnderCursor);
254
255 if (cursor.charFormat().property(propertyId: QTextFormat::FontPointSize).toInt() == size)
256 return;
257
258 QTextCharFormat format;
259 format.setFontPointSize(size);
260 mergeFormatOnWordOrSelection(format);
261 emit fontSizeChanged();
262}
263
264QString DocumentHandler::fileName() const
265{
266 const QString filePath = QQmlFile::urlToLocalFileOrQrc(m_fileUrl);
267 const QString fileName = QFileInfo(filePath).fileName();
268 if (fileName.isEmpty())
269 return QStringLiteral("untitled.txt");
270 return fileName;
271}
272
273QString DocumentHandler::fileType() const
274{
275 return QFileInfo(fileName()).suffix();
276}
277
278QUrl DocumentHandler::fileUrl() const
279{
280 return m_fileUrl;
281}
282
283void DocumentHandler::load(const QUrl &fileUrl)
284{
285 if (fileUrl == m_fileUrl)
286 return;
287
288 QQmlEngine *engine = qmlEngine(this);
289 if (!engine) {
290 qWarning() << "load() called before DocumentHandler has QQmlEngine";
291 return;
292 }
293
294 const QUrl path = QQmlFileSelector::get(engine)->selector()->select(filePath: fileUrl);
295 const QString fileName = QQmlFile::urlToLocalFileOrQrc(path);
296 if (QFile::exists(fileName)) {
297 QMimeType mime = QMimeDatabase().mimeTypeForFile(fileName);
298 QFile file(fileName);
299 if (file.open(flags: QFile::ReadOnly)) {
300 QByteArray data = file.readAll();
301 if (QTextDocument *doc = textDocument()) {
302 doc->setBaseUrl(path.adjusted(options: QUrl::RemoveFilename));
303 if (mime.inherits(mimeTypeName: "text/markdown")) {
304 emit loaded(text: QString::fromUtf8(str: data), format: Qt::MarkdownText);
305 } else {
306 QTextCodec *codec = QTextCodec::codecForHtml(ba: data);
307 emit loaded(text: codec->toUnicode(data), format: Qt::AutoText);
308 }
309 doc->setModified(false);
310 }
311
312 reset();
313 }
314 }
315
316 m_fileUrl = fileUrl;
317 emit fileUrlChanged();
318}
319
320void DocumentHandler::saveAs(const QUrl &fileUrl)
321{
322 QTextDocument *doc = textDocument();
323 if (!doc)
324 return;
325
326 const QString filePath = fileUrl.toLocalFile();
327 const bool isHtml = QFileInfo(filePath).suffix().contains(s: QLatin1String("htm"));
328 QFile file(filePath);
329 if (!file.open(flags: QFile::WriteOnly | QFile::Truncate | (isHtml ? QFile::NotOpen : QFile::Text))) {
330 emit error(message: tr(s: "Cannot save: ") + file.errorString());
331 return;
332 }
333 file.write(data: (isHtml ? doc->toHtml() : doc->toPlainText()).toUtf8());
334 file.close();
335
336 if (fileUrl == m_fileUrl)
337 return;
338
339 m_fileUrl = fileUrl;
340 emit fileUrlChanged();
341}
342
343void DocumentHandler::reset()
344{
345 emit fontFamilyChanged();
346 emit alignmentChanged();
347 emit boldChanged();
348 emit italicChanged();
349 emit underlineChanged();
350 emit fontSizeChanged();
351 emit textColorChanged();
352}
353
354QTextCursor DocumentHandler::textCursor() const
355{
356 QTextDocument *doc = textDocument();
357 if (!doc)
358 return QTextCursor();
359
360 QTextCursor cursor = QTextCursor(doc);
361 if (m_selectionStart != m_selectionEnd) {
362 cursor.setPosition(pos: m_selectionStart);
363 cursor.setPosition(pos: m_selectionEnd, mode: QTextCursor::KeepAnchor);
364 } else {
365 cursor.setPosition(pos: m_cursorPosition);
366 }
367 return cursor;
368}
369
370QTextDocument *DocumentHandler::textDocument() const
371{
372 if (!m_document)
373 return nullptr;
374
375 return m_document->textDocument();
376}
377
378void DocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
379{
380 QTextCursor cursor = textCursor();
381 if (!cursor.hasSelection())
382 cursor.select(selection: QTextCursor::WordUnderCursor);
383 cursor.mergeCharFormat(modifier: format);
384}
385
386bool DocumentHandler::modified() const
387{
388 return m_document && m_document->textDocument()->isModified();
389}
390
391void DocumentHandler::setModified(bool m)
392{
393 if (m_document)
394 m_document->textDocument()->setModified(m);
395}
396

source code of qtquickcontrols2/examples/quickcontrols2/texteditor/documenthandler.cpp