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