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 |
91 | const QString rsrcPath = ":/images/mac" ; |
92 | #else |
93 | const QString rsrcPath = ":/images/win" ; |
94 | #endif |
95 | |
96 | TextEdit::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 * = 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 | |
164 | void TextEdit::closeEvent(QCloseEvent *e) |
165 | { |
166 | if (maybeSave()) |
167 | e->accept(); |
168 | else |
169 | e->ignore(); |
170 | } |
171 | |
172 | void TextEdit::setupFileActions() |
173 | { |
174 | QToolBar *tb = addToolBar(title: tr(s: "File Actions" )); |
175 | QMenu * = 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 | |
223 | void TextEdit::setupEditActions() |
224 | { |
225 | QToolBar *tb = addToolBar(title: tr(s: "Edit Actions" )); |
226 | QMenu * = 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 | |
263 | void TextEdit::setupTextActions() |
264 | { |
265 | QToolBar *tb = addToolBar(title: tr(s: "Format Actions" )); |
266 | QMenu * = 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 | |
411 | bool 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 | |
440 | bool 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 | |
457 | void 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 | |
472 | void TextEdit::fileNew() |
473 | { |
474 | if (maybeSave()) { |
475 | textEdit->clear(); |
476 | setCurrentFileName(QString()); |
477 | } |
478 | } |
479 | |
480 | void 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 | |
503 | bool 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 | |
522 | bool 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 | |
546 | void 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 | |
560 | void 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 | |
570 | void 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 | |
580 | void 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 | |
601 | void TextEdit::textBold() |
602 | { |
603 | QTextCharFormat fmt; |
604 | fmt.setFontWeight(actionTextBold->isChecked() ? QFont::Bold : QFont::Normal); |
605 | mergeFormatOnWordOrSelection(format: fmt); |
606 | } |
607 | |
608 | void TextEdit::textUnderline() |
609 | { |
610 | QTextCharFormat fmt; |
611 | fmt.setFontUnderline(actionTextUnderline->isChecked()); |
612 | mergeFormatOnWordOrSelection(format: fmt); |
613 | } |
614 | |
615 | void TextEdit::textItalic() |
616 | { |
617 | QTextCharFormat fmt; |
618 | fmt.setFontItalic(actionTextItalic->isChecked()); |
619 | mergeFormatOnWordOrSelection(format: fmt); |
620 | } |
621 | |
622 | void TextEdit::textFamily(const QString &f) |
623 | { |
624 | QTextCharFormat fmt; |
625 | fmt.setFontFamily(f); |
626 | mergeFormatOnWordOrSelection(format: fmt); |
627 | } |
628 | |
629 | void 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 | |
639 | void 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 | |
723 | void 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 | |
734 | void 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 | |
746 | void TextEdit::setChecked(bool checked) |
747 | { |
748 | textStyle(styleIndex: checked ? 5 : 4); |
749 | } |
750 | |
751 | void TextEdit::indent() |
752 | { |
753 | modifyIndentation(amount: 1); |
754 | } |
755 | |
756 | void TextEdit::unindent() |
757 | { |
758 | modifyIndentation(amount: -1); |
759 | } |
760 | |
761 | void 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 | |
785 | void TextEdit::currentCharFormatChanged(const QTextCharFormat &format) |
786 | { |
787 | fontChanged(f: format.font()); |
788 | colorChanged(c: format.foreground().color()); |
789 | } |
790 | |
791 | void 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 | |
844 | void 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 | |
852 | void 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 | |
859 | void 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 | |
868 | void 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 | |
877 | void TextEdit::colorChanged(const QColor &c) |
878 | { |
879 | QPixmap pix(16, 16); |
880 | pix.fill(fillColor: c); |
881 | actionTextColor->setIcon(pix); |
882 | } |
883 | |
884 | void 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 | |