Replace QDialog::exec() with open() in text edit example

On WASM, the shortcut triggering the dialog in textedit example
seemingly lets the event invoke the default action. What really happens
is that the dialog's exec() does not return and WASM does not have a
chance to set preventDefault() on the keyboard event it receives. This
masks other problems with keyboards shortcuts.

Uses of exec() on message boxes were replaced, too. Closing operation
is now performed in two steps, first the event is ignored and maybeSave
is called to query for potential changes of the text document.
If there are no changes or the file is discarded, the close continues
with a second event, now accepted.

PrintDialog::exec(), QPrintPreviewDialog::exec(), QFileDialog::exec()
were also eliminated in favor of open().

Finally, indirect calls to exec() via QColorDialog::getColor were
removed.

Task-number: QTBUG-76587
Change-Id: Ia90fad46ec3f94244723512be3ec93b64df9d9ef
Reviewed-by: Mikołaj Boc <Mikolaj.Boc@qt.io>
This commit is contained in:
Mikolaj Boc 2022-09-08 15:26:00 +02:00
parent a6059cebe8
commit 7022d0e223
2 changed files with 149 additions and 91 deletions

View File

@ -21,6 +21,7 @@
#include <QTextCursor> #include <QTextCursor>
#include <QTextDocumentWriter> #include <QTextDocumentWriter>
#include <QTextList> #include <QTextList>
#include <QTimer>
#include <QtDebug> #include <QtDebug>
#include <QCloseEvent> #include <QCloseEvent>
#include <QMessageBox> #include <QMessageBox>
@ -119,10 +120,13 @@ TextEdit::TextEdit(QWidget *parent)
//! [closeevent] //! [closeevent]
void TextEdit::closeEvent(QCloseEvent *e) void TextEdit::closeEvent(QCloseEvent *e)
{ {
if (maybeSave()) if (closeAccepted) {
e->accept(); e->accept();
else return;
e->ignore(); }
e->ignore();
maybeSave(SaveContinuation::Close);
} }
//! [closeevent] //! [closeevent]
@ -145,12 +149,13 @@ void TextEdit::setupFileActions()
menu->addSeparator(); menu->addSeparator();
const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(rsrcPath + "/filesave.png")); const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(rsrcPath + "/filesave.png"));
actionSave = menu->addAction(saveIcon, tr("&Save"), this, &TextEdit::fileSave); actionSave = menu->addAction(saveIcon, tr("&Save"), this,
[this]() { fileSave(SaveContinuation::None); });
actionSave->setShortcut(QKeySequence::Save); actionSave->setShortcut(QKeySequence::Save);
actionSave->setEnabled(false); actionSave->setEnabled(false);
tb->addAction(actionSave); tb->addAction(actionSave);
a = menu->addAction(tr("Save &As..."), this, &TextEdit::fileSaveAs); a = menu->addAction(tr("Save &As..."), this, [this]() { fileSaveAs(SaveContinuation::None); });
a->setPriority(QAction::LowPriority); a->setPriority(QAction::LowPriority);
menu->addSeparator(); menu->addSeparator();
@ -398,21 +403,31 @@ bool TextEdit::load(const QString &f)
return true; return true;
} }
bool TextEdit::maybeSave() void TextEdit::maybeSave(SaveContinuation continuation)
{ {
if (!textEdit->document()->isModified()) if (!textEdit->document()->isModified()) {
return true; // Execute continuation as soon as control has returned to the event loop so that existing
// dialogs do not get in the way of closing the window.
QTimer::singleShot(0, [this, continuation]() { fileSaveComplete(continuation); });
return;
}
const QMessageBox::StandardButton ret = QMessageBox *msgBox =
QMessageBox::warning(this, QCoreApplication::applicationName(), new QMessageBox(QMessageBox::Icon::Warning, QCoreApplication::applicationName(),
tr("The document has been modified.\n" tr("The document has been modified.\n"
"Do you want to save your changes?"), "Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, this);
if (ret == QMessageBox::Save)
return fileSave(); connect(msgBox, &QMessageBox::finished, [=](int result) {
if (ret == QMessageBox::Cancel) if (result == QMessageBox::Save) {
return false; fileSave(continuation);
return true; return;
}
fileSaveComplete(result == QMessageBox::Discard ? continuation : SaveContinuation::None);
});
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->open();
} }
void TextEdit::setCurrentFileName(const QString &fileName) void TextEdit::setCurrentFileName(const QString &fileName)
@ -432,18 +447,15 @@ void TextEdit::setCurrentFileName(const QString &fileName)
void TextEdit::fileNew() void TextEdit::fileNew()
{ {
if (maybeSave()) { maybeSave(SaveContinuation::Clear);
textEdit->clear();
setCurrentFileName({});
}
} }
void TextEdit::fileOpen() void TextEdit::fileOpen()
{ {
QFileDialog fileDialog(this, tr("Open File...")); QFileDialog *fileDialog = new QFileDialog(this, tr("Open File..."));
fileDialog.setAcceptMode(QFileDialog::AcceptOpen); fileDialog->setAcceptMode(QFileDialog::AcceptOpen);
fileDialog.setFileMode(QFileDialog::ExistingFile); fileDialog->setFileMode(QFileDialog::ExistingFile);
fileDialog.setMimeTypeFilters({ fileDialog->setMimeTypeFilters({
#if QT_CONFIG(texthtmlparser) #if QT_CONFIG(texthtmlparser)
"text/html", "text/html",
#endif #endif
@ -452,19 +464,20 @@ void TextEdit::fileOpen()
"text/markdown", "text/markdown",
#endif #endif
"text/plain"}); "text/plain"});
if (fileDialog.exec() != QDialog::Accepted)
return; connect(fileDialog, &QFileDialog::fileSelected, [=](const QString &file) {
const QString fn = fileDialog.selectedFiles().constFirst(); statusBar()->showMessage(
if (load(fn)) load(file) ? tr(R"(Opened "%1")").arg(QDir::toNativeSeparators(file))
statusBar()->showMessage(tr("Opened \"%1\"").arg(QDir::toNativeSeparators(fn))); : tr(R"(Could not open "%1")").arg(QDir::toNativeSeparators(file)));
else });
statusBar()->showMessage(tr("Could not open \"%1\"").arg(QDir::toNativeSeparators(fn))); fileDialog->setAttribute(Qt::WA_DeleteOnClose);
fileDialog->open();
} }
bool TextEdit::fileSave() void TextEdit::fileSave(SaveContinuation continuation)
{ {
if (fileName.isEmpty() || fileName.startsWith(u":/")) if (fileName.isEmpty() || fileName.startsWith(u":/"))
return fileSaveAs(); return fileSaveAs(continuation);
QTextDocumentWriter writer(fileName); QTextDocumentWriter writer(fileName);
bool success = writer.write(textEdit->document()); bool success = writer.write(textEdit->document());
@ -475,13 +488,13 @@ bool TextEdit::fileSave()
statusBar()->showMessage(tr("Could not write to file \"%1\"") statusBar()->showMessage(tr("Could not write to file \"%1\"")
.arg(QDir::toNativeSeparators(fileName))); .arg(QDir::toNativeSeparators(fileName)));
} }
return success; fileSaveComplete(success ? continuation : SaveContinuation::None);
} }
bool TextEdit::fileSaveAs() void TextEdit::fileSaveAs(SaveContinuation continuation)
{ {
QFileDialog fileDialog(this, tr("Save as...")); QFileDialog *fileDialog = new QFileDialog(this, tr("Save as..."));
fileDialog.setAcceptMode(QFileDialog::AcceptSave); fileDialog->setAcceptMode(QFileDialog::AcceptSave);
QStringList mimeTypes{"text/plain", QStringList mimeTypes{"text/plain",
#if QT_CONFIG(textodfwriter) #if QT_CONFIG(textodfwriter)
"application/vnd.oasis.opendocument.text", "application/vnd.oasis.opendocument.text",
@ -490,58 +503,82 @@ bool TextEdit::fileSaveAs()
"text/markdown", "text/markdown",
#endif #endif
"text/html"}; "text/html"};
fileDialog.setMimeTypeFilters(mimeTypes); fileDialog->setMimeTypeFilters(mimeTypes);
#if QT_CONFIG(textodfwriter) #if QT_CONFIG(textodfwriter)
fileDialog.setDefaultSuffix("odt"); fileDialog->setDefaultSuffix("odt");
#endif #endif
if (fileDialog.exec() != QDialog::Accepted) connect(fileDialog, &QFileDialog::finished, [this, continuation, fileDialog](int result) {
return false; if (result != QDialog::Accepted)
const QString fn = fileDialog.selectedFiles().constFirst(); return;
setCurrentFileName(fn); setCurrentFileName(fileDialog->selectedFiles().constFirst());
return fileSave(); fileSave(continuation);
});
fileDialog->setAttribute(Qt::WA_DeleteOnClose);
fileDialog->open();
}
void TextEdit::fileSaveComplete(SaveContinuation continuation)
{
switch (continuation) {
case SaveContinuation::Clear:
textEdit->clear();
setCurrentFileName({});
return;
case SaveContinuation::Close:
closeAccepted = true;
close();
return;
case SaveContinuation::None:
// NOOP as promised
return;
}
} }
void TextEdit::filePrint() void TextEdit::filePrint()
{ {
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog) #if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog)
QPrinter printer(QPrinter::HighResolution); auto printer = std::make_shared<QPrinter>(QPrinter::HighResolution);
QPrintDialog dlg(&printer, this); QPrintDialog *dlg = new QPrintDialog(printer.get(), this);
if (textEdit->textCursor().hasSelection()) if (textEdit->textCursor().hasSelection())
dlg.setOption(QAbstractPrintDialog::PrintSelection); dlg->setOption(QAbstractPrintDialog::PrintSelection);
dlg.setWindowTitle(tr("Print Document")); dlg->setWindowTitle(tr("Print Document"));
if (dlg.exec() == QDialog::Accepted) dlg->setAttribute(Qt::WA_DeleteOnClose);
textEdit->print(&printer); connect(dlg, qOverload<QPrinter *>(&QPrintDialog::accepted),
[this](QPrinter *printer) { textEdit->print(printer); });
connect(dlg, &QPrintDialog::finished, [printer]() mutable { printer.reset(); });
dlg->open();
#endif #endif
} }
void TextEdit::filePrintPreview() void TextEdit::filePrintPreview()
{ {
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printpreviewdialog) #if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printpreviewdialog)
QPrinter printer(QPrinter::HighResolution); auto printer = std::make_shared<QPrinter>(QPrinter::HighResolution);
QPrintPreviewDialog preview(&printer, this); QPrintPreviewDialog *preview = new QPrintPreviewDialog(printer.get(), this);
connect(&preview, &QPrintPreviewDialog::paintRequested, textEdit, &QTextEdit::print); preview->setAttribute(Qt::WA_DeleteOnClose);
preview.exec(); connect(preview, &QPrintPreviewDialog::paintRequested, textEdit, &QTextEdit::print);
connect(preview, &QPrintPreviewDialog::finished, [printer]() mutable { printer.reset(); });
preview->open();
#endif #endif
} }
void TextEdit::filePrintPdf() void TextEdit::filePrintPdf()
{ {
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) #if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer)
//! [0] QFileDialog *fileDialog = new QFileDialog(this, tr("Export PDF"));
QFileDialog fileDialog(this, tr("Export PDF")); fileDialog->setAcceptMode(QFileDialog::AcceptSave);
fileDialog.setAcceptMode(QFileDialog::AcceptSave); fileDialog->setMimeTypeFilters(QStringList("application/pdf"));
fileDialog.setMimeTypeFilters(QStringList("application/pdf")); fileDialog->setDefaultSuffix("pdf");
fileDialog.setDefaultSuffix("pdf"); fileDialog->setAttribute(Qt::WA_DeleteOnClose);
if (fileDialog.exec() != QDialog::Accepted) connect(fileDialog, &QFileDialog::fileSelected, [this](const QString &file) {
return; QPrinter printer(QPrinter::HighResolution);
QString pdfFileName = fileDialog.selectedFiles().constFirst(); printer.setOutputFormat(QPrinter::PdfFormat);
QPrinter printer(QPrinter::HighResolution); printer.setOutputFileName(file);
printer.setOutputFormat(QPrinter::PdfFormat); textEdit->document()->print(&printer);
printer.setOutputFileName(pdfFileName); statusBar()->showMessage(tr("Exported \"%1\"").arg(QDir::toNativeSeparators(file)));
textEdit->document()->print(&printer); });
statusBar()->showMessage(tr("Exported \"%1\"")
.arg(QDir::toNativeSeparators(pdfFileName))); fileDialog->open();
//! [0]
#endif #endif
} }
@ -669,24 +706,34 @@ void TextEdit::textStyle(int styleIndex)
void TextEdit::textColor() void TextEdit::textColor()
{ {
QColor col = QColorDialog::getColor(textEdit->textColor(), this); QColorDialog *dlg = new QColorDialog(this);
if (!col.isValid()) dlg->setCurrentColor(textEdit->textColor());
return; connect(dlg, &QColorDialog::colorSelected, [this](const QColor &color) {
QTextCharFormat fmt; if (!color.isValid())
fmt.setForeground(col); return;
mergeFormatOnWordOrSelection(fmt); QTextCharFormat fmt;
colorChanged(col); fmt.setForeground(color);
mergeFormatOnWordOrSelection(fmt);
colorChanged(color);
});
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->open();
} }
void TextEdit::underlineColor() void TextEdit::underlineColor()
{ {
QColor col = QColorDialog::getColor(Qt::black, this); QColorDialog *dlg = new QColorDialog(this);
if (!col.isValid()) dlg->setCurrentColor(textEdit->textColor());
return; connect(dlg, &QColorDialog::colorSelected, [this](const QColor &color) {
QTextCharFormat fmt; if (!color.isValid())
fmt.setUnderlineColor(col); return;
mergeFormatOnWordOrSelection(fmt); QTextCharFormat fmt;
colorChanged(col); fmt.setUnderlineColor(color);
mergeFormatOnWordOrSelection(fmt);
colorChanged(color);
});
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->open();
} }
void TextEdit::textAlign(QAction *a) void TextEdit::textAlign(QAction *a)
@ -809,9 +856,13 @@ void TextEdit::clipboardDataChanged()
void TextEdit::about() void TextEdit::about()
{ {
QMessageBox::about(this, tr("About"), tr("This example demonstrates Qt's " QMessageBox *msgBox =
"rich text editing facilities in action, providing an example " new QMessageBox(QMessageBox::Icon::Information, tr("About"),
"document for you to experiment with.")); tr("This example demonstrates Qt's rich text editing facilities in "
"action, providing an example document for you to experiment with."),
QMessageBox::NoButton, this);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->open();
} }
void TextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format) void TextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format)

View File

@ -31,10 +31,11 @@ public slots:
protected: protected:
void closeEvent(QCloseEvent *e) override; void closeEvent(QCloseEvent *e) override;
private:
enum class SaveContinuation { None, Close, Clear };
private slots: private slots:
void fileOpen(); void fileOpen();
bool fileSave();
bool fileSaveAs();
void filePrint(); void filePrint();
void filePrintPreview(); void filePrintPreview();
void filePrintPdf(); void filePrintPdf();
@ -62,7 +63,7 @@ private:
void setupFileActions(); void setupFileActions();
void setupEditActions(); void setupEditActions();
void setupTextActions(); void setupTextActions();
bool maybeSave(); void maybeSave(SaveContinuation saveContinuation);
void setCurrentFileName(const QString &fileName); void setCurrentFileName(const QString &fileName);
void modifyIndentation(int amount); void modifyIndentation(int amount);
@ -71,6 +72,10 @@ private:
void colorChanged(const QColor &c); void colorChanged(const QColor &c);
void alignmentChanged(Qt::Alignment a); void alignmentChanged(Qt::Alignment a);
void fileSave(SaveContinuation continuation);
void fileSaveAs(SaveContinuation continuation);
void fileSaveComplete(SaveContinuation continuation);
QAction *actionSave; QAction *actionSave;
QAction *actionTextBold; QAction *actionTextBold;
QAction *actionTextUnderline; QAction *actionTextUnderline;
@ -98,6 +103,8 @@ private:
QString fileName; QString fileName;
QTextEdit *textEdit; QTextEdit *textEdit;
bool closeAccepted = false;
}; };
#endif // TEXTEDIT_H #endif // TEXTEDIT_H