/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the demonstration applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_PRINTER #include #include #include #endif #include "textedit.h" #ifdef Q_OS_MAC const QString rsrcPath = ":/images/mac"; #else const QString rsrcPath = ":/images/win"; #endif TextEdit::TextEdit(QWidget *parent) : QMainWindow(parent) { #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif setWindowTitle(QCoreApplication::applicationName()); textEdit = new QTextEdit(this); connect(textEdit, &QTextEdit::currentCharFormatChanged, this, &TextEdit::currentCharFormatChanged); connect(textEdit, &QTextEdit::cursorPositionChanged, this, &TextEdit::cursorPositionChanged); setCentralWidget(textEdit); setToolButtonStyle(Qt::ToolButtonFollowStyle); setupFileActions(); setupEditActions(); setupTextActions(); { QMenu *helpMenu = menuBar()->addMenu(tr("Help")); helpMenu->addAction(tr("About"), this, &TextEdit::about); helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); } QFont textFont("Helvetica"); textFont.setStyleHint(QFont::SansSerif); textEdit->setFont(textFont); fontChanged(textEdit->font()); colorChanged(textEdit->textColor()); alignmentChanged(textEdit->alignment()); connect(textEdit->document(), &QTextDocument::modificationChanged, actionSave, &QAction::setEnabled); connect(textEdit->document(), &QTextDocument::modificationChanged, this, &QWidget::setWindowModified); connect(textEdit->document(), &QTextDocument::undoAvailable, actionUndo, &QAction::setEnabled); connect(textEdit->document(), &QTextDocument::redoAvailable, actionRedo, &QAction::setEnabled); setWindowModified(textEdit->document()->isModified()); actionSave->setEnabled(textEdit->document()->isModified()); actionUndo->setEnabled(textEdit->document()->isUndoAvailable()); actionRedo->setEnabled(textEdit->document()->isRedoAvailable()); #ifndef QT_NO_CLIPBOARD actionCut->setEnabled(false); actionCopy->setEnabled(false); connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &TextEdit::clipboardDataChanged); #endif textEdit->setFocus(); setCurrentFileName(QString()); } void TextEdit::closeEvent(QCloseEvent *e) { if (maybeSave()) e->accept(); else e->ignore(); } void TextEdit::setupFileActions() { QToolBar *tb = addToolBar(tr("File Actions")); QMenu *menu = menuBar()->addMenu(tr("&File")); const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(rsrcPath + "/filenew.png")); QAction *a = menu->addAction(newIcon, tr("&New"), this, &TextEdit::fileNew); tb->addAction(a); a->setPriority(QAction::LowPriority); a->setShortcut(QKeySequence::New); const QIcon openIcon = QIcon::fromTheme("document-open", QIcon(rsrcPath + "/fileopen.png")); a = menu->addAction(openIcon, tr("&Open..."), this, &TextEdit::fileOpen); a->setShortcut(QKeySequence::Open); tb->addAction(a); menu->addSeparator(); const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(rsrcPath + "/filesave.png")); actionSave = menu->addAction(saveIcon, tr("&Save"), this, &TextEdit::fileSave); actionSave->setShortcut(QKeySequence::Save); actionSave->setEnabled(false); tb->addAction(actionSave); a = menu->addAction(tr("Save &As..."), this, &TextEdit::fileSaveAs); a->setPriority(QAction::LowPriority); menu->addSeparator(); #ifndef QT_NO_PRINTER const QIcon printIcon = QIcon::fromTheme("document-print", QIcon(rsrcPath + "/fileprint.png")); a = menu->addAction(printIcon, tr("&Print..."), this, &TextEdit::filePrint); a->setPriority(QAction::LowPriority); a->setShortcut(QKeySequence::Print); tb->addAction(a); const QIcon filePrintIcon = QIcon::fromTheme("fileprint", QIcon(rsrcPath + "/fileprint.png")); menu->addAction(filePrintIcon, tr("Print Preview..."), this, &TextEdit::filePrintPreview); const QIcon exportPdfIcon = QIcon::fromTheme("exportpdf", QIcon(rsrcPath + "/exportpdf.png")); a = menu->addAction(exportPdfIcon, tr("&Export PDF..."), this, &TextEdit::filePrintPdf); a->setPriority(QAction::LowPriority); a->setShortcut(Qt::CTRL + Qt::Key_D); tb->addAction(a); menu->addSeparator(); #endif a = menu->addAction(tr("&Quit"), this, &QWidget::close); a->setShortcut(Qt::CTRL + Qt::Key_Q); } void TextEdit::setupEditActions() { QToolBar *tb = addToolBar(tr("Edit Actions")); QMenu *menu = menuBar()->addMenu(tr("&Edit")); const QIcon undoIcon = QIcon::fromTheme("edit-undo", QIcon(rsrcPath + "/editundo.png")); actionUndo = menu->addAction(undoIcon, tr("&Undo"), textEdit, &QTextEdit::undo); actionUndo->setShortcut(QKeySequence::Undo); tb->addAction(actionUndo); const QIcon redoIcon = QIcon::fromTheme("edit-redo", QIcon(rsrcPath + "/editredo.png")); actionRedo = menu->addAction(redoIcon, tr("&Redo"), textEdit, &QTextEdit::redo); actionRedo->setPriority(QAction::LowPriority); actionRedo->setShortcut(QKeySequence::Redo); tb->addAction(actionRedo); menu->addSeparator(); #ifndef QT_NO_CLIPBOARD const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon(rsrcPath + "/editcut.png")); actionCut = menu->addAction(cutIcon, tr("Cu&t"), textEdit, &QTextEdit::cut); actionCut->setPriority(QAction::LowPriority); actionCut->setShortcut(QKeySequence::Cut); tb->addAction(actionCut); const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon(rsrcPath + "/editcopy.png")); actionCopy = menu->addAction(copyIcon, tr("&Copy"), textEdit, &QTextEdit::copy); actionCopy->setPriority(QAction::LowPriority); actionCopy->setShortcut(QKeySequence::Copy); tb->addAction(actionCopy); const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon(rsrcPath + "/editpaste.png")); actionPaste = menu->addAction(pasteIcon, tr("&Paste"), textEdit, &QTextEdit::paste); actionPaste->setPriority(QAction::LowPriority); actionPaste->setShortcut(QKeySequence::Paste); tb->addAction(actionPaste); if (const QMimeData *md = QApplication::clipboard()->mimeData()) actionPaste->setEnabled(md->hasText()); #endif } void TextEdit::setupTextActions() { QToolBar *tb = addToolBar(tr("Format Actions")); QMenu *menu = menuBar()->addMenu(tr("F&ormat")); const QIcon boldIcon = QIcon::fromTheme("format-text-bold", QIcon(rsrcPath + "/textbold.png")); actionTextBold = menu->addAction(boldIcon, tr("&Bold"), this, &TextEdit::textBold); actionTextBold->setShortcut(Qt::CTRL + Qt::Key_B); actionTextBold->setPriority(QAction::LowPriority); QFont bold; bold.setBold(true); actionTextBold->setFont(bold); tb->addAction(actionTextBold); actionTextBold->setCheckable(true); const QIcon italicIcon = QIcon::fromTheme("format-text-italic", QIcon(rsrcPath + "/textitalic.png")); actionTextItalic = menu->addAction(italicIcon, tr("&Italic"), this, &TextEdit::textItalic); actionTextItalic->setPriority(QAction::LowPriority); actionTextItalic->setShortcut(Qt::CTRL + Qt::Key_I); QFont italic; italic.setItalic(true); actionTextItalic->setFont(italic); tb->addAction(actionTextItalic); actionTextItalic->setCheckable(true); const QIcon underlineIcon = QIcon::fromTheme("format-text-underline", QIcon(rsrcPath + "/textunder.png")); actionTextUnderline = menu->addAction(underlineIcon, tr("&Underline"), this, &TextEdit::textUnderline); actionTextUnderline->setShortcut(Qt::CTRL + Qt::Key_U); actionTextUnderline->setPriority(QAction::LowPriority); QFont underline; underline.setUnderline(true); actionTextUnderline->setFont(underline); tb->addAction(actionTextUnderline); actionTextUnderline->setCheckable(true); menu->addSeparator(); const QIcon leftIcon = QIcon::fromTheme("format-justify-left", QIcon(rsrcPath + "/textleft.png")); actionAlignLeft = new QAction(leftIcon, tr("&Left"), this); actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L); actionAlignLeft->setCheckable(true); actionAlignLeft->setPriority(QAction::LowPriority); const QIcon centerIcon = QIcon::fromTheme("format-justify-center", QIcon(rsrcPath + "/textcenter.png")); actionAlignCenter = new QAction(centerIcon, tr("C&enter"), this); actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E); actionAlignCenter->setCheckable(true); actionAlignCenter->setPriority(QAction::LowPriority); const QIcon rightIcon = QIcon::fromTheme("format-justify-right", QIcon(rsrcPath + "/textright.png")); actionAlignRight = new QAction(rightIcon, tr("&Right"), this); actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R); actionAlignRight->setCheckable(true); actionAlignRight->setPriority(QAction::LowPriority); const QIcon fillIcon = QIcon::fromTheme("format-justify-fill", QIcon(rsrcPath + "/textjustify.png")); actionAlignJustify = new QAction(fillIcon, tr("&Justify"), this); actionAlignJustify->setShortcut(Qt::CTRL + Qt::Key_J); actionAlignJustify->setCheckable(true); actionAlignJustify->setPriority(QAction::LowPriority); // Make sure the alignLeft is always left of the alignRight QActionGroup *alignGroup = new QActionGroup(this); connect(alignGroup, &QActionGroup::triggered, this, &TextEdit::textAlign); if (QApplication::isLeftToRight()) { alignGroup->addAction(actionAlignLeft); alignGroup->addAction(actionAlignCenter); alignGroup->addAction(actionAlignRight); } else { alignGroup->addAction(actionAlignRight); alignGroup->addAction(actionAlignCenter); alignGroup->addAction(actionAlignLeft); } alignGroup->addAction(actionAlignJustify); tb->addActions(alignGroup->actions()); menu->addActions(alignGroup->actions()); menu->addSeparator(); QPixmap pix(16, 16); pix.fill(Qt::black); actionTextColor = menu->addAction(pix, tr("&Color..."), this, &TextEdit::textColor); tb->addAction(actionTextColor); tb = addToolBar(tr("Format Actions")); tb->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); addToolBarBreak(Qt::TopToolBarArea); addToolBar(tb); comboStyle = new QComboBox(tb); tb->addWidget(comboStyle); comboStyle->addItem("Standard"); comboStyle->addItem("Bullet List (Disc)"); comboStyle->addItem("Bullet List (Circle)"); comboStyle->addItem("Bullet List (Square)"); comboStyle->addItem("Ordered List (Decimal)"); comboStyle->addItem("Ordered List (Alpha lower)"); comboStyle->addItem("Ordered List (Alpha upper)"); comboStyle->addItem("Ordered List (Roman lower)"); comboStyle->addItem("Ordered List (Roman upper)"); connect(comboStyle, QOverload::of(&QComboBox::activated), this, &TextEdit::textStyle); comboFont = new QFontComboBox(tb); tb->addWidget(comboFont); connect(comboFont, QOverload::of(&QComboBox::activated), this, &TextEdit::textFamily); comboSize = new QComboBox(tb); comboSize->setObjectName("comboSize"); tb->addWidget(comboSize); comboSize->setEditable(true); const QList standardSizes = QFontDatabase::standardSizes(); foreach (int size, standardSizes) comboSize->addItem(QString::number(size)); comboSize->setCurrentIndex(standardSizes.indexOf(QApplication::font().pointSize())); connect(comboSize, QOverload::of(&QComboBox::activated), this, &TextEdit::textSize); } bool TextEdit::load(const QString &f) { if (!QFile::exists(f)) return false; QFile file(f); if (!file.open(QFile::ReadOnly)) return false; QByteArray data = file.readAll(); QTextCodec *codec = Qt::codecForHtml(data); QString str = codec->toUnicode(data); if (Qt::mightBeRichText(str)) { textEdit->setHtml(str); } else { str = QString::fromLocal8Bit(data); textEdit->setPlainText(str); } setCurrentFileName(f); return true; } bool TextEdit::maybeSave() { if (!textEdit->document()->isModified()) return true; const QMessageBox::StandardButton ret = QMessageBox::warning(this, QCoreApplication::applicationName(), tr("The document has been modified.\n" "Do you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); if (ret == QMessageBox::Save) return fileSave(); else if (ret == QMessageBox::Cancel) return false; return true; } void TextEdit::setCurrentFileName(const QString &fileName) { this->fileName = fileName; textEdit->document()->setModified(false); QString shownName; if (fileName.isEmpty()) shownName = "untitled.txt"; else shownName = QFileInfo(fileName).fileName(); setWindowTitle(tr("%1[*] - %2").arg(shownName, QCoreApplication::applicationName())); setWindowModified(false); } void TextEdit::fileNew() { if (maybeSave()) { textEdit->clear(); setCurrentFileName(QString()); } } void TextEdit::fileOpen() { QFileDialog fileDialog(this, tr("Open File...")); fileDialog.setAcceptMode(QFileDialog::AcceptOpen); fileDialog.setFileMode(QFileDialog::ExistingFile); fileDialog.setMimeTypeFilters(QStringList() << "text/html" << "text/plain"); if (fileDialog.exec() != QDialog::Accepted) return; const QString fn = fileDialog.selectedFiles().first(); if (load(fn)) statusBar()->showMessage(tr("Opened \"%1\"").arg(QDir::toNativeSeparators(fn))); else statusBar()->showMessage(tr("Could not open \"%1\"").arg(QDir::toNativeSeparators(fn))); } bool TextEdit::fileSave() { if (fileName.isEmpty()) return fileSaveAs(); if (fileName.startsWith(QStringLiteral(":/"))) return fileSaveAs(); QTextDocumentWriter writer(fileName); bool success = writer.write(textEdit->document()); if (success) { textEdit->document()->setModified(false); statusBar()->showMessage(tr("Wrote \"%1\"").arg(QDir::toNativeSeparators(fileName))); } else { statusBar()->showMessage(tr("Could not write to file \"%1\"") .arg(QDir::toNativeSeparators(fileName))); } return success; } bool TextEdit::fileSaveAs() { QFileDialog fileDialog(this, tr("Save as...")); fileDialog.setAcceptMode(QFileDialog::AcceptSave); QStringList mimeTypes; mimeTypes << "application/vnd.oasis.opendocument.text" << "text/html" << "text/plain"; fileDialog.setMimeTypeFilters(mimeTypes); fileDialog.setDefaultSuffix("odt"); if (fileDialog.exec() != QDialog::Accepted) return false; const QString fn = fileDialog.selectedFiles().first(); setCurrentFileName(fn); return fileSave(); } void TextEdit::filePrint() { #if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG) QPrinter printer(QPrinter::HighResolution); QPrintDialog *dlg = new QPrintDialog(&printer, this); if (textEdit->textCursor().hasSelection()) dlg->addEnabledOption(QAbstractPrintDialog::PrintSelection); dlg->setWindowTitle(tr("Print Document")); if (dlg->exec() == QDialog::Accepted) textEdit->print(&printer); delete dlg; #endif } void TextEdit::filePrintPreview() { #if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG) QPrinter printer(QPrinter::HighResolution); QPrintPreviewDialog preview(&printer, this); connect(&preview, &QPrintPreviewDialog::paintRequested, this, &TextEdit::printPreview); preview.exec(); #endif } void TextEdit::printPreview(QPrinter *printer) { #ifdef QT_NO_PRINTER Q_UNUSED(printer); #else textEdit->print(printer); #endif } void TextEdit::filePrintPdf() { #ifndef QT_NO_PRINTER //! [0] QFileDialog fileDialog(this, tr("Export PDF")); fileDialog.setAcceptMode(QFileDialog::AcceptSave); fileDialog.setMimeTypeFilters(QStringList("application/pdf")); fileDialog.setDefaultSuffix("pdf"); if (fileDialog.exec() != QDialog::Accepted) return; QString fileName = fileDialog.selectedFiles().first(); QPrinter printer(QPrinter::HighResolution); printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName(fileName); textEdit->document()->print(&printer); statusBar()->showMessage(tr("Exported \"%1\"") .arg(QDir::toNativeSeparators(fileName))); //! [0] #endif } void TextEdit::textBold() { QTextCharFormat fmt; fmt.setFontWeight(actionTextBold->isChecked() ? QFont::Bold : QFont::Normal); mergeFormatOnWordOrSelection(fmt); } void TextEdit::textUnderline() { QTextCharFormat fmt; fmt.setFontUnderline(actionTextUnderline->isChecked()); mergeFormatOnWordOrSelection(fmt); } void TextEdit::textItalic() { QTextCharFormat fmt; fmt.setFontItalic(actionTextItalic->isChecked()); mergeFormatOnWordOrSelection(fmt); } void TextEdit::textFamily(const QString &f) { QTextCharFormat fmt; fmt.setFontFamily(f); mergeFormatOnWordOrSelection(fmt); } void TextEdit::textSize(const QString &p) { qreal pointSize = p.toFloat(); if (p.toFloat() > 0) { QTextCharFormat fmt; fmt.setFontPointSize(pointSize); mergeFormatOnWordOrSelection(fmt); } } void TextEdit::textStyle(int styleIndex) { QTextCursor cursor = textEdit->textCursor(); if (styleIndex != 0) { QTextListFormat::Style style = QTextListFormat::ListDisc; switch (styleIndex) { default: case 1: style = QTextListFormat::ListDisc; break; case 2: style = QTextListFormat::ListCircle; break; case 3: style = QTextListFormat::ListSquare; break; case 4: style = QTextListFormat::ListDecimal; break; case 5: style = QTextListFormat::ListLowerAlpha; break; case 6: style = QTextListFormat::ListUpperAlpha; break; case 7: style = QTextListFormat::ListLowerRoman; break; case 8: style = QTextListFormat::ListUpperRoman; break; } cursor.beginEditBlock(); QTextBlockFormat blockFmt = cursor.blockFormat(); QTextListFormat listFmt; if (cursor.currentList()) { listFmt = cursor.currentList()->format(); } else { listFmt.setIndent(blockFmt.indent() + 1); blockFmt.setIndent(0); cursor.setBlockFormat(blockFmt); } listFmt.setStyle(style); cursor.createList(listFmt); cursor.endEditBlock(); } else { // #### QTextBlockFormat bfmt; bfmt.setObjectIndex(-1); cursor.mergeBlockFormat(bfmt); } } void TextEdit::textColor() { QColor col = QColorDialog::getColor(textEdit->textColor(), this); if (!col.isValid()) return; QTextCharFormat fmt; fmt.setForeground(col); mergeFormatOnWordOrSelection(fmt); colorChanged(col); } void TextEdit::textAlign(QAction *a) { if (a == actionAlignLeft) textEdit->setAlignment(Qt::AlignLeft | Qt::AlignAbsolute); else if (a == actionAlignCenter) textEdit->setAlignment(Qt::AlignHCenter); else if (a == actionAlignRight) textEdit->setAlignment(Qt::AlignRight | Qt::AlignAbsolute); else if (a == actionAlignJustify) textEdit->setAlignment(Qt::AlignJustify); } void TextEdit::currentCharFormatChanged(const QTextCharFormat &format) { fontChanged(format.font()); colorChanged(format.foreground().color()); } void TextEdit::cursorPositionChanged() { alignmentChanged(textEdit->alignment()); } void TextEdit::clipboardDataChanged() { #ifndef QT_NO_CLIPBOARD if (const QMimeData *md = QApplication::clipboard()->mimeData()) actionPaste->setEnabled(md->hasText()); #endif } void TextEdit::about() { QMessageBox::about(this, tr("About"), tr("This example demonstrates Qt's " "rich text editing facilities in action, providing an example " "document for you to experiment with.")); } void TextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format) { QTextCursor cursor = textEdit->textCursor(); if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); cursor.mergeCharFormat(format); textEdit->mergeCurrentCharFormat(format); } void TextEdit::fontChanged(const QFont &f) { comboFont->setCurrentIndex(comboFont->findText(QFontInfo(f).family())); comboSize->setCurrentIndex(comboSize->findText(QString::number(f.pointSize()))); actionTextBold->setChecked(f.bold()); actionTextItalic->setChecked(f.italic()); actionTextUnderline->setChecked(f.underline()); } void TextEdit::colorChanged(const QColor &c) { QPixmap pix(16, 16); pix.fill(c); actionTextColor->setIcon(pix); } void TextEdit::alignmentChanged(Qt::Alignment a) { if (a & Qt::AlignLeft) actionAlignLeft->setChecked(true); else if (a & Qt::AlignHCenter) actionAlignCenter->setChecked(true); else if (a & Qt::AlignRight) actionAlignRight->setChecked(true); else if (a & Qt::AlignJustify) actionAlignJustify->setChecked(true); }