1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the demonstration applications 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 <QAction>
52#include <QApplication>
53#include <QClipboard>
54#include <QColorDialog>
55#include <QComboBox>
56#include <QFontComboBox>
57#include <QFile>
58#include <QFileDialog>
59#include <QFileInfo>
60#include <QFontDatabase>
61#include <QMenu>
62#include <QMenuBar>
63#include <QTextCodec>
64#include <QTextEdit>
65#include <QStatusBar>
66#include <QToolBar>
67#include <QTextCursor>
68#include <QTextDocumentWriter>
69#include <QTextList>
70#include <QtDebug>
71#include <QCloseEvent>
72#include <QMessageBox>
73#include <QMimeData>
74#include <QMimeDatabase>
75#if defined(QT_PRINTSUPPORT_LIB)
76#include <QtPrintSupport/qtprintsupportglobal.h>
77#if QT_CONFIG(printer)
78#if QT_CONFIG(printdialog)
79#include <QPrintDialog>
80#endif
81#include <QPrinter>
82#if QT_CONFIG(printpreviewdialog)
83#include <QPrintPreviewDialog>
84#endif
85#endif
86#endif
87
88#include "textedit.h"
89
90#ifdef Q_OS_MAC
91const QString rsrcPath = ":/images/mac";
92#else
93const QString rsrcPath = ":/images/win";
94#endif
95
96TextEdit::TextEdit(QWidget *parent)
97 : QMainWindow(parent)
98{
99#ifdef Q_OS_MACOS
100 setUnifiedTitleAndToolBarOnMac(true);
101#endif
102 setWindowTitle(QCoreApplication::applicationName());
103
104 textEdit = new QTextEdit(this);
105 connect(sender: textEdit, signal: &QTextEdit::currentCharFormatChanged,
106 receiver: this, slot: &TextEdit::currentCharFormatChanged);
107 connect(sender: textEdit, signal: &QTextEdit::cursorPositionChanged,
108 receiver: this, slot: &TextEdit::cursorPositionChanged);
109 setCentralWidget(textEdit);
110
111 setToolButtonStyle(Qt::ToolButtonFollowStyle);
112 setupFileActions();
113 setupEditActions();
114 setupTextActions();
115
116 {
117 QMenu *helpMenu = menuBar()->addMenu(title: tr(s: "Help"));
118 helpMenu->addAction(text: tr(s: "About"), object: this, slot: &TextEdit::about);
119 helpMenu->addAction(text: tr(s: "About &Qt"), qApp, slot: &QApplication::aboutQt);
120 }
121
122 QFont textFont("Helvetica");
123 textFont.setStyleHint(QFont::SansSerif);
124 textEdit->setFont(textFont);
125 fontChanged(f: textEdit->font());
126 colorChanged(c: textEdit->textColor());
127 alignmentChanged(a: textEdit->alignment());
128
129 connect(sender: textEdit->document(), signal: &QTextDocument::modificationChanged,
130 receiver: actionSave, slot: &QAction::setEnabled);
131 connect(sender: textEdit->document(), signal: &QTextDocument::modificationChanged,
132 receiver: this, slot: &QWidget::setWindowModified);
133 connect(sender: textEdit->document(), signal: &QTextDocument::undoAvailable,
134 receiver: actionUndo, slot: &QAction::setEnabled);
135 connect(sender: textEdit->document(), signal: &QTextDocument::redoAvailable,
136 receiver: actionRedo, slot: &QAction::setEnabled);
137
138 setWindowModified(textEdit->document()->isModified());
139 actionSave->setEnabled(textEdit->document()->isModified());
140 actionUndo->setEnabled(textEdit->document()->isUndoAvailable());
141 actionRedo->setEnabled(textEdit->document()->isRedoAvailable());
142
143#ifndef QT_NO_CLIPBOARD
144 actionCut->setEnabled(false);
145 connect(sender: textEdit, signal: &QTextEdit::copyAvailable, receiver: actionCut, slot: &QAction::setEnabled);
146 actionCopy->setEnabled(false);
147 connect(sender: textEdit, signal: &QTextEdit::copyAvailable, receiver: actionCopy, slot: &QAction::setEnabled);
148
149 connect(sender: QApplication::clipboard(), signal: &QClipboard::dataChanged, receiver: this, slot: &TextEdit::clipboardDataChanged);
150#endif
151
152 textEdit->setFocus();
153 setCurrentFileName(QString());
154
155#ifdef Q_OS_MACOS
156 // Use dark text on light background on macOS, also in dark mode.
157 QPalette pal = textEdit->palette();
158 pal.setColor(QPalette::Base, QColor(Qt::white));
159 pal.setColor(QPalette::Text, QColor(Qt::black));
160 textEdit->setPalette(pal);
161#endif
162}
163
164void TextEdit::closeEvent(QCloseEvent *e)
165{
166 if (maybeSave())
167 e->accept();
168 else
169 e->ignore();
170}
171
172void TextEdit::setupFileActions()
173{
174 QToolBar *tb = addToolBar(title: tr(s: "File Actions"));
175 QMenu *menu = menuBar()->addMenu(title: tr(s: "&File"));
176
177 const QIcon newIcon = QIcon::fromTheme(name: "document-new", fallback: QIcon(rsrcPath + "/filenew.png"));
178 QAction *a = menu->addAction(actionIcon: newIcon, text: tr(s: "&New"), object: this, slot: &TextEdit::fileNew);
179 tb->addAction(action: a);
180 a->setPriority(QAction::LowPriority);
181 a->setShortcut(QKeySequence::New);
182
183 const QIcon openIcon = QIcon::fromTheme(name: "document-open", fallback: QIcon(rsrcPath + "/fileopen.png"));
184 a = menu->addAction(actionIcon: openIcon, text: tr(s: "&Open..."), object: this, slot: &TextEdit::fileOpen);
185 a->setShortcut(QKeySequence::Open);
186 tb->addAction(action: a);
187
188 menu->addSeparator();
189
190 const QIcon saveIcon = QIcon::fromTheme(name: "document-save", fallback: QIcon(rsrcPath + "/filesave.png"));
191 actionSave = menu->addAction(actionIcon: saveIcon, text: tr(s: "&Save"), object: this, slot: &TextEdit::fileSave);
192 actionSave->setShortcut(QKeySequence::Save);
193 actionSave->setEnabled(false);
194 tb->addAction(action: actionSave);
195
196 a = menu->addAction(text: tr(s: "Save &As..."), object: this, slot: &TextEdit::fileSaveAs);
197 a->setPriority(QAction::LowPriority);
198 menu->addSeparator();
199
200#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer)
201 const QIcon printIcon = QIcon::fromTheme(name: "document-print", fallback: QIcon(rsrcPath + "/fileprint.png"));
202 a = menu->addAction(actionIcon: printIcon, text: tr(s: "&Print..."), object: this, slot: &TextEdit::filePrint);
203 a->setPriority(QAction::LowPriority);
204 a->setShortcut(QKeySequence::Print);
205 tb->addAction(action: a);
206
207 const QIcon filePrintIcon = QIcon::fromTheme(name: "fileprint", fallback: QIcon(rsrcPath + "/fileprint.png"));
208 menu->addAction(actionIcon: filePrintIcon, text: tr(s: "Print Preview..."), object: this, slot: &TextEdit::filePrintPreview);
209
210 const QIcon exportPdfIcon = QIcon::fromTheme(name: "exportpdf", fallback: QIcon(rsrcPath + "/exportpdf.png"));
211 a = menu->addAction(actionIcon: exportPdfIcon, text: tr(s: "&Export PDF..."), object: this, slot: &TextEdit::filePrintPdf);
212 a->setPriority(QAction::LowPriority);
213 a->setShortcut(Qt::CTRL + Qt::Key_D);
214 tb->addAction(action: a);
215
216 menu->addSeparator();
217#endif
218
219 a = menu->addAction(text: tr(s: "&Quit"), object: this, slot: &QWidget::close);
220 a->setShortcut(Qt::CTRL + Qt::Key_Q);
221}
222
223void TextEdit::setupEditActions()
224{
225 QToolBar *tb = addToolBar(title: tr(s: "Edit Actions"));
226 QMenu *menu = menuBar()->addMenu(title: tr(s: "&Edit"));
227
228 const QIcon undoIcon = QIcon::fromTheme(name: "edit-undo", fallback: QIcon(rsrcPath + "/editundo.png"));
229 actionUndo = menu->addAction(actionIcon: undoIcon, text: tr(s: "&Undo"), object: textEdit, slot: &QTextEdit::undo);
230 actionUndo->setShortcut(QKeySequence::Undo);
231 tb->addAction(action: actionUndo);
232
233 const QIcon redoIcon = QIcon::fromTheme(name: "edit-redo", fallback: QIcon(rsrcPath + "/editredo.png"));
234 actionRedo = menu->addAction(actionIcon: redoIcon, text: tr(s: "&Redo"), object: textEdit, slot: &QTextEdit::redo);
235 actionRedo->setPriority(QAction::LowPriority);
236 actionRedo->setShortcut(QKeySequence::Redo);
237 tb->addAction(action: actionRedo);
238 menu->addSeparator();
239
240#ifndef QT_NO_CLIPBOARD
241 const QIcon cutIcon = QIcon::fromTheme(name: "edit-cut", fallback: QIcon(rsrcPath + "/editcut.png"));
242 actionCut = menu->addAction(actionIcon: cutIcon, text: tr(s: "Cu&t"), object: textEdit, slot: &QTextEdit::cut);
243 actionCut->setPriority(QAction::LowPriority);
244 actionCut->setShortcut(QKeySequence::Cut);
245 tb->addAction(action: actionCut);
246
247 const QIcon copyIcon = QIcon::fromTheme(name: "edit-copy", fallback: QIcon(rsrcPath + "/editcopy.png"));
248 actionCopy = menu->addAction(actionIcon: copyIcon, text: tr(s: "&Copy"), object: textEdit, slot: &QTextEdit::copy);
249 actionCopy->setPriority(QAction::LowPriority);
250 actionCopy->setShortcut(QKeySequence::Copy);
251 tb->addAction(action: actionCopy);
252
253 const QIcon pasteIcon = QIcon::fromTheme(name: "edit-paste", fallback: QIcon(rsrcPath + "/editpaste.png"));
254 actionPaste = menu->addAction(actionIcon: pasteIcon, text: tr(s: "&Paste"), object: textEdit, slot: &QTextEdit::paste);
255 actionPaste->setPriority(QAction::LowPriority);
256 actionPaste->setShortcut(QKeySequence::Paste);
257 tb->addAction(action: actionPaste);
258 if (const QMimeData *md = QApplication::clipboard()->mimeData())
259 actionPaste->setEnabled(md->hasText());
260#endif
261}
262
263void TextEdit::setupTextActions()
264{
265 QToolBar *tb = addToolBar(title: tr(s: "Format Actions"));
266 QMenu *menu = menuBar()->addMenu(title: tr(s: "F&ormat"));
267
268 const QIcon boldIcon = QIcon::fromTheme(name: "format-text-bold", fallback: QIcon(rsrcPath + "/textbold.png"));
269 actionTextBold = menu->addAction(actionIcon: boldIcon, text: tr(s: "&Bold"), object: this, slot: &TextEdit::textBold);
270 actionTextBold->setShortcut(Qt::CTRL + Qt::Key_B);
271 actionTextBold->setPriority(QAction::LowPriority);
272 QFont bold;
273 bold.setBold(true);
274 actionTextBold->setFont(bold);
275 tb->addAction(action: actionTextBold);
276 actionTextBold->setCheckable(true);
277
278 const QIcon italicIcon = QIcon::fromTheme(name: "format-text-italic", fallback: QIcon(rsrcPath + "/textitalic.png"));
279 actionTextItalic = menu->addAction(actionIcon: italicIcon, text: tr(s: "&Italic"), object: this, slot: &TextEdit::textItalic);
280 actionTextItalic->setPriority(QAction::LowPriority);
281 actionTextItalic->setShortcut(Qt::CTRL + Qt::Key_I);
282 QFont italic;
283 italic.setItalic(true);
284 actionTextItalic->setFont(italic);
285 tb->addAction(action: actionTextItalic);
286 actionTextItalic->setCheckable(true);
287
288 const QIcon underlineIcon = QIcon::fromTheme(name: "format-text-underline", fallback: QIcon(rsrcPath + "/textunder.png"));
289 actionTextUnderline = menu->addAction(actionIcon: underlineIcon, text: tr(s: "&Underline"), object: this, slot: &TextEdit::textUnderline);
290 actionTextUnderline->setShortcut(Qt::CTRL + Qt::Key_U);
291 actionTextUnderline->setPriority(QAction::LowPriority);
292 QFont underline;
293 underline.setUnderline(true);
294 actionTextUnderline->setFont(underline);
295 tb->addAction(action: actionTextUnderline);
296 actionTextUnderline->setCheckable(true);
297
298 menu->addSeparator();
299
300 const QIcon leftIcon = QIcon::fromTheme(name: "format-justify-left", fallback: QIcon(rsrcPath + "/textleft.png"));
301 actionAlignLeft = new QAction(leftIcon, tr(s: "&Left"), this);
302 actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L);
303 actionAlignLeft->setCheckable(true);
304 actionAlignLeft->setPriority(QAction::LowPriority);
305 const QIcon centerIcon = QIcon::fromTheme(name: "format-justify-center", fallback: QIcon(rsrcPath + "/textcenter.png"));
306 actionAlignCenter = new QAction(centerIcon, tr(s: "C&enter"), this);
307 actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E);
308 actionAlignCenter->setCheckable(true);
309 actionAlignCenter->setPriority(QAction::LowPriority);
310 const QIcon rightIcon = QIcon::fromTheme(name: "format-justify-right", fallback: QIcon(rsrcPath + "/textright.png"));
311 actionAlignRight = new QAction(rightIcon, tr(s: "&Right"), this);
312 actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R);
313 actionAlignRight->setCheckable(true);
314 actionAlignRight->setPriority(QAction::LowPriority);
315 const QIcon fillIcon = QIcon::fromTheme(name: "format-justify-fill", fallback: QIcon(rsrcPath + "/textjustify.png"));
316 actionAlignJustify = new QAction(fillIcon, tr(s: "&Justify"), this);
317 actionAlignJustify->setShortcut(Qt::CTRL + Qt::Key_J);
318 actionAlignJustify->setCheckable(true);
319 actionAlignJustify->setPriority(QAction::LowPriority);
320 const QIcon indentMoreIcon = QIcon::fromTheme(name: "format-indent-more", fallback: QIcon(rsrcPath + "/format-indent-more.png"));
321 actionIndentMore = menu->addAction(actionIcon: indentMoreIcon, text: tr(s: "&Indent"), object: this, slot: &TextEdit::indent);
322 actionIndentMore->setShortcut(Qt::CTRL + Qt::Key_BracketRight);
323 actionIndentMore->setPriority(QAction::LowPriority);
324 const QIcon indentLessIcon = QIcon::fromTheme(name: "format-indent-less", fallback: QIcon(rsrcPath + "/format-indent-less.png"));
325 actionIndentLess = menu->addAction(actionIcon: indentLessIcon, text: tr(s: "&Unindent"), object: this, slot: &TextEdit::unindent);
326 actionIndentLess->setShortcut(Qt::CTRL + Qt::Key_BracketLeft);
327 actionIndentLess->setPriority(QAction::LowPriority);
328
329 // Make sure the alignLeft is always left of the alignRight
330 QActionGroup *alignGroup = new QActionGroup(this);
331 connect(sender: alignGroup, signal: &QActionGroup::triggered, receiver: this, slot: &TextEdit::textAlign);
332
333 if (QApplication::isLeftToRight()) {
334 alignGroup->addAction(a: actionAlignLeft);
335 alignGroup->addAction(a: actionAlignCenter);
336 alignGroup->addAction(a: actionAlignRight);
337 } else {
338 alignGroup->addAction(a: actionAlignRight);
339 alignGroup->addAction(a: actionAlignCenter);
340 alignGroup->addAction(a: actionAlignLeft);
341 }
342 alignGroup->addAction(a: actionAlignJustify);
343
344 tb->addActions(actions: alignGroup->actions());
345 menu->addActions(actions: alignGroup->actions());
346 tb->addAction(action: actionIndentMore);
347 tb->addAction(action: actionIndentLess);
348 menu->addAction(action: actionIndentMore);
349 menu->addAction(action: actionIndentLess);
350
351 menu->addSeparator();
352
353 QPixmap pix(16, 16);
354 pix.fill(fillColor: Qt::black);
355 actionTextColor = menu->addAction(actionIcon: pix, text: tr(s: "&Color..."), object: this, slot: &TextEdit::textColor);
356 tb->addAction(action: actionTextColor);
357
358 menu->addSeparator();
359
360 const QIcon checkboxIcon = QIcon::fromTheme(name: "status-checkbox-checked", fallback: QIcon(rsrcPath + "/checkbox-checked.png"));
361 actionToggleCheckState = menu->addAction(actionIcon: checkboxIcon, text: tr(s: "Chec&ked"), object: this, slot: &TextEdit::setChecked);
362 actionToggleCheckState->setShortcut(Qt::CTRL + Qt::Key_K);
363 actionToggleCheckState->setCheckable(true);
364 actionToggleCheckState->setPriority(QAction::LowPriority);
365 tb->addAction(action: actionToggleCheckState);
366
367 tb = addToolBar(title: tr(s: "Format Actions"));
368 tb->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
369 addToolBarBreak(area: Qt::TopToolBarArea);
370 addToolBar(toolbar: tb);
371
372 comboStyle = new QComboBox(tb);
373 tb->addWidget(widget: comboStyle);
374 comboStyle->addItem(atext: "Standard");
375 comboStyle->addItem(atext: "Bullet List (Disc)");
376 comboStyle->addItem(atext: "Bullet List (Circle)");
377 comboStyle->addItem(atext: "Bullet List (Square)");
378 comboStyle->addItem(atext: "Task List (Unchecked)");
379 comboStyle->addItem(atext: "Task List (Checked)");
380 comboStyle->addItem(atext: "Ordered List (Decimal)");
381 comboStyle->addItem(atext: "Ordered List (Alpha lower)");
382 comboStyle->addItem(atext: "Ordered List (Alpha upper)");
383 comboStyle->addItem(atext: "Ordered List (Roman lower)");
384 comboStyle->addItem(atext: "Ordered List (Roman upper)");
385 comboStyle->addItem(atext: "Heading 1");
386 comboStyle->addItem(atext: "Heading 2");
387 comboStyle->addItem(atext: "Heading 3");
388 comboStyle->addItem(atext: "Heading 4");
389 comboStyle->addItem(atext: "Heading 5");
390 comboStyle->addItem(atext: "Heading 6");
391
392 connect(sender: comboStyle, signal: QOverload<int>::of(ptr: &QComboBox::activated), receiver: this, slot: &TextEdit::textStyle);
393
394 comboFont = new QFontComboBox(tb);
395 tb->addWidget(widget: comboFont);
396 connect(sender: comboFont, signal: &QComboBox::textActivated, receiver: this, slot: &TextEdit::textFamily);
397
398 comboSize = new QComboBox(tb);
399 comboSize->setObjectName("comboSize");
400 tb->addWidget(widget: comboSize);
401 comboSize->setEditable(true);
402
403 const QList<int> standardSizes = QFontDatabase::standardSizes();
404 for (int size : standardSizes)
405 comboSize->addItem(atext: QString::number(size));
406 comboSize->setCurrentIndex(standardSizes.indexOf(t: QApplication::font().pointSize()));
407
408 connect(sender: comboSize, signal: &QComboBox::textActivated, receiver: this, slot: &TextEdit::textSize);
409}
410
411bool TextEdit::load(const QString &f)
412{
413 if (!QFile::exists(fileName: f))
414 return false;
415 QFile file(f);
416 if (!file.open(flags: QFile::ReadOnly))
417 return false;
418
419 QByteArray data = file.readAll();
420 QTextCodec *codec = Qt::codecForHtml(ba: data);
421 QString str = codec->toUnicode(data);
422 if (Qt::mightBeRichText(str)) {
423 QUrl baseUrl = (f.front() == QLatin1Char(':') ? QUrl(f) : QUrl::fromLocalFile(localfile: f)).adjusted(options: QUrl::RemoveFilename);
424 textEdit->document()->setBaseUrl(baseUrl);
425 textEdit->setHtml(str);
426 } else {
427#if QT_CONFIG(textmarkdownreader)
428 QMimeDatabase db;
429 if (db.mimeTypeForFileNameAndData(fileName: f, data).name() == QLatin1String("text/markdown"))
430 textEdit->setMarkdown(QString::fromUtf8(str: data));
431 else
432#endif
433 textEdit->setPlainText(QString::fromUtf8(str: data));
434 }
435
436 setCurrentFileName(f);
437 return true;
438}
439
440bool TextEdit::maybeSave()
441{
442 if (!textEdit->document()->isModified())
443 return true;
444
445 const QMessageBox::StandardButton ret =
446 QMessageBox::warning(parent: this, title: QCoreApplication::applicationName(),
447 text: tr(s: "The document has been modified.\n"
448 "Do you want to save your changes?"),
449 buttons: QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
450 if (ret == QMessageBox::Save)
451 return fileSave();
452 else if (ret == QMessageBox::Cancel)
453 return false;
454 return true;
455}
456
457void TextEdit::setCurrentFileName(const QString &fileName)
458{
459 this->fileName = fileName;
460 textEdit->document()->setModified(false);
461
462 QString shownName;
463 if (fileName.isEmpty())
464 shownName = "untitled.txt";
465 else
466 shownName = QFileInfo(fileName).fileName();
467
468 setWindowTitle(tr(s: "%1[*] - %2").arg(args&: shownName, args: QCoreApplication::applicationName()));
469 setWindowModified(false);
470}
471
472void TextEdit::fileNew()
473{
474 if (maybeSave()) {
475 textEdit->clear();
476 setCurrentFileName(QString());
477 }
478}
479
480void TextEdit::fileOpen()
481{
482 QFileDialog fileDialog(this, tr(s: "Open File..."));
483 fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
484 fileDialog.setFileMode(QFileDialog::ExistingFile);
485 fileDialog.setMimeTypeFilters(QStringList()
486#if QT_CONFIG(texthtmlparser)
487 << "text/html"
488#endif
489#if QT_CONFIG(textmarkdownreader)
490
491 << "text/markdown"
492#endif
493 << "text/plain");
494 if (fileDialog.exec() != QDialog::Accepted)
495 return;
496 const QString fn = fileDialog.selectedFiles().first();
497 if (load(f: fn))
498 statusBar()->showMessage(text: tr(s: "Opened \"%1\"").arg(a: QDir::toNativeSeparators(pathName: fn)));
499 else
500 statusBar()->showMessage(text: tr(s: "Could not open \"%1\"").arg(a: QDir::toNativeSeparators(pathName: fn)));
501}
502
503bool TextEdit::fileSave()
504{
505 if (fileName.isEmpty())
506 return fileSaveAs();
507 if (fileName.startsWith(QStringLiteral(":/")))
508 return fileSaveAs();
509
510 QTextDocumentWriter writer(fileName);
511 bool success = writer.write(document: textEdit->document());
512 if (success) {
513 textEdit->document()->setModified(false);
514 statusBar()->showMessage(text: tr(s: "Wrote \"%1\"").arg(a: QDir::toNativeSeparators(pathName: fileName)));
515 } else {
516 statusBar()->showMessage(text: tr(s: "Could not write to file \"%1\"")
517 .arg(a: QDir::toNativeSeparators(pathName: fileName)));
518 }
519 return success;
520}
521
522bool TextEdit::fileSaveAs()
523{
524 QFileDialog fileDialog(this, tr(s: "Save as..."));
525 fileDialog.setAcceptMode(QFileDialog::AcceptSave);
526 QStringList mimeTypes;
527 mimeTypes << "text/plain"
528#if QT_CONFIG(textodfwriter)
529 << "application/vnd.oasis.opendocument.text"
530#endif
531#if QT_CONFIG(textmarkdownwriter)
532 << "text/markdown"
533#endif
534 << "text/html";
535 fileDialog.setMimeTypeFilters(mimeTypes);
536#if QT_CONFIG(textodfwriter)
537 fileDialog.setDefaultSuffix("odt");
538#endif
539 if (fileDialog.exec() != QDialog::Accepted)
540 return false;
541 const QString fn = fileDialog.selectedFiles().first();
542 setCurrentFileName(fn);
543 return fileSave();
544}
545
546void TextEdit::filePrint()
547{
548#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog)
549 QPrinter printer(QPrinter::HighResolution);
550 QPrintDialog *dlg = new QPrintDialog(&printer, this);
551 if (textEdit->textCursor().hasSelection())
552 dlg->addEnabledOption(option: QAbstractPrintDialog::PrintSelection);
553 dlg->setWindowTitle(tr(s: "Print Document"));
554 if (dlg->exec() == QDialog::Accepted)
555 textEdit->print(printer: &printer);
556 delete dlg;
557#endif
558}
559
560void TextEdit::filePrintPreview()
561{
562#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printpreviewdialog)
563 QPrinter printer(QPrinter::HighResolution);
564 QPrintPreviewDialog preview(&printer, this);
565 connect(sender: &preview, signal: &QPrintPreviewDialog::paintRequested, receiver: this, slot: &TextEdit::printPreview);
566 preview.exec();
567#endif
568}
569
570void TextEdit::printPreview(QPrinter *printer)
571{
572#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer)
573 textEdit->print(printer);
574#else
575 Q_UNUSED(printer)
576#endif
577}
578
579
580void TextEdit::filePrintPdf()
581{
582#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer)
583//! [0]
584 QFileDialog fileDialog(this, tr(s: "Export PDF"));
585 fileDialog.setAcceptMode(QFileDialog::AcceptSave);
586 fileDialog.setMimeTypeFilters(QStringList("application/pdf"));
587 fileDialog.setDefaultSuffix("pdf");
588 if (fileDialog.exec() != QDialog::Accepted)
589 return;
590 QString fileName = fileDialog.selectedFiles().first();
591 QPrinter printer(QPrinter::HighResolution);
592 printer.setOutputFormat(QPrinter::PdfFormat);
593 printer.setOutputFileName(fileName);
594 textEdit->document()->print(printer: &printer);
595 statusBar()->showMessage(text: tr(s: "Exported \"%1\"")
596 .arg(a: QDir::toNativeSeparators(pathName: fileName)));
597//! [0]
598#endif
599}
600
601void TextEdit::textBold()
602{
603 QTextCharFormat fmt;
604 fmt.setFontWeight(actionTextBold->isChecked() ? QFont::Bold : QFont::Normal);
605 mergeFormatOnWordOrSelection(format: fmt);
606}
607
608void TextEdit::textUnderline()
609{
610 QTextCharFormat fmt;
611 fmt.setFontUnderline(actionTextUnderline->isChecked());
612 mergeFormatOnWordOrSelection(format: fmt);
613}
614
615void TextEdit::textItalic()
616{
617 QTextCharFormat fmt;
618 fmt.setFontItalic(actionTextItalic->isChecked());
619 mergeFormatOnWordOrSelection(format: fmt);
620}
621
622void TextEdit::textFamily(const QString &f)
623{
624 QTextCharFormat fmt;
625 fmt.setFontFamily(f);
626 mergeFormatOnWordOrSelection(format: fmt);
627}
628
629void TextEdit::textSize(const QString &p)
630{
631 qreal pointSize = p.toFloat();
632 if (p.toFloat() > 0) {
633 QTextCharFormat fmt;
634 fmt.setFontPointSize(pointSize);
635 mergeFormatOnWordOrSelection(format: fmt);
636 }
637}
638
639void TextEdit::textStyle(int styleIndex)
640{
641 QTextCursor cursor = textEdit->textCursor();
642 QTextListFormat::Style style = QTextListFormat::ListStyleUndefined;
643 QTextBlockFormat::MarkerType marker = QTextBlockFormat::MarkerType::NoMarker;
644
645 switch (styleIndex) {
646 case 1:
647 style = QTextListFormat::ListDisc;
648 break;
649 case 2:
650 style = QTextListFormat::ListCircle;
651 break;
652 case 3:
653 style = QTextListFormat::ListSquare;
654 break;
655 case 4:
656 if (cursor.currentList())
657 style = cursor.currentList()->format().style();
658 else
659 style = QTextListFormat::ListDisc;
660 marker = QTextBlockFormat::MarkerType::Unchecked;
661 break;
662 case 5:
663 if (cursor.currentList())
664 style = cursor.currentList()->format().style();
665 else
666 style = QTextListFormat::ListDisc;
667 marker = QTextBlockFormat::MarkerType::Checked;
668 break;
669 case 6:
670 style = QTextListFormat::ListDecimal;
671 break;
672 case 7:
673 style = QTextListFormat::ListLowerAlpha;
674 break;
675 case 8:
676 style = QTextListFormat::ListUpperAlpha;
677 break;
678 case 9:
679 style = QTextListFormat::ListLowerRoman;
680 break;
681 case 10:
682 style = QTextListFormat::ListUpperRoman;
683 break;
684 default:
685 break;
686 }
687
688 cursor.beginEditBlock();
689
690 QTextBlockFormat blockFmt = cursor.blockFormat();
691
692 if (style == QTextListFormat::ListStyleUndefined) {
693 blockFmt.setObjectIndex(-1);
694 int headingLevel = styleIndex >= 11 ? styleIndex - 11 + 1 : 0; // H1 to H6, or Standard
695 blockFmt.setHeadingLevel(headingLevel);
696 cursor.setBlockFormat(blockFmt);
697
698 int sizeAdjustment = headingLevel ? 4 - headingLevel : 0; // H1 to H6: +3 to -2
699 QTextCharFormat fmt;
700 fmt.setFontWeight(headingLevel ? QFont::Bold : QFont::Normal);
701 fmt.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: sizeAdjustment);
702 cursor.select(selection: QTextCursor::LineUnderCursor);
703 cursor.mergeCharFormat(modifier: fmt);
704 textEdit->mergeCurrentCharFormat(modifier: fmt);
705 } else {
706 blockFmt.setMarker(marker);
707 cursor.setBlockFormat(blockFmt);
708 QTextListFormat listFmt;
709 if (cursor.currentList()) {
710 listFmt = cursor.currentList()->format();
711 } else {
712 listFmt.setIndent(blockFmt.indent() + 1);
713 blockFmt.setIndent(0);
714 cursor.setBlockFormat(blockFmt);
715 }
716 listFmt.setStyle(style);
717 cursor.createList(format: listFmt);
718 }
719
720 cursor.endEditBlock();
721}
722
723void TextEdit::textColor()
724{
725 QColor col = QColorDialog::getColor(initial: textEdit->textColor(), parent: this);
726 if (!col.isValid())
727 return;
728 QTextCharFormat fmt;
729 fmt.setForeground(col);
730 mergeFormatOnWordOrSelection(format: fmt);
731 colorChanged(c: col);
732}
733
734void TextEdit::textAlign(QAction *a)
735{
736 if (a == actionAlignLeft)
737 textEdit->setAlignment(Qt::AlignLeft | Qt::AlignAbsolute);
738 else if (a == actionAlignCenter)
739 textEdit->setAlignment(Qt::AlignHCenter);
740 else if (a == actionAlignRight)
741 textEdit->setAlignment(Qt::AlignRight | Qt::AlignAbsolute);
742 else if (a == actionAlignJustify)
743 textEdit->setAlignment(Qt::AlignJustify);
744}
745
746void TextEdit::setChecked(bool checked)
747{
748 textStyle(styleIndex: checked ? 5 : 4);
749}
750
751void TextEdit::indent()
752{
753 modifyIndentation(amount: 1);
754}
755
756void TextEdit::unindent()
757{
758 modifyIndentation(amount: -1);
759}
760
761void TextEdit::modifyIndentation(int amount)
762{
763 QTextCursor cursor = textEdit->textCursor();
764 cursor.beginEditBlock();
765 if (cursor.currentList()) {
766 QTextListFormat listFmt = cursor.currentList()->format();
767 // See whether the line above is the list we want to move this item into,
768 // or whether we need a new list.
769 QTextCursor above(cursor);
770 above.movePosition(op: QTextCursor::Up);
771 if (above.currentList() && listFmt.indent() + amount == above.currentList()->format().indent()) {
772 above.currentList()->add(block: cursor.block());
773 } else {
774 listFmt.setIndent(listFmt.indent() + amount);
775 cursor.createList(format: listFmt);
776 }
777 } else {
778 QTextBlockFormat blockFmt = cursor.blockFormat();
779 blockFmt.setIndent(blockFmt.indent() + amount);
780 cursor.setBlockFormat(blockFmt);
781 }
782 cursor.endEditBlock();
783}
784
785void TextEdit::currentCharFormatChanged(const QTextCharFormat &format)
786{
787 fontChanged(f: format.font());
788 colorChanged(c: format.foreground().color());
789}
790
791void TextEdit::cursorPositionChanged()
792{
793 alignmentChanged(a: textEdit->alignment());
794 QTextList *list = textEdit->textCursor().currentList();
795 if (list) {
796 switch (list->format().style()) {
797 case QTextListFormat::ListDisc:
798 comboStyle->setCurrentIndex(1);
799 break;
800 case QTextListFormat::ListCircle:
801 comboStyle->setCurrentIndex(2);
802 break;
803 case QTextListFormat::ListSquare:
804 comboStyle->setCurrentIndex(3);
805 break;
806 case QTextListFormat::ListDecimal:
807 comboStyle->setCurrentIndex(6);
808 break;
809 case QTextListFormat::ListLowerAlpha:
810 comboStyle->setCurrentIndex(7);
811 break;
812 case QTextListFormat::ListUpperAlpha:
813 comboStyle->setCurrentIndex(8);
814 break;
815 case QTextListFormat::ListLowerRoman:
816 comboStyle->setCurrentIndex(9);
817 break;
818 case QTextListFormat::ListUpperRoman:
819 comboStyle->setCurrentIndex(10);
820 break;
821 default:
822 comboStyle->setCurrentIndex(-1);
823 break;
824 }
825 switch (textEdit->textCursor().block().blockFormat().marker()) {
826 case QTextBlockFormat::MarkerType::NoMarker:
827 actionToggleCheckState->setChecked(false);
828 break;
829 case QTextBlockFormat::MarkerType::Unchecked:
830 comboStyle->setCurrentIndex(4);
831 actionToggleCheckState->setChecked(false);
832 break;
833 case QTextBlockFormat::MarkerType::Checked:
834 comboStyle->setCurrentIndex(5);
835 actionToggleCheckState->setChecked(true);
836 break;
837 }
838 } else {
839 int headingLevel = textEdit->textCursor().blockFormat().headingLevel();
840 comboStyle->setCurrentIndex(headingLevel ? headingLevel + 10 : 0);
841 }
842}
843
844void TextEdit::clipboardDataChanged()
845{
846#ifndef QT_NO_CLIPBOARD
847 if (const QMimeData *md = QApplication::clipboard()->mimeData())
848 actionPaste->setEnabled(md->hasText());
849#endif
850}
851
852void TextEdit::about()
853{
854 QMessageBox::about(parent: this, title: tr(s: "About"), text: tr(s: "This example demonstrates Qt's "
855 "rich text editing facilities in action, providing an example "
856 "document for you to experiment with."));
857}
858
859void TextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
860{
861 QTextCursor cursor = textEdit->textCursor();
862 if (!cursor.hasSelection())
863 cursor.select(selection: QTextCursor::WordUnderCursor);
864 cursor.mergeCharFormat(modifier: format);
865 textEdit->mergeCurrentCharFormat(modifier: format);
866}
867
868void TextEdit::fontChanged(const QFont &f)
869{
870 comboFont->setCurrentIndex(comboFont->findText(text: QFontInfo(f).family()));
871 comboSize->setCurrentIndex(comboSize->findText(text: QString::number(f.pointSize())));
872 actionTextBold->setChecked(f.bold());
873 actionTextItalic->setChecked(f.italic());
874 actionTextUnderline->setChecked(f.underline());
875}
876
877void TextEdit::colorChanged(const QColor &c)
878{
879 QPixmap pix(16, 16);
880 pix.fill(fillColor: c);
881 actionTextColor->setIcon(pix);
882}
883
884void TextEdit::alignmentChanged(Qt::Alignment a)
885{
886 if (a & Qt::AlignLeft)
887 actionAlignLeft->setChecked(true);
888 else if (a & Qt::AlignHCenter)
889 actionAlignCenter->setChecked(true);
890 else if (a & Qt::AlignRight)
891 actionAlignRight->setChecked(true);
892 else if (a & Qt::AlignJustify)
893 actionAlignJustify->setChecked(true);
894}
895
896

source code of qtbase/examples/widgets/richtext/textedit/textedit.cpp