- Subclassing QMainWindow
- Creating Menus and Toolbars
- Implementing the File Menu
- Setting Up the Status Bar
- Using Dialogs
- Storing Settings
- Multiple Documents
- Splash Screens
Creating Menus and Toolbars
Most modern GUI applications provide both menus and toolbars, and typically they contain more or less the same commands. The menus enable users to explore the application and learn how to do new things, while the toolbars provide quick access to frequently used functionality.
Qt simplifies the programming of menus and toolbars through its "action" concept. An action is an item that can be added to a menu, a toolbar, or both. Creating menus and toolbars in Qt involves these steps:
-
Create the actions.
-
Add the actions to menus.
-
Add the actions to toolbars.
In the Spreadsheet application, actions are created in createActions():
void MainWindow::createActions() { newAct = new QAction(tr("&New"), tr("Ctrl+N"), this); newAct->setIconSet(QPixmap::fromMimeSource("new.png")); newAct->setStatusTip(tr("Create a new spreadsheet file")); connect(newAct, SIGNAL(activated()), this, SLOT(newFile()));
The New action has a shortcut key (New), an accelerator (Ctrl+N), a parent (the main window), an icon (new.png), and a status tip. We connect the action's activated() signal to the main window's private newFile() slot, which we'll implement in the next section. Without the connection, nothing would happen when the user chooses the File|New menu item or clicks the New toolbar button.
The other actions for the File, Edit, and Tools menus are very similar to the New action.
Figure 3.3. The Spreadsheet application's menus
The Show Grid action in the Options menu is different:
showGridAct = new QAction(tr("&Show Grid"), 0, this); showGridAct->setToggleAction(true); showGridAct->setOn(spreadsheet->showGrid()); showGridAct->setStatusTip(tr("Show or hide the spreadsheet's " "grid")); connect(showGridAct, SIGNAL(toggled(bool)), spreadsheet, SLOT(setShowGrid(bool)));
Show Grid is a toggle action. It is rendered with a checkmark in the menu and implemented as a toggle button in the toolbar. When the action is turned on, the Spreadsheet component displays a grid. We initialize the action with the default for the Spreadsheet component, so that they are synchronized at start up. Then we connect the Show Grid action's toggled(bool) signal to the Spreadsheet component's setShowGrid(bool) slot, which it inherits from QTable. Once this action is added to a menu or toolbar, the user can toggle the grid on and off.
The Show Grid and Auto-recalculate actions are independent toggle actions. QAction also provides for mutually exclusive actions through its QActionGroup subclass.
Figure 3.4. About Qt
aboutQtAct = new QAction(tr("About &Qt"), 0, this); aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); connect(aboutQtAct, SIGNAL(activated()), qApp, SLOT(aboutQt())); }
For About Qt, we use the QApplication object's aboutQt() slot, accessible through the qApp global variable.
Now that we have created the actions, we can move on to building a menu system through which the actions can be invoked:
void MainWindow::createMenus() { fileMenu = new QPopupMenu(this); newAct->addTo(fileMenu); openAct->addTo(fileMenu); saveAct->addTo(fileMenu); saveAsAct->addTo(fileMenu); fileMenu->insertSeparator(); exitAct->addTo(fileMenu); for (int i = 0; i < MaxRecentFiles; ++i) recentFileIds[i] = -1;
In Qt, all menus are instances of QPopupMenu. We create the File menu and then add the New, Open, Save, Save As, and Exit actions to it. We insert a separator to visually group closely related items together. The for loop takes care of initializing the recentFilesIds array. We will use recentFilesIds in the next section when implementing the File menu slots.
editMenu = new QPopupMenu(this); cutAct->addTo(editMenu); copyAct->addTo(editMenu); pasteAct->addTo(editMenu); deleteAct->addTo(editMenu); selectSubMenu = new QPopupMenu(this); selectRowAct->addTo(selectSubMenu); selectColumnAct->addTo(selectSubMenu); selectAllAct->addTo(selectSubMenu); editMenu->insertItem(tr("&Select"), selectSubMenu); editMenu->insertSeparator(); findAct->addTo(editMenu); goToCellAct->addTo(editMenu);
The Edit menu includes a submenu. The submenu, like the menu it belongs to, is a QPopupMenu. We simply create the submenu with this as parent and insert it into the Edit menu where we want it to appear.
toolsMenu = new QPopupMenu(this); recalculateAct->addTo(toolsMenu); sortAct->addTo(toolsMenu); optionsMenu = new QPopupMenu(this); showGridAct->addTo(optionsMenu); autoRecalcAct->addTo(optionsMenu); helpMenu = new QPopupMenu(this); aboutAct->addTo(helpMenu); aboutQtAct->addTo(helpMenu); menuBar()->insertItem(tr("&File"), fileMenu); menuBar()->insertItem(tr("&Edit"), editMenu); menuBar()->insertItem(tr("&Tools"), toolsMenu); menuBar()->insertItem(tr("&Options"), optionsMenu); menuBar()->insertSeparator(); menuBar()->insertItem(tr("&Help"), helpMenu); }
We create the Tools, Options, and Help menus in a similar fashion, and we insert all the menus into the menu bar. The QMainWindow::menuBar() function returns a pointer to a QMenuBar. (The menu bar is created the first time menuBar() is called.) We insert a separator between the Options and Help menu. In Motif and similar styles, the separator pushes the Help menu to the right; in other styles, the separator is ignored.
Figure 3.5. Menu bar in Motif and Windows styles
Creating toolbars is very similar to creating menus:
void MainWindow::createToolBars() { fileToolBar = new QToolBar(tr("File"), this); newAct->addTo(fileToolBar); openAct->addTo(fileToolBar); saveAct->addTo(fileToolBar); editToolBar = new QToolBar(tr("Edit"), this); cutAct->addTo(editToolBar); copyAct->addTo(editToolBar); pasteAct->addTo(editToolBar); editToolBar->addSeparator(); findAct->addTo(editToolBar); goToCellAct->addTo(editToolBar); }
We create a File toolbar and an Edit toolbar. Just like a popup menu, a toolbar can have separators.
Figure 3.6. The Spreadsheet application's toolbars
Now that we have finished the menus and toolbars, we will add a context menu to complete the interface:
void MainWindow::contextMenuEvent(QContextMenuEvent *event) { QPopupMenu contextMenu(this); cutAct->addTo(&contextMenu); copyAct->addTo(&contextMenu); pasteAct->addTo(&contextMenu); contextMenu.exec(event->globalPos()); }
When the user clicks the right-mouse button (or presses the Menu key on some keyboards), a "context menu" event is sent to the widget. By reimplementing the QWidget::contextMenuEvent() function, we can respond to this event and pop up a context menu at the current mouse pointer position.
Figure 3.7. The Spreadsheet application's context menu
Just like signals and slots, events are a fundamental aspect of Qt programming. Events are generated by Qt's kernel to report mouse clicks, key presses, resize requests, and similar occurrences. They can be handled by reimplementing virtual functions, as we are doing here.
We have chosen to implement the context menu in MainWindow because that's where we store the actions, but it would also have been possible to implement it in Spreadsheet. When the user right-clicks the Spreadsheet widget, Qt sends a context menu event to that widget first. If Spreadsheet reimplements contextMenuEvent() and handles the event, the event stops there; otherwise, it is sent to the parent (the MainWindow). Events are fully explained in Chapter 7.
The context menu event handler differs from all the code seen so far because it creates a widget (a QPopupMenu) as a variable on the stack. We could just as easily have used new and delete:
QPopupMenu *contextMenu = new QPopupMenu(this); cutAct->addTo(contextMenu); copyAct->addTo(contextMenu); pasteAct->addTo(contextMenu); contextMenu->exec(event->globalPos()); delete contextMenu;
Another noteworthy aspect of the code is the exec() call. QPopupMenu::exec() shows the popup menu at a given screen position and waits until the user chooses an option (or dismisses the popup menu) before it returns. At this point, the QPopupMenu object has achieved its purpose, so we can destroy it. If the QPopupMenu object is located on the stack, it is destroyed automatically at the end of the function; otherwise, we must call delete.
We have now completed the user interface part of the menus and toolbars. We still have not implemented all of the slots or written code to handle the File menu's recently opened files. The next two sections will address these issues.