Anda di halaman 1dari 6

The "[*]" marker in the window title is a place marker for where we want the asterisk to appear when

the file has unsaved changes


on platforms other than Mac OS X. On Mac OS X, unsaved documents have a dot in their window's close button. We covered this
place marker in Chapter 3 (p. 61).
In addition to creating new files, users often want to open existing files, picked from either a file dialog or a list such as a recently
opened files list. Two static functions are provided to support these uses: open() for choosing a file name from the file system,
and openFile() to create an Editor and to read into it the contents of a specified file.
Editor *Editor::open(QWidget *parent)
{
QString fileName =
QFileDialog::getOpenFileName(parent, tr("Open"), ".");
if (fileName.isEmpty())
return 0;
return openFile(fileName, parent);
}

The static open() function pops up a file dialog through which the user can choose a file. If a file is chosen, openFile() is
called to create an Editor and to read in the file's contents.
Editor *Editor::openFile(const QString &fileName, QWidget *parent)
{
Editor *editor = new Editor(parent);
if (editor->readFile(fileName)) {
editor->setCurrentFile(fileName);
return editor;
} else {
delete editor;
return 0;
}
}

This static function begins by creating a new Editor widget, and then attempts to read in the specified file. If the read is
successful, the Editor is returned; otherwise, the user is informed of the problem (in readFile()), the editor is deleted, and a
null pointer is returned.
bool Editor::save()
{
if (isUntitled) {
return saveAs();
} else {
return saveFile(curFile);
}
}

The save() function uses the isUntitled variable to determine whether it should call saveFile() or saveAs().
void Editor::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
event->accept();
} else {
event->ignore();
}
}

The closeEvent() function is reimplemented to allow the user to save unsaved changes. The logic is coded in the
okToContinue() function, which pops up a message box that asks, "Do you want to save your changes?" If
okToContinue() returns true, we accept the close event; otherwise, we "ignore" it and leave the window unaffected by it.
void Editor::setCurrentFile(const QString &fileName)
{
curFile = fileName;
isUntitled = false;
action->setText(strippedName(curFile));
document()->setModified(false);
setWindowTitle(strippedName(curFile) + "[*]");
setWindowModified(false);
}

165

The setCurrentFile() function is called from openFile() and saveFile() to update the curFile and
isUntitled variables, to set the window title and action text, and to set the document's "modified" flag to false. Whenever
the user modifies the text in the editor, the underlying QTextDocument emits the contentsChanged() signal and sets its
internal "modified" flag to true.
QSize Editor::sizeHint() const
{
return QSize(72 * fontMetrics().width('x'),
25 * fontMetrics().lineSpacing());
}

Finally, the sizeHint() function returns a size based on the width of the letter 'x' and the height of a text line. QMdiArea uses
the size hint to give an initial size to the window.
MDI is one way of handling multiple documents simultaneously. On Mac OS X, the preferred approach is to use multiple
top-level windows. We covered this approach in the "Multiple Documents" section of Chapter 3.

166

7. Event Processing

Reimplementing Event Handlers


Installing Event Filters
Staying Responsive during Intensive Processing
Events are generated by the window system or by Qt itself in response to various occurrences. When the user presses or releases a
key or mouse button, a key or mouse event is generated; when a window is shown for the first time, a paint event is generated to
tell the newly visible window that it needs to draw itself. Most events are generated in response to user actions, but some, such as
timer events, are generated independently by the system.
When we program with Qt, we seldom need to think about events, because Qt widgets emit signals when something significant
occurs. Events become useful when we write our own custom widgets or when we want to modify the behavior of existing Qt
widgets.
Events should not be confused with signals. As a rule, signals are useful when using a widget, whereas events are useful when
implementing a widget. For example, when we are using QPushButton, we are more interested in its clicked() signal than
in the low-level mouse or key events that caused the signal to be emitted. But if we are implementing a class such as
QPushButton, we need to write code to handle mouse and key events and emit the clicked() signal when necessary.

Reimplementing Event Handlers


In Qt, an event is an instance of a QEvent subclass. Qt handles more than a hundred types of events, each identified by an enum
value. For example, QEvent::type() returns QEvent::MouseButtonPress for mouse press events.
Many event types require more information than can be stored in a plain QEvent object; for example, mouse press events need to
store which mouse button triggered the event as well as where the mouse pointer was positioned when the event occurred. This
additional information is stored in dedicated QEvent subclasses, such as QMouseEvent.
Events are notified to objects through their event() function, inherited from QObject. The event() implementation in
QWidget forwards the most common types of events to specific event handlers, such as mousePressEvent(),
keyPressEvent(), and paintEvent().
We have already seen many event handlers when implementing MainWindow, IconEditor, and Plotter in earlier
chapters. Many other types of events are listed in the QEvent reference documentation, and it is also possible to create custom
event types and to dispatch events ourselves. Here, we will review two common event types that deserve more explanation: key
events and timer events.
Key events are handled by reimplementing keyPressEvent() and keyReleaseEvent(). The Plotter widget
reimplements keyPressEvent(). Normally, we only need to reimplement keyPressEvent() since the only keys for
which release is important are the modifier keys Ctrl, Shift, and Alt, and these can be checked for in a keyPressEvent()
using QKeyEvent::modifiers(). For example, if we were implementing a CodeEditor widget, its stripped-down
keyPressEvent() that distinguishes between Home and Ctrl+Home would look like this:
void CodeEditor::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Home:
if (event->modifiers() & Qt::ControlModifier) {
goToBeginningOfDocument();
} else {
goToBeginningOfLine();
}
break;
case Qt::Key_End:
...
default:
QWidget::keyPressEvent(event);
}
}

The Tab and Backtab (Shift+Tab) keys are special cases. QWidget::event() handles them before it calls
keyPressEvent(), with the semantic of passing the focus to the next or previous widget in the focus chain. This behavior is
usually what we want, but in a CodeEditor widget, we might prefer to make Tab indent a line. The event()
reimplementation would then look like this:
167

bool CodeEditor::event(QEvent *event)


{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
insertAtCurrentPosition('\t');
return true;
}
}
return QWidget::event(event);
}

If the event is a key press, we cast the QEvent object to a QKeyEvent and check which key was pressed. If the key is Tab, we
do some processing and return true to tell Qt that we have handled the event. If we returned false, Qt would propagate the
event to the parent widget.
A higher-level approach for implementing key bindings is to use a QAction. For example, if goToBeginningOfLine() and
goToBeginningOfDocument() are public slots in the CodeEditor widget, and the CodeEditor is used as the central
widget in a MainWindow class, we could add the key bindings with the following code:
MainWindow::MainWindow()
{
editor = new CodeEditor;
setCentralWidget(editor);
goToBeginningOfLineAction =
new QAction(tr("Go to Beginning of Line"), this);
goToBeginningOfLineAction->setShortcut(tr("Home"));
connect(goToBeginningOfLineAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfLine()));
goToBeginningOfDocumentAction =
new QAction(tr("Go to Beginning of Document"), this);
goToBeginningOfDocumentAction->setShortcut(tr("Ctrl+Home"));
connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfDocument()));
...
}

This makes it easy to add the commands to a menu or a toolbar, as we saw in Chapter 3. If the commands don't appear in the user
interface, the QAction objects could be replaced with a QShortcut object, the class used internally by QAction to support
key bindings.
By default, key bindings set using QAction or QShortcut on a widget are enabled whenever the window that contains the
widget is active. This can be changed using QAction::setShortcutContext() or QShortcut::setContext().
Another common type of event is the timer event. While most other event types occur as a result of a user action, timer events
allow applications to perform processing at regular time intervals. Timer events can be used to implement blinking cursors and
other animations, or simply to refresh the display.
To demonstrate timer events, we will implement the Ticker widget shown in Figure 7.1. This widget shows a text banner that
scrolls left by one pixel every 30 milliseconds. If the widget is wider than the text, the text is repeated as often as necessary to fill
the entire width of the widget.
Figure 7.1. The Ticker widget

Here's the header file:


Code View:
#ifndef TICKER_H
#define TICKER_H
#include <QWidget>
class Ticker : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
public:
Ticker(QWidget *parent = 0);

168

void setText(const QString &newText);


QString text() const { return myText; }
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event);
private:
QString myText;
int offset;
int myTimerId;
};
#endif

We reimplement four event handlers in Ticker, three of which we have not seen before: timerEvent(), showEvent(),
and hideEvent().
Now let's review the implementation:
#include <QtGui>
#include "ticker.h"
Ticker::Ticker(QWidget *parent)
: QWidget(parent)
{
offset = 0;
myTimerId = 0;
}

The constructor initializes the offset variable to 0. The x-coordinate at which the text is drawn is derived from the offset
value. Timer IDs are always non-zero, so we use 0 to indicate that no timer has been started.
void Ticker::setText(const QString &newText)
{
myText = newText;
update();
updateGeometry();
}

The setText() function sets the text to display. It calls update() to request a repaint and updateGeometry() to notify
any layout manager responsible for the Ticker widget about a size hint change.
QSize Ticker::sizeHint() const
{
return fontMetrics().size(0, text());
}

The sizeHint() function returns the space needed by the text as the widget's ideal size. QWidget::fontMetrics()
returns a QFontMetrics object that can be queried to obtain information relating to the widget's font. In this case, we ask for
the size required by the given text. (The first argument to QFontMetrics::size() is a flag that isn't needed for simple
strings, so we just pass 0.)
void Ticker::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
int textWidth = fontMetrics().width(text());
if (textWidth < 1)
return;
int x = -offset;
while (x < width()) {
painter.drawText(x, 0, textWidth, height(),
Qt::AlignLeft | Qt::AlignVCenter, text());
x += textWidth;
}
}

169

The paintEvent() function draws the text using QPainter::drawText(). It uses fontMetrics() to ascertain how
much horizontal space the text requires, and then draws the text as many times as necessary to fill the entire width of the widget,
taking offset into account.
void Ticker::showEvent(QShowEvent * /* event */)
{
myTimerId = startTimer(30);
}

The showEvent() function starts a timer. The call to QObject::startTimer() returns an ID number, which we can use
later to identify the timer. QObject supports multiple independent timers, each with its own time interval. After the call to
startTimer(), Qt will generate a timer event approximately every 30 milliseconds; the accuracy depends on the underlying
operating system.
We could have called startTimer() in the Ticker constructor, but we save some resources by having Qt generate timer
events only when the widget is actually visible.
void Ticker::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
++offset;
if (offset >= fontMetrics().width(text()))
offset = 0;
scroll(-1, 0);
} else {
QWidget::timerEvent(event);
}
}

The system calls the timerEvent() function at intervals. It increments offset by 1 to simulate movement, wrapping at the
width of the text. Then it scrolls the contents of the widget one pixel to the left using QWidget::scroll(). It would have
been sufficient to call update() instead of scroll(), but scroll() is more efficient because it simply moves the existing
pixels on-screen and generates a paint event only for the widget's newly revealed area (a 1-pixel-wide strip in this case).
If the timer event isn't for the timer we are interested in, we pass it on to the base class.
void Ticker::hideEvent(QHideEvent * /* event */)
{
killTimer(myTimerId);
myTimerId = 0;
}

The hideEvent() function calls QObject::killTimer() to stop the timer.


Timer events are low-level, and if we need multiple timers, it can become cumbersome to keep track of all the timer IDs. In such
situations, it is usually easier to create a QTimer object for each timer. QTimer emits the timeout() signal at each time
interval. QTimer also provides a convenient interface for single-shot timers (timers that time out just once), as we saw in Chapter
6 (p. 158).

170

Anda mungkin juga menyukai