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 |
90 | const QString rsrcPath = ":/images/mac" ; |
91 | #else |
92 | const QString rsrcPath = ":/images/win" ; |
93 | #endif |
94 | |
95 | TextEdit::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 * = 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 | |
163 | void TextEdit::closeEvent(QCloseEvent *e) |
164 | { |
165 | if (maybeSave()) |
166 | e->accept(); |
167 | else |
168 | e->ignore(); |
169 | } |
170 | |
171 | void TextEdit::setupFileActions() |
172 | { |
173 | QToolBar *tb = addToolBar(tr("File Actions" )); |
174 | QMenu * = 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 | |
222 | void TextEdit::setupEditActions() |
223 | { |
224 | QToolBar *tb = addToolBar(tr("Edit Actions" )); |
225 | QMenu * = 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 | |
262 | void TextEdit::setupTextActions() |
263 | { |
264 | QToolBar *tb = addToolBar(tr("Format Actions" )); |
265 | QMenu * = 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 | |
410 | bool 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 | |
438 | bool 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 | |
455 | void 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 | |
470 | void TextEdit::fileNew() |
471 | { |
472 | if (maybeSave()) { |
473 | textEdit->clear(); |
474 | setCurrentFileName(QString()); |
475 | } |
476 | } |
477 | |
478 | void 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 | |
501 | bool 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 | |
520 | bool 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 | |
544 | void 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 | |
558 | void 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 | |
568 | void 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 | |
578 | void 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 | |
599 | void TextEdit::textBold() |
600 | { |
601 | QTextCharFormat fmt; |
602 | fmt.setFontWeight(actionTextBold->isChecked() ? QFont::Bold : QFont::Normal); |
603 | mergeFormatOnWordOrSelection(fmt); |
604 | } |
605 | |
606 | void TextEdit::textUnderline() |
607 | { |
608 | QTextCharFormat fmt; |
609 | fmt.setFontUnderline(actionTextUnderline->isChecked()); |
610 | mergeFormatOnWordOrSelection(fmt); |
611 | } |
612 | |
613 | void TextEdit::textItalic() |
614 | { |
615 | QTextCharFormat fmt; |
616 | fmt.setFontItalic(actionTextItalic->isChecked()); |
617 | mergeFormatOnWordOrSelection(fmt); |
618 | } |
619 | |
620 | void TextEdit::textFamily(const QString &f) |
621 | { |
622 | QTextCharFormat fmt; |
623 | fmt.setFontFamily(f); |
624 | mergeFormatOnWordOrSelection(fmt); |
625 | } |
626 | |
627 | void 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 | |
637 | void 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 | |
721 | void 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 | |
732 | void 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 | |
744 | void TextEdit::setChecked(bool checked) |
745 | { |
746 | textStyle(checked ? 5 : 4); |
747 | } |
748 | |
749 | void TextEdit::indent() |
750 | { |
751 | modifyIndentation(1); |
752 | } |
753 | |
754 | void TextEdit::unindent() |
755 | { |
756 | modifyIndentation(-1); |
757 | } |
758 | |
759 | void 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 | |
783 | void TextEdit::currentCharFormatChanged(const QTextCharFormat &format) |
784 | { |
785 | fontChanged(format.font()); |
786 | colorChanged(format.foreground().color()); |
787 | } |
788 | |
789 | void 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 | |
842 | void 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 | |
850 | void 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 | |
857 | void 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 | |
866 | void 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 | |
875 | void TextEdit::colorChanged(const QColor &c) |
876 | { |
877 | QPixmap pix(16, 16); |
878 | pix.fill(c); |
879 | actionTextColor->setIcon(pix); |
880 | } |
881 | |
882 | void 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 | |