• No results found

In this section, we will explain how to use dialogs in Qt—how to create and initialize them, run them, and respond to choices made by the user interacting with them. We will make use of the Find, Go-to-Cell, and Sort dialogs that we created in Chapter 2. We will also create a simple About box.

We will begin with the Find dialog. Since we want the user to be able to switch between the main Spreadsheet window and the Find dialog at will, the Find dialog must be modeless. Amodelesswindow is one that runs independently of any other windows in the application.

When modeless dialogs are created, they normally have their signals connect- ed to slots that respond to the user’s interactions.

void MainWindow::find() {

if (!findDialog) {

findDialog = new FindDialog(this);

connect(findDialog, SIGNAL(findNext(const QString &, bool)), spreadsheet, SLOT(findNext(const QString &, bool))); connect(findDialog, SIGNAL(findPrev(const QString &, bool)), spreadsheet, SLOT(findPrev(const QString &, bool))); }

findDialog->show(); findDialog->raise();

findDialog->setActiveWindow(); }

The Find dialog is a window that enables the user to search for text in the spreadsheet. Thefind()slot is called when the user clicksEdit|Findto pop up the Find dialog. At that point, several scenarios are possible:

• This is the first time the user has invoked the Find dialog. • The Find dialog was invoked before, but the user closed it. • The Find dialog was invoked before and is still visible.

If the Find dialog doesn’t already exist, we create it and connect itsfindNext()

and findPrev() signals toSpreadsheet’s matching slots. We could also have created the dialog in the MainWindow constructor, but delaying its creation

Using Dialogs 59 makes startup faster. Also, if the dialog is never used, it is never created, saving both time and memory.

Then we callshow(),raise(), andsetActiveWindow()to ensure that the window is visible, on top of the others, and active. A call toshow()alone is sufficient to make a hidden window visible, but the Find dialog may be invoked when its window is already visible, in which caseshow()does nothing. Since we must make the dialog’s window visible, active, and on top regardless of its previous state, we must use theraise() andsetActiveWindow() calls. An alternative would have been to write

if (findDialog->isHidden()) { findDialog->show(); } else { findDialog->raise(); findDialog->setActiveWindow(); }

the programming equivalent of driving along at 90 in a 100 km/h zone. We will now look at the Go-to-Cell dialog. We want the user to pop it up, use it, and close it without being able to switch from the Go-to-Cell dialog to any other window in the application. This means that the Go-to-Cell dialog must be modal. Amodalwindow is a window that pops up when invoked and blocks the application, preventing any other processing or interactions from taking place until the window is closed. With the exception of the Find dialog, all the dialogs we have used so far have been modal.

A dialog is modeless if it’s invoked using show() (unless we callsetModal()

beforehand to make it modal); it is modal if it’s invoked usingexec(). When

we invoke modal dialogs usingexec(), we typically don’t need to set up any

signal–slot connections. void MainWindow::goToCell() { GoToCellDialog dialog(this); if (dialog.exec()) { QString str = dialog.lineEdit->text(); spreadsheet->setCurrentCell(str.mid(1).toInt() - 1, str[0].upper().unicode() - ’A’); } }

TheQDialog::exec()function returnstrueif the dialog is accepted,falseoth- erwise. (Recall that when we created the Go-to-Cell dialog usingQt Designer

in Chapter 2, we connectedOKtoaccept()andCanceltoreject().) If the user choosesOK, we set the current cell to the value in the line editor; if the user choosesCancel,exec()returnsfalseand we do nothing.

TheQTable::setCurrentCell()function expects two arguments: a row index and a column index. In the Spreadsheet application, cell A1 is cell (0, 0) and cell B27 is cell (26, 1). To obtain the row index from theQStringreturned by QLabel::text(), we extract the row number usingQString::mid() (which

to an using , and subtract 1 to make it 0-based. For the column number, we subtract the numeric value of ‘A’ from the numeric value of the string’s upper-cased first character.

Unlike Find, the Go-to-Cell dialog is created on the stack. This is a common programming pattern for modal dialogs, just as it is for context menus, since we don’t need the dialog after we have used it.

We will now turn to the Sort dialog. The Sort dialog is a modal dialog that allows the user to sort the currently selected area by the columns they specify. Figure 3.14 shows an example of sorting, with column B as the primary sort key and column A as the secondary sort key (both ascending).

(a) Before sort (b) After sort Figure 3.14. Sorting the spreadsheet’s selected area void MainWindow::sort()

{

SortDialog dialog(this);

QTableSelection sel = spreadsheet->selection();

dialog.setColumnRange(’A’ + sel.leftCol(), ’A’ + sel.rightCol());

if (dialog.exec()) { SpreadsheetCompare compare; compare.keys[0] = dialog.primaryColumnCombo->currentItem(); compare.keys[1] = dialog.secondaryColumnCombo->currentItem() - 1; compare.keys[2] = dialog.tertiaryColumnCombo->currentItem() - 1; compare.ascending[0] = (dialog.primaryOrderCombo->currentItem() == 0); compare.ascending[1] = (dialog.secondaryOrderCombo->currentItem() == 0); compare.ascending[2] = (dialog.tertiaryOrderCombo->currentItem() == 0); spreadsheet->sort(compare); } }

Using Dialogs 61 The code insort()follows a similar pattern to that used forgoToCell():

• We create the dialog on the stack and initialize it. • We pop up the dialog usingexec().

• If the user clicksOK, we extract the values entered by the user from the dialog’s widgets and make use of them.

Thecompare object stores the primary, secondary, and tertiary sort keys and

sort orders. (We will see the definition of theSpreadsheetCompareclass in the

next chapter.) The object is used bySpreadsheet::sort()to compare two rows.

Thekeysarray stores the column numbers of the keys. For example, if the

selection extends from C2 to E5, column C has position 0. Theascendingarray

stores the order associated with each key as abool.QComboBox::currentItem()

returns the index of the currently selected item, starting at 0. For the sec- ondary and tertiary keys, we subtract one from the current item to account for the “None” item.

Thesort()dialog does the job, but it is very fragile. It takes for granted that

the Sort dialog is implemented in a certain way, with comboboxes and “None” items. This means that if we redesign the Sort dialog, we may also need to rewrite this code. While this approach is adequate for a dialog that is only called from one place, it opens the door to maintenance nightmares if the dialog is used in several places.

A more robust approach is to make theSortDialog class smarter by having

it create aSpreadsheetCompareobject itself, which can then be accessed by its

caller. This simplifiesMainWindow::sort()significantly: void MainWindow::sort()

{

SortDialog dialog(this);

QTableSelection sel = spreadsheet->selection();

dialog.setColumnRange(’A’ + sel.leftCol(), ’A’ + sel.rightCol()); if (dialog.exec())

spreadsheet->performSort(dialog.comparisonObject()); }

This approach leads to loosely coupled components and is almost always the right choice for dialogs that will be called from more than one place.

A more radical approach is to pass a pointer to theSpreadsheet object when initializing theSortDialogobject and to allow the dialog to operate directly on theSpreadsheet. This makes theSortDialogmuch less general, since it will only work on a certain type of widget, but it simplifies the code ever further by eliminating theSortDialog::setColumnRange()function. TheMainWindow:: sort()function then becomes

void MainWindow::sort() {

SortDialog dialog(this);

This approach mirrors the first: Instead of the caller needing intimate knowl- edge of the dialog, the dialog needs intimate knowledge of the data structures supplied by the caller. This approach may be useful where the dialog needs to apply changes live. But just as the caller code is fragile using the first ap- proach, this third approach breaks if the data structures change.

Some developers choose just one approach to using dialogs and stick with that. This has the benefit of familiarity and simplicity since all their dialog usages follow the same pattern, but it also misses the benefits of the approaches that are not used. The decision on which approach to use should be made on a per-dialog basis.

We will round off this section with a simple About box. We could create a cus- tom dialog like the Find or Go-to-Cell dialogs to present the “about” informa- tion, but since most About boxes are highly stylized, Qt provides a simpler so- lution.

void MainWindow::about() {

QMessageBox::about(this, tr("About Spreadsheet"), tr("<h2>Spreadsheet 1.0</h2>"

"<p>Copyright &copy; 2003 Software Inc." "<p>Spreadsheet is a small application that " "demonstrates <b>QAction</b>, <b>QMainWindow</b>, " "<b>QMenuBar</b>, <b>QStatusBar</b>, "

"<b>QToolBar</b>, and many other Qt classes.")); }

The About box is obtained by calling QMessageBox::about(), a static conve- nience function. The function is very similar toQMessageBox::warning(), except that it uses the parent window’s icon instead of the standard “warning” icon.

Figure 3.15. About Spreadsheet

So far we have used several convenience static functions from bothQMessageBox

andQFileDialog. These functions create a dialog, initialize it, and callexec()

on it. It is also possible, although less convenient, to create aQMessageBox or aQFileDialogwidget like any other widget and explicitly callexec(), or even

Storing Settings 63

Storing Settings

In theMainWindow constructor, we calledreadSettings() to load the applica- tion’s stored settings. Similarly, incloseEvent(), we calledwriteSettings()to save the settings. These two functions are the lastMainWindowmember func- tions that need to be implemented.

The arrangement we opted for inMainWindow, with all the QSettings-related code in readSettings() and writeSettings(), is just one of many possible approaches. AQSettingsobject can be created to query or modify some setting at any time during the execution of the application and from anywhere in the code. void MainWindow::writeSettings() { QSettings settings; settings.setPath("software-inc.com", "Spreadsheet"); settings.beginGroup("/Spreadsheet"); settings.writeEntry("/geometry/x", x()); settings.writeEntry("/geometry/y", y()); settings.writeEntry("/geometry/width", width()); settings.writeEntry("/geometry/height", height()); settings.writeEntry("/recentFiles", recentFiles); settings.writeEntry("/showGrid", showGridAct->isOn()); settings.writeEntry("/autoRecalc", showGridAct->isOn()); settings.endGroup(); }

The writeSettings() function saves the main window’s geometry (position and size), the list of recently opened files, and theShow GridandAuto-recalculate

options.

QSettingsstores the application’s settings in platform-specific locations. On Windows, it uses the system registry; on Unix, it stores the data in text files; on Mac OS X, it uses the Carbon preferences API. ThesetPath()call provides

QSettingswith the organization’s name (as an Internet domain name) and the product’s name. This information is used in a platform-specific way to find a location for the settings.

QSettingsstores settings askeyvaluepairs. Thekeyis similar to a file system path and should always start with the name of the application. For example,

/Spreadsheet/geometry/x and /Spreadsheet/showGrid are valid keys. (The

beginGroup() call saves us from writing/Spreadsheet in front of every key.) Thevaluecan be anint, abool, adouble, aQString, or aQStringList.

void MainWindow::readSettings() { QSettings settings; settings.setPath("software-inc.com", "Spreadsheet"); settings.beginGroup("/Spreadsheet"); int x = settings.readNumEntry("/geometry/x", 200); int y = settings.readNumEntry("/geometry/y", 200);

move(x, y); resize(w, h); recentFiles = settings.readListEntry("/recentFiles"); updateRecentFileItems(); showGridAct->setOn( settings.readBoolEntry("/showGrid", true)); autoRecalcAct->setOn( settings.readBoolEntry("/autoRecalc", true)); settings.endGroup(); }

ThereadSettings()function loads the settings that were saved bywriteSet- tings(). The second argument to the “read” functions specifies a default value, in case there are no settings available. The default values are used the first time the application is run.

We have now completed the Spreadsheet’sMainWindowimplementation. In the following sections, we will discuss how the Spreadsheet application can be modified to handle multiple documents and how to implement a splash screen. We will complete its functionality in the next chapter.