|  | Home · Overviews · Examples

# Application Example

The Application example shows how to implement a standard GUI application with menus, toolbars, and a status bar. The example itself is a simple text editor program built around `QTextEdit`.

Nearly all of the code for the Application example is in the `MainWindow` class, which inherits `QMainWindow`. `QMainWindow` provides the framework for windows that have menus, toolbars, dock windows, and a status bar. The application provides File, Edit, and Help entries in the menu bar, with the following popup menus:

The status bar at the bottom of the main window shows a description of the menu item or toolbar button under the cursor.

To keep the example simple, recently opened files aren't shown in the File menu, even though this feature is desired in 90% of applications. The Recent Files example shows how to implement this. Furthermore, this example can only load one file at a time. The SDI and MDI examples shows how to lift these restrictions.

## Importing the Qt Classes

        import com.trolltech.qt.QVariant;
        import com.trolltech.qt.core.*;
        import com.trolltech.qt.gui.*;

We start by importing all the classes in the `QtCore` and `QtGui` modules. This saves us from the trouble of having to import every class we use individually.

## Application Class Implementation

The entire example is implemented in a single subclass of `QMainWindow`:

        public class Application extends QMainWindow {

            private String curFile;
            private QTextEdit textEdit;
            private QMenu fileMenu;
            private QMenu editMenu;
            private QMenu helpMenu;

            private QToolBar fileToolBar;
            private QToolBar editToolBar;

            private QAction newAct;
            private QAction openAct;
            private QAction saveAct;
            private QAction saveAsAct;
            private QAction exitAct;
            private QAction cutAct;
            private QAction copyAct;
            private QAction pasteAct;
            private QAction aboutAct;
            private QAction aboutQtJambiAct;
            private QAction aboutQtAct;

            private String rsrcPath = "classpath:com/trolltech/images";

We declare various variables for widgets, menus, toolbars, and actions that will be explained later.

In the constructor, we start by creating a menu bar and setting it as the menu bar for the main window:

            public Application()
            {
                QMenuBar menuBar = new QMenuBar();
                setMenuBar(menuBar);

We also create a `QTextEdit` widget, and call setCentralWidget() to make it occupy the central area of the main window, between the toolbars and the status bar.

Actions are usually reused in menus and toolbars to provide a consistent user interface. The actions used in the main window are set up before any of the standard menus, toolbars or other window elements.

                try {
                    createActions();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                createMenus();
                createToolBars();
                createStatusBar();

We call `createActions()`, `createMenus()`, `createToolBars()`, and `createStatusBar()`, four private methods that set up the user interface.

                textEdit.document().contentsChanged.connect(this, "documentWasModified()");

                setCurrentFile("");
            }

We establish a signal-slot connection between the `QTextEdit`'s document object and our `documentWasModified()` slot. Whenever the user modifies the text in the `QTextEdit`, we want to update the title bar to show that the file was modified.

We reimplement QWidget::closeEvent() to detect when the user attempts to close the window, and warn the user about unsaved changes:

            public void closeEvent(QCloseEvent event)
            {
                if (maybeSave()) {
                    writeSettings();
                    event.accept();
                } else {
                    event.ignore();
                }
            }

When the user attempts to close the window, we call the private method `maybeSave()` to give the user the possibility to save pending changes. The method returns true if the user wants the application to close; otherwise, it returns false. In the first case, we save the user's preferences to disk and accept the close event; in the second case, we ignore the close event, meaning that the application will stay up and running as if nothing happened.

            public void newFile()
            {
                if (maybeSave()) {
                    textEdit.clear();
                    setCurrentFile("");
                }
            }

The `newFile()` slot is invoked when the user selects File|New from the menu. We call `maybeSave()` to save any pending changes and if the user accepts to go on, we clear the `QTextEdit` and call the private method `setCurrentFile()` to update the window title and clear the windowModified flag.

            public void open()
            {
                if (maybeSave()) {
                    String fileName = QFileDialog.getOpenFileName(this);
                    if (fileName.length() != 0)
                        loadFile(fileName);
                }
            }

The `open()` slot is invoked when the user clicks File|Open. We pop up a `QFileDialog` asking the user to choose a file. If the user chooses a file (i.e., `fileName` is not an empty string), we call the private method `loadFile()` to actually load the file.

            public boolean save()
            {
                if (curFile.length() == 0) {
                    return saveAs();
                } else {
                    return saveFile(curFile);
                }
            }

The `save()` slot is invoked when the user clicks File|Save. If the user hasn't provided a name for the file yet, we call `saveAs()`; otherwise, we call the private method `saveFile()` to actually save the file.

            public boolean saveAs()
            {
                String fileName = QFileDialog.getSaveFileName(this);
                if (fileName.length() == 0)
                    return false;

                return saveFile(fileName);
            }

In `saveAs()`, we start by popping up a `QFileDialog` asking the user to provide a name. If the user clicks Cancel, the returned file name is empty, and we do nothing.

            public void about()
            {
                QMessageBox.about(this,
                                 tr("About Application"),
                                 tr("The <b>Application</b> example demonstrates how to " +
                                    "write modern GUI applications using Qt, with a menu bar, " +
                                    "toolbars, and a status bar."));
            }

The application's About box is done using one statement, using the about() static method and relying on its support for an HTML subset.

The tr() call around the literal string marks the string for translation. It is a good habit to call tr() on all user-visible strings, in case you later decide to translate your application to other languages. The Internationalization with Qt overview convers tr() in more detail.

            public void documentWasModified()
            {
                setWindowModified(textEdit.document().isModified());
            }

The `documentWasModified()` slot is invoked each time the text in the `QTextEdit` changes because of user edits. We call QWidget.setWindowModified() to make the title bar show that the file was modified. How this is done varies on each platform.

            private void createActions()
            {
                newAct = new QAction(new QIcon(rsrcPath + "/new.png"), tr("&New"), this);
                newAct.setShortcut(new QKeySequence(tr("Ctrl+N")));
                newAct.setStatusTip(tr("Create a new file"));
                newAct.triggered.connect(this, "newFile()");

                openAct = new QAction(new QIcon(rsrcPath + "/open.png"), tr("&Open..."), this);
                openAct.setShortcut(tr("Ctrl+O"));
                openAct.setStatusTip(tr("Open an existing file"));
                openAct.triggered.connect(this, "open()");
            ...
                aboutQtAct = new QAction(tr("About Q&t"), this);
                aboutQtAct.setStatusTip(tr("Show the Qt library's About box"));
                aboutQtAct.triggered.connect(QApplication.instance(), "aboutQt()");

The `createActions()` private method, which is called from the `MainWindow` constructor, creates `QAction`s. The code is very repetitive, so we show only the actions corresponding to File|New, File|Open, and Help|About Qt.

A `QAction` is an object that represents one user action, such as saving a file or invoking a dialog. An action can be put in a `QMenu` or a `QToolBar`, or both, or in any other widget that reimplements QWidget::actionEvent().

An action has a text that is shown in the menu, an icon, a shortcut key, a tooltip, a status tip (shown in the status bar), a "What's This?" text, and more. It emits a QAction.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.

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 with `:`. 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 `application.qrc` file that's part of the project.

                cutAct.setEnabled(false);
                copyAct.setEnabled(false);
                textEdit.copyAvailable.connect(cutAct, "setEnabled(boolean)");
                textEdit.copyAvailable.connect(copyAct, "setEnabled(boolean)");
            }

The Edit|Cut and Edit|Copy actions must be available only when the `QTextEdit` contains selected text. We disable them by default and connect the QTextEdit.copyAvailable() signal to the action's setEnabled() slot, ensuring that the actions are disabled when the text editor has no selection.

            private void createMenus()
            {
                fileMenu = menuBar().addMenu(tr("&File"));
                fileMenu.addAction(newAct);
                fileMenu.addAction(openAct);
                fileMenu.addAction(saveAct);
                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.addSeparator();
                helpMenu.addAction(aboutQtJambiAct);
                helpMenu.addAction(aboutQtAct);
            }

Creating actions isn't sufficient to make them available to the user; we must also add them to the menu system. This is what `createMenus()` does. We create a File, an Edit, and a 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 method, the `QMenuBar` is created.

Just before we create the Help menu, we call QMenuBar.addSeparator(). This has no effect for most widget styles (e.g., Windows and Mac OS X styles), but for Motif-based styles this makes sure that Help is pushed to the right side of the menu bar. Try running the application with various styles and see the results:

        application -style=windows
        application -style=motif
        application -style=cde

Let's now review the toolbars:

            private void createToolBars()
            {
                fileToolBar = addToolBar(tr("File"));
                fileToolBar.addAction(newAct);
                fileToolBar.addAction(openAct);
                fileToolBar.addAction(saveAct);

                editToolBar = addToolBar(tr("Edit"));
                editToolBar.addAction(cutAct);
                editToolBar.addAction(copyAct);
                editToolBar.addAction(pasteAct);
            }

Creating toolbars is very similar to creating menus. The same actions that we put in the menus can be reused in the toolbars.

            private void createStatusBar()
            {
                statusBar().showMessage(tr("Ready"));
            }

QMainWindow::statusBar() returns a pointer to the main window's `QStatusBar` widget. Like with `QMainWindow::menuBar`, the widget is automatically created the first time the method is called.

            private boolean maybeSave()
            {
                if (textEdit.document().isModified()) {
                    QMessageBox.StandardButton ret = QMessageBox.warning(this, tr("Application"),
                                                                         tr("The document has been modified.\n" +
                                                                            "Save your changes?"),
                                                                         new QMessageBox.StandardButtons(QMessageBox.StandardButton.Ok,
                                                                                                         QMessageBox.StandardButton.Discard,
                                                                                                         QMessageBox.StandardButton.Cancel));
                    if (ret == QMessageBox.StandardButton.Ok) {
                        return save();
                    } else if (ret == QMessageBox.StandardButton.Cancel) {
                        return false;
                    }
                }
                return true;
            }

The `maybeSave()` method is called to save pending changes. If there are pending changes, it pops up a `QMessageBox` giving the user to save the document. The options are QMessageBox.Yes, QMessageBox.No, and QMessageBox.Cancel. The Yes button is made the default button (the button that is invoked when the user presses Return) using the QMessageBox.Default flag; the Cancel button is made the escape button (the button that is invoked when the user presses Esc) using the QMessageBox.Escape flag.

The `maybeSave()` method returns `true` in all cases, except when the user clicks Cancel. The caller must check the return value and stop whatever it was doing if the return value is `false`.

            public void loadFile(String fileName)
            {
                QFile file = new QFile(fileName);
                if (!file.open(new QFile.OpenMode(QFile.OpenModeFlag.ReadOnly, QFile.OpenModeFlag.Text))) {
                    QMessageBox.warning(this, tr("Application"), String.format(tr("Cannot read file %1$s:\n%2$s."), fileName, file.errorString()));
                    return;
                }

                QTextStream in = new QTextStream(file);
                QApplication.setOverrideCursor(new QCursor(Qt.CursorShape.WaitCursor));
                textEdit.setPlainText(in.readAll());
                QApplication.restoreOverrideCursor();

                setCurrentFile(fileName);
                statusBar().showMessage(tr("File loaded"), 2000);
            }

In `loadFile()`, we use `QFile` and `QTextStream` to read in the data. The `QFile` object provides access to the bytes stored in a file.

We start by opening the file in read-only mode. The QFile::Text flag indicates that the file is a text file, not a binary file. On Unix and Mac OS X, this makes no difference, but on Windows, it ensures that the "\r\n" end-of-line sequence is converted to "\n" when reading.

If we successfully opened the file, we use a `QTextStream` object to read in the data. `QTextStream` automatically converts the 8-bit data into a Unicode `QString` and supports various encodings. If no encoding is specified, `QTextStream` assumes the file is written using the system's default 8-bit encoding (for example, Latin-1; see QTextCodec.codecForLocale() for details).

Since the call to QTextStream.readAll() might take some time, we set the cursor to be Qt.WaitCursor for the entire application while it goes on.

At the end, we call the private `setCurrentFile()` method, which we'll cover in a moment, and we display the string "File loaded" in the status bar for 2 seconds (2000 milliseconds).

            public boolean saveFile(String fileName)
            {
                QFile file = new QFile(fileName);
                if (!file.open(new QFile.OpenMode(QFile.OpenModeFlag.WriteOnly, QFile.OpenModeFlag.Text))) {
                    QMessageBox.warning(this, tr("Application"), String.format(tr("Cannot write file %1$s:\n%2$s."), fileName, file.errorString()));
                    return false;
                }

                QTextStream out = new QTextStream(file);
                QApplication.setOverrideCursor(new QCursor(Qt.CursorShape.WaitCursor));
                out.writeString(textEdit.toPlainText());
                QApplication.restoreOverrideCursor();

                setCurrentFile(fileName);
                statusBar().showMessage(tr("File saved"), 2000);
                file.close();
                return true;
            }

Saving a file is very similar to loading one. Here, the QFile.Text flag ensures that on Windows, "\n" is converted into "\r\n" to conform to the Windows convension.

            public void setCurrentFile(String fileName)
            {
                curFile = fileName;
                textEdit.document().setModified(false);
                setWindowModified(false);

                String shownName;
                if (curFile.length() == 0)
                    shownName = "untitled.txt";
                else
                    shownName = strippedName(curFile);

                setWindowTitle(String.format(tr("%1$s[*] - %2$s"), shownName, tr("Application")));
            }

The `setCurrentFile()` method is called to reset the state of a few variables when a file is loaded or saved, or when the user starts editing a new file (in which case `fileName` is empty). We update the `curFile` variable, clear the QTextDocument.modified flag and the associated `QWidget.windowModified` flag, and update the window title to contain the new file name (or `untitled.txt`).

The `strippedName()` method call around `curFile` in the setWindowTitle() call shortens the file name to exclude the path. Here's the method:

            private static String strippedName(String fullFileName)
            {
                return new QFileInfo(fullFileName).fileName();
            }

## The main() Function

The `main()` function for this application is typical of applications that contain one main window:

            public static void main(String[] args) {
                QApplication.initialize(args);

                Application application = new Application();
                application.show();

                QApplication.exec();
            }