Polish the widgets/mainwindows/application example.

- Introduce Qt 5 signals & slot syntax
- Use QCommandLineParser to obtain file arguments
- Merge MainWindow::createMenus()/createQToolBars()
  into MainWindow::createActions(), removing the need
  to store the actions as member variables.
  Use QMenu::addAction() for brevity.
- Use QIcon::fromTheme() to obtain system icons and use
  resource icons as fallback.
- Rewrite settings code to use
  QWidget::saveGeometry(), Widget::restoreGeometry() since
  saving size and position does not work well with multiple
  screens. Query the available size when determining
  the initial size instead of using hard-coded values
  for High-DPI screens.
- Fix minor issues in code, use multi-argument version
  of QString::arg(), QDir::toNativeSeparators() to
  present file paths to the user.
- Fix snippet references accordingly.

Change-Id: I1bc49a8913aa6d669e0e666f04be3f1f42eaba10
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@theqtcompany.com>
Reviewed-by: Topi Reiniö <topi.reinio@digia.com>
This commit is contained in:
Friedemann Kleint 2015-07-10 11:39:17 +02:00
parent 554d54b432
commit b693900da5
6 changed files with 153 additions and 162 deletions

View File

@ -96,8 +96,7 @@
the widget that occupies the central area of the main window,
between the toolbars and the status bar.
Then we call \c createActions(), \c createMenus(), \c
createToolBars(), and \c createStatusBar(), four private
Then we call \c createActions() and \c createStatusBar(), two private
functions that set up the user interface. After that, we call \c
readSettings() to restore the user's preferences.
@ -184,7 +183,8 @@
\snippet mainwindows/application/mainwindow.cpp 22
The \c createActions() private function, which is called from the
\c MainWindow constructor, creates \l{QAction}s. The code is very
\c MainWindow constructor, creates \l{QAction}s and populates
the menus and two toolbars. The code is very
repetitive, so we show only the actions corresponding to
\uicontrol{File|New}, \uicontrol{File|Open}, and \uicontrol{Help|About Qt}.
@ -198,13 +198,27 @@
a "What's This?" text, and more. It emits a
\l{QAction::triggered()}{triggered()} signal whenever the user
invokes the action (e.g., by clicking the associated menu item or
toolbar button). We connect this signal to a slot that performs
the actual action.
toolbar button).
Instances of QAction can be created by passing a parent QObject or
by using one of the convenience functions of QMenu, QMenuBar or QToolBar.
We create the actions that are in a menu as well as in a toolbar
parented on the window to prevent ownership issues. For actions
that are only in the menu, we use the convenience function
QMenu::addAction(), which allows us to pass text, icon and the
target object and its slot member function.
Creating toolbars is very similar to creating menus. The same
actions that we put in the menus can be reused in the toolbars.
After creating the action, we add it to the toolbar using
QToolBar::addAction().
The code above contains one more idiom that must be explained.
For some of the actions, we specify an icon as a QIcon to the
QAction constructor. The QIcon constructor takes the file name
of an image that it tries to load. Here, the file name starts
QAction constructor. We use QIcon::fromTheme() to obtain
the correct standard icon from the underlying window system.
If that fails due to the platform not supporting it, we
pass a file name as fallback. Here, the file name starts
with \c{:}. Such file names aren't ordinary file names, but
rather path in the executable's stored resources. We'll come back
to this when we review the \c application.qrc file that's part of
@ -219,30 +233,12 @@
the QAction::setEnabled() slot, ensuring that the actions are
disabled when the text editor has no selection.
\snippet mainwindows/application/mainwindow.cpp 25
\snippet mainwindows/application/mainwindow.cpp 27
Creating actions isn't sufficient to make them available to the
user; we must also add them to the menu system. This is what \c
createMenus() does. We create a \uicontrol{File}, an \uicontrol{Edit}, and
a \uicontrol{Help} menu. QMainWindow::menuBar() lets us access the
window's menu bar widget. We don't have to worry about creating
the menu bar ourselves; the first time we call this function, the
QMenuBar is created.
Just before we create the \uicontrol{Help} menu, we call
QMenuBar::addSeparator(). This has no effect for most widget
styles (e.g., Windows and OS X styles), but for some
styles this makes sure that \uicontrol{Help} is pushed to the right
side of the menu bar.
Let's now review the toolbars:
\snippet mainwindows/application/mainwindow.cpp 30
Creating toolbars is very similar to creating menus. The same
actions that we put in the menus can be reused in the toolbars.
\snippet mainwindows/application/mainwindow.cpp 32
\snippet mainwindows/application/mainwindow.cpp 33
@ -265,15 +261,15 @@
company and the name of the product. This ensures that the
settings for different applications are kept separately.
We use QSettings::value() to extract the value of the "pos" and
"size" settings. The second argument to QSettings::value() is
We use QSettings::value() to extract the value of the geometry setting.
The second argument to QSettings::value() is
optional and specifies a default value for the setting if there
exists none. This value is used the first time the application is
run.
When restoring the position and size of a window, it's important
to call QWidget::resize() before QWidget::move(). The reason why
is given in the \l{Window Geometry} overview.
We use QWidget::saveGeometry() and Widget::restoreGeometry() to
save the position. They use an opaque QByteArray to store
screen number, geometry and window state.
\snippet mainwindows/application/mainwindow.cpp 37
\snippet mainwindows/application/mainwindow.cpp 39
@ -297,9 +293,9 @@
QMessageBox::Escape flag.
The \c maybeSave() function returns \c true in all cases, except
when the user clicks \uicontrol{Cancel}. The caller must check the
return value and stop whatever it was doing if the return value
is \c false.
when the user clicks \uicontrol{Cancel} or saving the file fails.
The caller must check the return value and stop whatever it was
doing if the return value is \c false.
\snippet mainwindows/application/mainwindow.cpp 42
\snippet mainwindows/application/mainwindow.cpp 43
@ -361,6 +357,10 @@
\snippet mainwindows/application/main.cpp 0
The main function uses QCommandLineParser to check whether some file
argument was passed to the application and loads it via
MainWindow::loadFile().
\section1 The Resource File
As you will probably recall, for some of the actions, we

View File

@ -40,6 +40,8 @@
//! [0]
#include <QApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include "mainwindow.h"
@ -48,9 +50,19 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(application);
QApplication app(argc, argv);
app.setOrganizationName("QtProject");
app.setApplicationName("Application Example");
QCoreApplication::setOrganizationName("QtProject");
QCoreApplication::setApplicationName("Application Example");
QCoreApplication::setApplicationVersion(QT_VERSION_STR);
QCommandLineParser parser;
parser.setApplicationDescription(QCoreApplication::applicationName());
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("file", "The file to open.");
parser.process(app);
MainWindow mainWin;
if (!parser.positionalArguments().isEmpty())
mainWin.loadFile(parser.positionalArguments().first());
mainWin.show();
return app.exec();
}

View File

@ -46,22 +46,20 @@
//! [1]
MainWindow::MainWindow()
: textEdit(new QPlainTextEdit)
//! [1] //! [2]
{
textEdit = new QPlainTextEdit;
setCentralWidget(textEdit);
createActions();
createMenus();
createToolBars();
createStatusBar();
readSettings();
connect(textEdit->document(), SIGNAL(contentsChanged()),
this, SLOT(documentWasModified()));
connect(textEdit->document(), &QTextDocument::contentsChanged,
this, &MainWindow::documentWasModified);
setCurrentFile("");
setCurrentFile(QString());
setUnifiedTitleAndToolBarOnMac(true);
}
//! [2]
@ -85,7 +83,7 @@ void MainWindow::newFile()
{
if (maybeSave()) {
textEdit->clear();
setCurrentFile("");
setCurrentFile(QString());
}
}
//! [6]
@ -121,13 +119,9 @@ bool MainWindow::saveAs()
QFileDialog dialog(this);
dialog.setWindowModality(Qt::WindowModal);
dialog.setAcceptMode(QFileDialog::AcceptSave);
QStringList files;
if (dialog.exec())
files = dialog.selectedFiles();
else
if (dialog.exec() != QDialog::Accepted)
return false;
return saveFile(files.at(0));
return saveFile(dialog.selectedFiles().first());
}
//! [12]
@ -154,121 +148,103 @@ void MainWindow::documentWasModified()
void MainWindow::createActions()
//! [17] //! [18]
{
newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this);
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
QToolBar *fileToolBar = addToolBar(tr("File"));
const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(":/images/new.png"));
QAction *newAct = new QAction(newIcon, tr("&New"), this);
newAct->setShortcuts(QKeySequence::New);
newAct->setStatusTip(tr("Create a new file"));
connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
connect(newAct, &QAction::triggered, this, &MainWindow::newFile);
fileMenu->addAction(newAct);
fileToolBar->addAction(newAct);
//! [19]
openAct = new QAction(QIcon(":/images/open.png"), tr("&Open..."), this);
const QIcon openIcon = QIcon::fromTheme("document-open", QIcon(":/images/open.png"));
QAction *openAct = new QAction(openIcon, tr("&Open..."), this);
openAct->setShortcuts(QKeySequence::Open);
openAct->setStatusTip(tr("Open an existing file"));
connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
connect(openAct, &QAction::triggered, this, &MainWindow::open);
fileMenu->addAction(openAct);
fileToolBar->addAction(openAct);
//! [18] //! [19]
saveAct = new QAction(QIcon(":/images/save.png"), tr("&Save"), this);
const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(":/images/save.png"));
QAction *saveAct = new QAction(saveIcon, tr("&Save"), this);
saveAct->setShortcuts(QKeySequence::Save);
saveAct->setStatusTip(tr("Save the document to disk"));
connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));
connect(saveAct, &QAction::triggered, this, &MainWindow::save);
fileMenu->addAction(saveAct);
fileToolBar->addAction(saveAct);
saveAsAct = new QAction(tr("Save &As..."), this);
const QIcon saveAsIcon = QIcon::fromTheme("document-save-as");
QAction *saveAsAct = fileMenu->addAction(saveAsIcon, tr("Save &As..."), this, &MainWindow::saveAs);
saveAsAct->setShortcuts(QKeySequence::SaveAs);
saveAsAct->setStatusTip(tr("Save the document under a new name"));
connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));
//! [20]
exitAct = new QAction(tr("E&xit"), this);
fileMenu->addSeparator();
const QIcon exitIcon = QIcon::fromTheme("application-exit");
QAction *exitAct = fileMenu->addAction(exitIcon, tr("E&xit"), this, &QWidget::close);
exitAct->setShortcuts(QKeySequence::Quit);
//! [20]
exitAct->setStatusTip(tr("Exit the application"));
connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
//! [21]
cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this);
QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
QToolBar *editToolBar = addToolBar(tr("Edit"));
//!
const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon(":/images/cut.png"));
QAction *cutAct = new QAction(cutIcon, tr("Cu&t"), this);
//! [21]
cutAct->setShortcuts(QKeySequence::Cut);
cutAct->setStatusTip(tr("Cut the current selection's contents to the "
"clipboard"));
connect(cutAct, SIGNAL(triggered()), textEdit, SLOT(cut()));
connect(cutAct, &QAction::triggered, textEdit, &QPlainTextEdit::cut);
editMenu->addAction(cutAct);
editToolBar->addAction(cutAct);
copyAct = new QAction(QIcon(":/images/copy.png"), tr("&Copy"), this);
const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon(":/images/copy.png"));
QAction *copyAct = new QAction(copyIcon, tr("&Copy"), this);
copyAct->setShortcuts(QKeySequence::Copy);
copyAct->setStatusTip(tr("Copy the current selection's contents to the "
"clipboard"));
connect(copyAct, SIGNAL(triggered()), textEdit, SLOT(copy()));
connect(copyAct, &QAction::triggered, textEdit, &QPlainTextEdit::copy);
editMenu->addAction(copyAct);
editToolBar->addAction(copyAct);
pasteAct = new QAction(QIcon(":/images/paste.png"), tr("&Paste"), this);
const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon(":/images/paste.png"));
QAction *pasteAct = new QAction(pasteIcon, tr("&Paste"), this);
pasteAct->setShortcuts(QKeySequence::Paste);
pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
"selection"));
connect(pasteAct, SIGNAL(triggered()), textEdit, SLOT(paste()));
connect(pasteAct, &QAction::triggered, textEdit, &QPlainTextEdit::paste);
editMenu->addAction(pasteAct);
editToolBar->addAction(pasteAct);
aboutAct = new QAction(tr("&About"), this);
menuBar()->addSeparator();
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &MainWindow::about);
aboutAct->setStatusTip(tr("Show the application's About box"));
connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
//! [22]
aboutQtAct = new QAction(tr("About &Qt"), this);
QAction *aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
//! [22]
//! [23]
cutAct->setEnabled(false);
//! [23] //! [24]
copyAct->setEnabled(false);
connect(textEdit, SIGNAL(copyAvailable(bool)),
cutAct, SLOT(setEnabled(bool)));
connect(textEdit, SIGNAL(copyAvailable(bool)),
copyAct, SLOT(setEnabled(bool)));
connect(textEdit, &QPlainTextEdit::copyAvailable, cutAct, &QAction::setEnabled);
connect(textEdit, &QPlainTextEdit::copyAvailable, copyAct, &QAction::setEnabled);
}
//! [24]
//! [25] //! [26]
void MainWindow::createMenus()
//! [25] //! [27]
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAct);
//! [28]
fileMenu->addAction(openAct);
//! [28]
fileMenu->addAction(saveAct);
//! [26]
fileMenu->addAction(saveAsAct);
fileMenu->addSeparator();
fileMenu->addAction(exitAct);
editMenu = menuBar()->addMenu(tr("&Edit"));
editMenu->addAction(cutAct);
editMenu->addAction(copyAct);
editMenu->addAction(pasteAct);
menuBar()->addSeparator();
helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(aboutAct);
helpMenu->addAction(aboutQtAct);
}
//! [27]
//! [29] //! [30]
void MainWindow::createToolBars()
{
fileToolBar = addToolBar(tr("File"));
fileToolBar->addAction(newAct);
//! [29] //! [31]
fileToolBar->addAction(openAct);
//! [31]
fileToolBar->addAction(saveAct);
editToolBar = addToolBar(tr("Edit"));
editToolBar->addAction(cutAct);
editToolBar->addAction(copyAct);
editToolBar->addAction(pasteAct);
}
//! [30]
//! [32]
void MainWindow::createStatusBar()
//! [32] //! [33]
@ -281,11 +257,16 @@ void MainWindow::createStatusBar()
void MainWindow::readSettings()
//! [34] //! [36]
{
QSettings settings("QtProject", "Application Example");
QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
QSize size = settings.value("size", QSize(400, 400)).toSize();
resize(size);
move(pos);
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
const QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray();
if (geometry.isEmpty()) {
const QRect availableGeometry = QApplication::desktop()->availableGeometry(this);
resize(availableGeometry.width() / 3, availableGeometry.height() / 2);
move((availableGeometry.width() - width()) / 2,
(availableGeometry.height() - height()) / 2);
} else {
restoreGeometry(geometry);
}
}
//! [35] //! [36]
@ -293,9 +274,8 @@ void MainWindow::readSettings()
void MainWindow::writeSettings()
//! [37] //! [39]
{
QSettings settings("QtProject", "Application Example");
settings.setValue("pos", pos());
settings.setValue("size", size());
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
settings.setValue("geometry", saveGeometry());
}
//! [38] //! [39]
@ -303,16 +283,20 @@ void MainWindow::writeSettings()
bool MainWindow::maybeSave()
//! [40] //! [41]
{
if (textEdit->document()->isModified()) {
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(this, tr("Application"),
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 save();
else if (ret == QMessageBox::Cancel)
return false;
if (!textEdit->document()->isModified())
return true;
const QMessageBox::StandardButton ret
= QMessageBox::warning(this, tr("Application"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
switch (ret) {
case QMessageBox::Save:
return save();
case QMessageBox::Cancel:
return false;
default:
break;
}
return true;
}
@ -326,8 +310,7 @@ void MainWindow::loadFile(const QString &fileName)
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Application"),
tr("Cannot read file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
.arg(QDir::toNativeSeparators(fileName), file.errorString()));
return;
}
@ -353,8 +336,8 @@ bool MainWindow::saveFile(const QString &fileName)
if (!file.open(QFile::WriteOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Application"),
tr("Cannot write file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
.arg(QDir::toNativeSeparators(fileName),
file.errorString()));
return false;
}

View File

@ -57,6 +57,8 @@ class MainWindow : public QMainWindow
public:
MainWindow();
void loadFile(const QString &fileName);
protected:
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
@ -70,35 +72,16 @@ private slots:
private:
void createActions();
void createMenus();
void createToolBars();
void createStatusBar();
void readSettings();
void writeSettings();
bool maybeSave();
void loadFile(const QString &fileName);
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
QString strippedName(const QString &fullFileName);
QPlainTextEdit *textEdit;
QString curFile;
QMenu *fileMenu;
QMenu *editMenu;
QMenu *helpMenu;
QToolBar *fileToolBar;
QToolBar *editToolBar;
QAction *newAct;
QAction *openAct;
QAction *saveAct;
QAction *saveAsAct;
QAction *exitAct;
QAction *cutAct;
QAction *copyAct;
QAction *pasteAct;
QAction *aboutAct;
QAction *aboutQtAct;
};
//! [0]

View File

@ -228,8 +228,9 @@ void QActionPrivate::setShortcutEnabled(bool enable, QShortcutMap &map)
\snippet mainwindows/application/mainwindow.cpp 19
\codeline
\snippet mainwindows/application/mainwindow.cpp 28
\snippet mainwindows/application/mainwindow.cpp 31
\code
fileMenu->addAction(openAct);
\endcode
We recommend that actions are created as children of the window
they are used in. In most cases actions will be children of

View File

@ -263,7 +263,14 @@ void QMainWindowPrivate::init()
An example of how to create menus follows:
\snippet mainwindows/application/mainwindow.cpp 26
\code
void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAct);
fileMenu->addAction(openAct);
fileMenu->addAction(saveAct);
\endcode
The \c createPopupMenu() function creates popup menus when the
main window receives context menu events. The default
@ -290,7 +297,12 @@ void QMainWindowPrivate::init()
An example of toolbar creation follows:
\snippet mainwindows/application/mainwindow.cpp 29
\code
void MainWindow::createToolBars()
{
fileToolBar = addToolBar(tr("File"));
fileToolBar->addAction(newAct);
\endcode
\section2 Creating Dock Widgets