| 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 | |