C++ GUI Programming with Qt 4 C++ GUI Programming with Qt 4 Jasmin Blanchette Mark Summerfield In association with Trolltech Press Upper Saddle River, NJ Boston Indianapolis San Francisco New York Toronto Montreal London Munich Paris Madrid Capetown Sydney Tokyo Singapore Mexico City Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks Where those designations appear in this book, and the publisher was aware of a trademark claim, the.
Basic Qt
Creating Custom Widgets
◆ Integrating Custom Widgets with Qt Designer
This chapter covers the development of custom widgets in Qt, detailing two main approaches: subclassing an existing Qt widget or subclassing QWidget directly We will illustrate both methods and demonstrate how to integrate a custom widget with Qt Designer for seamless use alongside built-in widgets Additionally, we will showcase a custom widget that employs double buffering, a technique that enhances drawing performance.
When a Qt widget demands greater customization than what is achievable through property settings in Qt Designer or function calls, a straightforward solution is to subclass the appropriate widget class to tailor it to our specific requirements.
In this section, we will create a hexadecimal spin box to demonstrate its functionality Although QSpinBox only supports decimal integers, it can be easily subclassed to accept and display hexadecimal values.
#include class QRegExpValidator; class HexSpinBox : public QSpinBox
QValidator::State validate(QString &text, int &pos) const; int valueFromText(const QString &text) const;
QString textFromValue(int value) const; private:
TheHexSpinBoxinherits most of its functionality fromQSpinBox It provides a typical constructor and reimplements three virtual functions fromQSpinBox.
{ setRange(0, 255); validator = new QRegExpValidator(QRegExp("[0-9A-Fa-f]{1,8}"), this); }
We set the default range to be 0 to 255 (0x00to0xFF), which is more appropriate for a hexadecimal spin box thanQSpinBox’s default of 0 to 99.
Users can adjust a spin box's value by either clicking the up and down arrows or entering a value directly into the line editor To ensure accuracy, input should be limited to valid hexadecimal numbers only.
To achieve this, we use aQRegExpValidatorthat accepts between one and eight characters, each of which must be in one of the sets, ‘0’ to ‘9’, ‘A’ to ‘F’, and ‘a’ to ‘f’.
QValidator::State HexSpinBox::validate(QString &text, int &pos) const { return validator->validate(text, pos);
The QSpinBox function checks the validity of the entered text, yielding three possible outcomes: Invalid, indicating the text does not match the regular expression; Intermediate, suggesting the text is a plausible segment of a valid value; and Acceptable, confirming the text is valid The QRegExpValidator’s validate() function is utilized to determine these results Although it is theoretically necessary to return Invalid or Intermediate for values outside the spin box's range, QSpinBox can independently identify this condition.
QString HexSpinBox::textFromValue(int value) const
{ return QString::number(value, 16).toUpper();
The `textFromValue()` function transforms an integer into a string, which is utilized by QSpinBox to refresh the editor section when users interact with the spin box's arrows To convert the integer value into a lowercase hexadecimal format, the static function `QString::number()` is employed with a second argument of 16, followed by applying `QString::toUpper()` to convert the result into uppercase The method `HexSpinBox::valueFromText(const QString &text) const` is designed to process this text input.
{ bool ok; return text.toInt(&ok, 16);
The `valueFromText()` function converts a string input into an integer value when a user enters data into a QSpinBox and presses Enter It utilizes the `QString::toInt()` function to attempt this conversion, specifically interpreting the input as a hexadecimal value If the input is not a valid hexadecimal string, the function sets the `ok` variable to false and returns 0 However, since the validator only allows valid hexadecimal strings, this scenario is not a concern Instead of using a dummy variable for `ok`, a null pointer can be passed as the first argument to `toInt()`.
We have successfully completed the customization of the hexadecimal spin box The process for tailoring other Qt widgets is similar: select an appropriate Qt widget, create a subclass, and override specific virtual functions to modify its functionality.
Custom widgets often consist of a combination of existing widgets, including built-in Qt widgets and other custom options like HexSpinBox These composite custom widgets can typically be created using Qt Designer, allowing for efficient development and design integration.
• Create a new form using the “Widget” template.
• Add the necessary widgets to the form, and lay them out.
• Set up the signals and slots connections.
• If behavior beyond what can be achieved through signals and slots is required, write the necessary code in a class that inherits bothQWidgetand theuic-generated class.
Naturally, combining existing widgets can also be done entirely in code. Whichever approach is taken, the resulting class inherits directly fromQWid- get.
If a widget lacks its own signals and slots and does not override any virtual functions, it can be constructed by combining existing widgets without the need for subclassing This method was demonstrated in Chapter 1 while developing the Age application, utilizing a QWidget and a QSpinBox.
QSlider Even so, we could just as easily have subclassedQWidgetand created theQSpinBoxandQSliderin the subclass’s constructor.
When Qt's existing widgets do not meet our needs, we can create custom widgets by subclassing QWidget and reimplementing specific event handlers to manage painting and mouse interactions This method allows us to fully customize both the appearance and functionality of our widgets Qt's standard widgets, such as QLabel, QPushButton, and QTableWidget, are built using this technique, demonstrating that we can also independently create similar components using the public functions available in QWidget, ensuring platform independence.
To demonstrate how to write a custom widget using this approach, we will create theIconEditor widget shown in Figure 5.2 TheIconEditoris a widget that could be used in an icon editing program.
Figure 5.2 The IconEditor widget Let’s begin by reviewing the header file.
#include class IconEditor : public QWidget
Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor)
Q_PROPERTY(QImage iconImage READ iconImage WRITE setIconImage) Q_PROPERTY(int zoomFactor READ zoomFactor WRITE setZoomFactor) public:
IconEditor(QWidget *parent = 0); void setPenColor(const QColor &newColor);
QColor penColor() const { return curColor; } void setZoomFactor(int newZoom); int zoomFactor() const { return zoom; } void setIconImage(const QImage &newImage);
QImage iconImage() const { return image; }
The IconEditor class utilizes the Q_PROPERTY() macro to define three custom properties: penColor, iconImage, and zoomFactor Each property is associated with a specific data type and includes a "read" function alongside an optional "write" function For instance, the penColor property, which is of type QColor, can be accessed and modified through the penColor() and setPenColor() functions, respectively.
In Qt Designer, when utilizing a widget, custom properties are displayed in the property editor beneath the inherited QWidget properties These properties can be of any type supported by QVariant, and the Q_OBJECT macro is essential for classes that define these properties Key event handling methods include mousePressEvent, mouseMoveEvent, and paintEvent, while a private method, setImagePixel, is used to manipulate image pixels based on position and opacity.
QRect pixelRect(int i, int j) const;
IconEditorreimplements three protected functions fromQWidgetand has a few private functions and variables The three private variables hold the values of the three properties.
The implementation file begins with theIconEditor’s constructor:
{ setAttribute(Qt::WA_StaticContents); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); curColor = Qt::black; zoom = 8; image = QImage(16, 16, QImage::Format_ARGB32); image.fill(qRgba(0, 0, 0, 0));
The constructor has some subtle aspects such as the Qt::WA_StaticContents attribute and thesetSizePolicy()call We will discuss them shortly.
The pen color is set to black The zoom factor is set to 8, meaning that each pixel in the icon will be rendered as an 8 × 8 square.
The icon data is managed through the image member variable, accessible via the setIconImage() and iconImage() functions An icon editor program commonly invokes setIconImage() when opening an icon file, while iconImage() is used to retrieve the icon for saving purposes The image variable is defined as a QImage type, initialized to a size of 16 × 16 pixels in a 32-bit ARGB format, which allows for semi-transparency To ensure a clean slate, the image data is cleared by filling it with a transparent color.
The QImage class offers a hardware-independent way to store images, supporting 1-bit, 8-bit, or 32-bit depth configurations In a 32-bit image, each pixel consists of 8 bits allocated for the red, green, and blue color components, along with an additional 8 bits for the alpha component, which determines opacity For instance, a pure red color is represented by the values 255 for red, 0 for green, 0 for blue, and 255 for alpha In Qt, this specific color can be easily defined using these values.
QRgb red = qRgba(255, 0, 0, 255); or, since the color is opaque, as
QRgb is simply a typedef for unsigned int, andqRgb() and qRgba() are inline functions that combine their arguments into one 32-bit integer value It is also possible to write
Intermediate Qt 6 Layout Management
Event Processing
◆ Staying Responsive During Intensive Processing
Events in a window system or Qt are triggered by various occurrences, primarily user actions such as pressing or releasing a key or mouse button, which generate key or mouse events Additionally, a paint event is created when a window is displayed for the first time, indicating that it should render itself While many events stem from user interactions, others, like timer events, are generated autonomously by the system.
When we program with Qt, we seldom need to think about events, because
Qt widgets generate signals in response to important events, which become particularly valuable when creating custom widgets or altering the functionality of existing ones.
Events and signals serve distinct purposes in widget development Signals, such as the clicked() signal of a QPushButton, are primarily relevant during widget usage, while events are crucial during widget implementation When utilizing a QPushButton, the focus is on the clicked() signal rather than the underlying mouse or key events Conversely, when creating a class like QPushButton, it is essential to manage mouse and key events and emit the clicked() signal as needed.
In Qt, events are objects derived from QEvent, with over a hundred event types identified by unique enum values For instance, the method QEvent::type() returns QEvent::MouseButtonPress for mouse press events.
Certain event types necessitate more detailed information than what a standard QEvent object can provide For instance, mouse press events must capture which mouse button was pressed and the precise location of the mouse pointer at the time of the event This extra data is managed through specialized QEvent subclasses, like QMouseEvent.
In QObject, events are communicated to objects via the event() function The QWidget class enhances this by directing common event types to their respective handlers, including mousePressEvent(), keyPressEvent(), and paintEvent().
In previous chapters, we explored various event handlers while implementing MainWindow, IconEditor, and Plotter The QEvent reference documentation outlines numerous event types, and it is also feasible to create custom events and manage event dispatching This article will focus on two significant event types: key events and timer events, providing a detailed examination of their functionalities.
In handling key events, the keyPressEvent() and keyReleaseEvent() functions are typically reimplemented, with a focus on keyPressEvent() for most cases This is because only modifier keys such as Ctrl, Shift, and Alt require attention during release, which can be effectively monitored within keyPressEvent() using QKeyEvent::modifiers() For instance, when developing a CodeEditor widget, the implementation of keyPressEvent() can differentiate between the Home key and the Ctrl+Home combination.
{ switch (event->key()) { case Qt::Key_Home: if (event->modifiers() & Qt::ControlModifier) { goToBeginningOfDocument();
} break; case Qt::Key_End:
The Tab and Backtab (Shift+Tab) keys are unique in that they are processed by QWidget::event() prior to keyPressEvent(), facilitating the transfer of focus to the next or previous widget in the focus chain While this behavior is typically desired, in a CodeEditor widget, it may be more beneficial to configure the Tab key to indent a line instead To achieve this, the event() function can be overridden as follows: bool CodeEditor::event(QEvent *event).
{ if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Tab) { insertAtCurrentPosition(’\t’); return true;
When handling key press events in Qt, we cast the QEvent object to a QKeyEvent to identify the pressed key If the Tab key is detected, we perform the necessary processing and return true to indicate that the event has been handled Returning false would allow Qt to propagate the event to the parent widget.
To implement key bindings effectively, a higher-level approach involves utilizing QAction For instance, if the functions goToBeginningOfLine() and goToBeginningOfDocument() are defined as public slots within the CodeEditor widget, and this widget is employed as the central component of a MainWindow class, key bindings can be seamlessly integrated using the appropriate code.
In a code editor setup, a new action called "Go to Beginning of Line" is created and assigned the shortcut key "Home," allowing users to quickly navigate to the start of the current line Additionally, another action, "Go to Beginning of Document," is configured with the shortcut "Ctrl+Home," enabling users to jump to the start of the entire document Both actions are connected to their respective slots in the editor for seamless functionality.
Adding commands to a menu or toolbar is straightforward, as demonstrated in Chapter 3 If commands are not visible in the user interface, you can substitute QAction objects with QShortcut objects, which QAction uses internally to enable key bindings.
By default, key bindings set usingQActionorQShortcuton a widget are enabled whenever the window that contains the widget is active This can be changed usingQAction::setShortcutContext()orQShortcut::setContext().
Timer events are a prevalent type of event that enable applications to execute processes at consistent time intervals, distinguishing them from other event types that typically arise from user actions These events are useful for implementing features such as blinking cursors, animations, and refreshing displays.
In this article, we will create a ticker widget that showcases a scrolling text banner, moving left by one pixel every 30 milliseconds The ticker is designed to repeat the text as needed to ensure it fully occupies the widget's width, providing a seamless viewing experience.
#include class Ticker : public QWidget
Q_PROPERTY(QString text READ text WRITE setText) public:
Ticker(QWidget *parent = 0); 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;
We reimplement four event handlers inTicker, three of which we have not seen before:timerEvent(),showEvent(), andhideEvent().
Now let’s review the implementation:
Drag and Drop
Drag and drop is a user-friendly method for transferring information both within an application and across different applications This feature is commonly available alongside clipboard support, enhancing the process of moving and copying data efficiently.
In this chapter, we will explore the implementation of drag and drop functionality in an application, including the management of custom formats Additionally, we will demonstrate how to leverage the same drag and drop code to incorporate clipboard support, thanks to the QMimeData class, which enables data handling in multiple formats.
Drag and drop involves two distinct actions: dragging and dropping Qt widgets can serve as drag sites, as drop sites, or as both.
This article demonstrates how to enable drag-and-drop functionality in a Qt application, specifically a main window featuring a QTextEdit widget When users drag a text file from their desktop or file explorer and drop it onto the application, the content of the file is automatically loaded into the QTextEdit, enhancing user interaction and file management capabilities.
Here’s the definition of the example’sMainWindowclass: class MainWindow : public QMainWindow
MainWindow(); protected: void dragEnterEvent(QDragEnterEvent *event); void dropEvent(QDropEvent *event); private: bool readFile(const QString &fileName);
The MainWindow class overrides the dragEnterEvent() and dropEvent() methods from QWidget to demonstrate drag and drop functionality As this example focuses primarily on showcasing these features, many typical functionalities expected in a main window class have been intentionally left out.
{ textEdit = new QTextEdit; setCentralWidget(textEdit); textEdit->setAcceptDrops(false); setAcceptDrops(true); setWindowTitle(tr("Text Editor"));
In the constructor, we initialize a QTextEdit and designate it as the central widget, which inherently accepts text drags from external applications When a user drops a file onto the QTextEdit, it automatically inserts the file name into the text area To capture drop events for the entire MainWindow, we disable dropping on the QTextEdit while enabling it on the main window, allowing us to handle drag-and-drop events effectively.
{ if (event->mimeData()->hasFormat("text/uri-list")) event->acceptProposedAction();
ThedragEnterEvent()is called whenever the user drags an object onto a widget.
By invoking the callacceptProposedAction() function on an event, we signal that the user is permitted to drop the draggable object onto the widget Without this function, the widget would not accept the drop by default Additionally, Qt automatically adjusts the cursor to inform the user if the widget is a valid drop target.
To enable users to drag files without any additional actions, we verify the MIME type of the dragged content The MIME type `text/uri-list` is specifically designed to hold a collection of universal resource identifiers (URIs), which may include file names and URLs (such as HTTP or FTP paths) The Internet Assigned Numbers Authority (IANA) defines standard MIME types, consisting of a primary type and a subtype divided by a slash These MIME types play a crucial role in the clipboard and drag-and-drop functionalities, facilitating the identification of various data types For a comprehensive list of MIME types, refer to the official IANA registry at [iana.org](http://www.iana.org/assignments/media-types/).
QList urls = event->mimeData()->urls(); if (urls.isEmpty()) return;
QString fileName = urls.first().toLocalFile(); if (fileName.isEmpty()) return; if (readFile(fileName)) setWindowTitle(tr("%1 - %2").arg(fileName)
The `dropEvent()` function is triggered when a user drops an object onto a widget, utilizing `QMimeData::urls()` to retrieve a list of QUrls While users typically drag a single file, they may also select and drag multiple files at once If multiple URLs are detected, or if the URL does not correspond to a local file, the function will exit immediately.
QWidget also provides dragMoveEvent() and dragLeaveEvent(), but for most applications they don’t need to be reimplemented.
This example demonstrates the implementation of drag-and-drop functionality by creating a subclass of QListWidget This custom widget will be utilized as a component in the Project Chooser application, as depicted in Figure 9.1.
Figure 9.1 The Project Chooser application
The Project Chooser application features two list widgets, each representing a different project and populated with names Users can easily drag and drop names between the lists to reassign individuals from one project to another, streamlining project management and collaboration.
The drag and drop code is all located in theQListWidgetsubclass Here’s the class definition: class ProjectListWidget : public QListWidget
ProjectListWidget(QWidget *parent = 0); protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); private: void startDrag();
The ProjectListWidget class reimplements five event handlers declared in QWidget.
In the constructor, we enable drops on the list widget. void ProjectListWidget::mousePressEvent(QMouseEvent *event)
{ if (event->button() == Qt::LeftButton) startPos = event->pos();
When the left mouse button is pressed, the current mouse position is saved in the private variable startPos We then invoke the QListWidget's mousePressEvent() method to allow the QListWidget to handle mouse press events as expected.
{ if (event->buttons() & Qt::LeftButton) { int distance = (event->pos() - startPos).manhattanLength(); if (distance >= QApplication::startDragDistance()) startDrag();
When a user holds down the left mouse button and moves the mouse cursor, a drag action is initiated if the distance between the current mouse position and the initial press position exceeds the recommended threshold of 4 pixels set by QApplication This implementation helps prevent accidental drags caused by minor hand movements.
QListWidgetItem *item = currentItem(); if (item) {
QMimeData *mimeData = new QMimeData; mimeData->setText(item->text());
QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->setPixmap(QPixmap(":/images/person.png")); if (drag->start(Qt::MoveAction) == Qt::MoveAction) delete item;
In the InstartDrag() function, we instantiate a QDrag object with 'this' as its parent, which utilizes a QMimeData object to store the data For this example, we set the data as a text/plain string using QMimeData::setText() QMimeData offers various functions to manage common drag types such as images, URLs, and colors, and it can also accommodate arbitrary MIME types represented as QByteArrays Additionally, the QDrag::setPixmap() function is called to define the icon that follows the cursor during the drag operation.
The `QDrag::start()` function initiates the dragging process and waits for the user to either drop or cancel the action It accepts a combination of supported drag actions, such as `Qt::CopyAction`, `Qt::MoveAction`, and `Qt::LinkAction`, returning the executed drag action or `Qt::IgnoreAction` if none occurs The executed action depends on the permissions of the source widget, the capabilities of the target, and the modifier keys pressed during the drop After invoking `start()`, Qt assumes ownership of the drag object and will automatically delete it when it's no longer needed.
ProjectListWidget *source = qobject_cast(event->source()); if (source && source != this) { event->setDropAction(Qt::MoveAction); event->accept();
The ProjectListWidget is capable of both initiating and accepting drag-and-drop actions from other ProjectListWidgets within the same application The QDragEnterEvent::source() function provides a pointer to the originating widget if it belongs to the same application; otherwise, it returns a null pointer To confirm that the drag event originates from a ProjectListWidget, we utilize qobject_cast() Once verified, we inform Qt that we are prepared to accept the action as a move operation within the dragMoveEvent function.
ProjectListWidget *source = qobject_cast(event->source()); if (source && source != this) { event->setDropAction(Qt::MoveAction); event->accept();
Input/Output
Reading from and writing to files or devices is essential for nearly all applications, and Qt offers robust I/O support via QIODevice This powerful abstraction represents "devices" that can read and write byte blocks, and Qt features several subclasses of QIODevice to enhance functionality.
QFile Accesses files in the local file system and in embedded resources QTemporaryFile Creates and accesses temporary files in the local file system
QBuffer Reads data from or writes data to a QByteArray
QProcess Runs external programs and handles inter-process communication QTcpSocket Transfers a stream of data over the network using TCP
QUdpSocket Sends or receives UDP datagrams over the network
QProcess, QTcpSocket, and QUdpSocket are sequential devices that allow data access in a linear manner, from the first byte to the last In contrast, QFile, QTemporaryFile, and QBuffer are random-access devices, enabling multiple reads from any position within the data These random-access devices also feature the QIODevice::seek() function, which allows users to reposition the file pointer as needed.
Qt offers two advanced stream classes, QDataStream for binary data and QTextStream for text, allowing seamless reading and writing from any I/O device These classes handle byte ordering and text encodings, enabling Qt applications across various platforms and countries to interact with each other's files effortlessly This convenience makes Qt's I/O classes superior to the Standard C++ classes, which require developers to manage these complexities themselves.
QFile simplifies access to individual files, whether stored in the file system or embedded within an application's executable resources For applications requiring the identification of entire file sets, Qt offers the QDir class to facilitate this process.
QFileInfoclasses, which handle directories and provide information about the files inside them.
The QProcess class enables the execution of external programs while facilitating communication via standard input, output, and error channels (cin, cout, and cerr) It allows users to configure environment variables and the working directory for the external application By default, the communication is asynchronous (non-blocking), although blocking on specific operations is also an option.
Networking and reading and writing XML are such substantial topics that they are covered separately in their own dedicated chapters (Chapter 14 and Chapter 15).
Reading and Writing Binary Data
To efficiently load and save binary data in Qt, you can create a QFile object, open the desired file, and utilize a QDataStream object for access QDataStream offers a platform-independent storage format compatible with fundamental C++ types such as int and double, as well as various Qt data types including QByteArray, QFont, QImage, QPixmap, QString, and QVariant Additionally, it supports Qt container classes like QList and QMap.
Here’s how we would store an integer, aQImage, and aQMapin a file calledfacts.dat:
QMap map; map.insert("red", Qt::red); map.insert("green", Qt::green); map.insert("blue", Qt::blue);
QFile file("facts.dat"); if (!file.open(QIODevice::WriteOnly)) { cerr parent();
} else if (qName == "page") { if (currentItem) {
QString allPages = currentItem->text(1); if (!allPages.isEmpty()) allPages += ", "; allPages += currentText; currentItem->setText(1, allPages);
TheendElement()function is called when the reader encounters a closing tag. Just as withstartElement(), the third parameter is the name of the tag.
When the tag is , we update the private variable currentItem to reference the parent of the current QTreeWidgetItem This process guarantees that currentItem is reverted to its previous value prior to reading the corresponding tag.
When the tag is , the designated page number or range is appended to the comma-separated list in the text of the current item in column 1 The method bool SaxHandler::fatalError(const QXmlParseException &exception) is invoked to handle any parsing errors.
QMessageBox::warning(0, QObject::tr("SAX Handler"),
QObject::tr("Parse error at line %1, column " "%2:\n%3.")
arg(exception.message())); return false;
ThefatalError()function is called when the reader fails to parse the XML file.
If this occurs, we simply display a message box, giving the line number, the column number, and the parser’s error text.
This completes the implementation of theSaxHandlerclass Now let’s see how we can make use of it: bool parseFile(const QString &fileName)
QStringList labels; labels setWindowTitle(QObject::tr("SAX Handler")); treeWidget->show();
SaxHandler handler(treeWidget); reader.setContentHandler(&handler); reader.setErrorHandler(&handler); return reader.parse(inputSource);
Providing Online Help
◆ Using QTextBrowser as a Simple Help Engine
◆ Using Qt Assistant for Powerful Online Help
Most applications offer users online help, which can range from brief tooltips and status tips to comprehensive resources spanning multiple pages Qt supports various forms of help, including these short aids For more extensive assistance, developers can utilize QTextBrowser as a straightforward online help viewer or launch Qt Assistant or an HTML browser directly from their application.
Tooltips, Status Tips, and “What’s This?” Help
A tooltip is a brief text description that appears when a user hovers their mouse over a specific widget for a short duration Typically displayed in black text on a yellow background, tooltips serve the main purpose of offering explanations for toolbar buttons.
We can add tooltips to arbitrary widgets in code usingQWidget::setToolTip(). For example: findButton->setToolTip(tr("Find next"));
To set the tooltip for a QAction that can be included in a menu or toolbar, use the setToolTip() method For instance, you can create a new action with the title "&New" and assign it a tooltip that describes it as "New document" by using the following code: `newAction = new QAction(tr("&New"), this); newAction->setToolTip(tr("New document"));`.
If we don’t explicitly set a tooltip, QAction will automatically use the ac- tion text.
A status tip is a brief descriptive text that appears in the status bar when a user hovers over a toolbar button or menu option To add a status tip to an action or widget, use the CallsetStatusTip() function, such as in the example: newAction->setStatusTip(tr("Create a new document")).
Figure 16.1 An application showing a tooltip and a status tip
In certain scenarios, it is beneficial to offer more detailed information about a widget beyond what tooltips or status tips can convey For instance, a complex dialog with explanations for each field can enhance user understanding without requiring a separate help window The "What's This?" mode serves as an ideal solution, allowing users to click on any interface component to access its help text To activate "What's This?" mode, users can click the question mark button in the dialog's title bar or press Shift+F1.
Here is an example of a “What’s This?” text set on a dialog: dialog->setWhatsThis(tr(""
" The meaning of the Source field depends " "on the Type field:"
"
"
HTML tags can effectively format the text in a "What's This?" feature, allowing for the inclusion of images, bulleted lists, and bold text For a comprehensive list of supported tags and attributes in Qt, refer to the official documentation at http://doc.trolltech.com/4.1/richtext-html-subset.html.
Figure 16.2 A dialog showing a “What’s This?” help text
When implementing "What's This?" text for actions in an application, it appears when users interact with menu items, toolbar buttons, or shortcut keys while in "What's This?" mode It's standard practice to include a "What's This?" option in the Help menu and a corresponding toolbar button This can be achieved by creating a "What's This?" action using the static QWhatsThis::createAction() function and adding the resulting action to both the Help menu and the toolbar Additionally, the QWhatsThis class offers static functions to programmatically enter and exit "What's This?" mode.
Using QTextBrowser as a Simple Help Engine
Large applications may require more online help than tooltips, status tips, and
A practical solution for user assistance is to implement a help browser within applications This feature usually includes a "Help" option in the main menu and a "Help" button in every dialog, ensuring users can easily access support when needed.
This section introduces the simple help browser, illustrated in Figure 16.3, and outlines its application usage The window utilizes QTextBrowser to present help pages formatted with an HTML-based syntax Due to its capability to manage numerous HTML tags, QTextBrowser is well-suited for displaying help content effectively.
We begin with the header file:
#include class QPushButton; class QTextBrowser; class HelpBrowser : public QWidget
HelpBrowser(const QString &path, const QString &page,
QWidget *parent = 0); static void showPage(const QString &page); private slots: void updateWindowTitle(); private:
TheHelpBrowserprovides a static function that can be called from anywhere in the application This function creates aHelpBrowserwindow and shows the given page.
Figure 16.3 The HelpBrowser widget Here’s the beginning of the implementation:
HelpBrowser::HelpBrowser(const QString &path, const QString &page, QWidget *parent)
In the code snippet, the attributes for a Qt widget are set to enable automatic deletion upon closure and establish group leadership A new QTextBrowser is instantiated, accompanied by three QPushButtons labeled "Home," "Back," and "Close," with the "Close" button assigned the shortcut key "Esc."
QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(homeButton); buttonLayout->addWidget(backButton); buttonLayout->addStretch(); buttonLayout->addWidget(closeButton);
The code snippet initializes a vertical layout using QVBoxLayout, adding a button layout and a text browser widget to it It establishes connections between various buttons and their respective slots, allowing for navigation functions like going home, moving backward, and closing the window Additionally, it updates the window title when the source of the text browser changes and sets the search paths for resources, including images, while specifying the source page for the text browser.
Setting the Qt::WA_GroupLeader attribute allows users to interact with HelpBrowser windows from modal dialogs, in addition to the main application window Typically, modal dialogs restrict user interaction with other windows, but by enabling this attribute, users can access both the modal dialog and the HelpBrowser simultaneously after requesting help.
We offer two search paths for accessing application documentation: one for the file system and another for image resources Images can be referenced in HTML using standard file system paths or by starting with a colon and a slash (:/) The 'page' parameter specifies the documentation file name, which can include an optional HTML anchor.
{ setWindowTitle(tr("Help: %1").arg(textBrowser->documentTitle())); }
Whenever the source page changes, theupdateWindowTitle()slot is called The documentTitle()function returns the text specified in the page’stag. void HelpBrowser::showPage(const QString &page)
QString path = QApplication::applicationDirPath() + "/doc";
HelpBrowser *browser = new HelpBrowser(path, page); browser->resize(500, 400); browser->show();
In the theshowPage() static function, we initialize the HelpBrowser window and display it to the user This window is designed to close automatically when the user exits, thanks to the Qt::WA_DeleteOnClose attribute set in the HelpBrowser constructor.
In this example, we assume that the documentation resides in the 'doc' subdirectory of the directory containing the application's executable, with all pages provided to the showPage() function sourced from this subdirectory.
Internationalization
In addition to the Latin alphabet used for English and for many European languages, Qt 4 also provides extensive support for the rest of the world’s writing systems:
• Qt uses Unicode throughout the API and internally No matter what language we use for the user interface, the application can support all users alike.
• Qt’s text engine can handle all the major non-Latin writing systems, including Arabic, Chinese, Cyrillic, Hebrew, Japanese, Korean, Thai, and the Indic languages.
• Qt’s layout engine supports right-to-left layouts for languages such as Arabic and Hebrew.
• Certain languages require special input methods for entering text Editor widgets such asQLineEditandQTextEditwork well with any input method installed on the user’s system.
To ensure a seamless user experience, it's essential to translate not only user-input text but also the entire user interface Qt simplifies this process by allowing developers to wrap all visible strings with the tr() function and utilize its tools to create translation files in various languages For translators, Qt offers a GUI tool called Qt Linguist, which works alongside command-line programs lupdate and lrelease, typically used by developers for managing translations effectively.
In many applications, a translation file is loaded at startup according to the user's locale settings; however, some scenarios necessitate the ability for users to switch languages during runtime This is achievable with Qt, albeit with some additional effort Fortunately, Qt’s layout system ensures that user interface components automatically adjust to accommodate translated texts, even when they exceed the length of the original content.
Unicode is a comprehensive character encoding standard that accommodates the majority of the world's writing systems by utilizing 16 bits for character storage, allowing for the encoding of approximately 65,000 characters, as opposed to the mere 256 characters supported by 8 bits It includes ASCII and ISO 8859-1 (Latin-1) as subsets, maintaining consistent code positions; for instance, the character 'A' is represented by the value 0x41 across ASCII, Latin-1, and Unicode.
‘Â’ has value0xD1in both Latin-1 and Unicode.
Qt’sQStringclass stores strings as Unicode Each character in aQStringis a 16-bitQCharrather than an 8-bitchar Here are two ways of setting the first character of a string to ‘A’: str[0] = ’A’; str[0] = QChar(0x41);
If the source file is encoded in Latin-1, specifying Latin-1 characters is just as easy: str[0] = ’N ~ ’;
And if the source file has another encoding, the numeric value works fine: str[0] = QChar(0xD1);
Unicode characters can be specified using their numeric values, such as the Greek capital letter sigma (Σ) with the value 0x3A3 and the euro currency symbol (€) with the value 0x20AC.
The Unicode Standard lists the numeric values for all supported characters at [unicode.org](http://www.unicode.org/standard/) For those who infrequently use non-Latin-1 Unicode characters, online searches are adequate; however, Qt offers more efficient methods for inputting Unicode strings in applications, which will be discussed further in this section.
Qt 4’s text engine is versatile, supporting a wide range of writing systems across all platforms, including Arabic, Chinese, Cyrillic, Greek, Hebrew, Japanese, Korean, Lao, Latin, Thai, and Vietnamese, as well as all Unicode 4.1 scripts that do not require special processing Additionally, on X11 with Fontconfig and recent Windows versions, it accommodates Bengali, Devanagari, Gujarati, Gurmukhi, Kannada, Khmer, Malayalam, Syriac, Tamil, Telugu, Thaana (Dhivehi), and Tibetan Oriya is supported specifically on X11, while Windows XP users can access Mongolian and Sinhala With the appropriate fonts installed, Qt can effectively render text in any of these writing systems.
Recent Unicode versions now include character values exceeding 65,535, which are represented by sequences of two 16-bit values known as "surrogate pairs." With the appropriate input methods installed, users can seamlessly input text in various writing systems within their Qt applications.
Programming with QChar differs from using char, as obtaining the numeric value of a QChar requires calling the unicode() function To retrieve the ASCII or Latin-1 value of a QChar, the toLatin1() function should be used; however, for non-Latin-1 characters, toLatin1() will return ‘\0’.
If we know that all the strings in a program are ASCII, we can use standard
When working with character classification in Qt, it's advisable to use QChar's member functions instead of the functions like isalpha(), isdigit(), and isspace() on the return value of toLatin1() QChar offers a range of functions that are effective for any Unicode character, including isPrint(), isPunct(), isSpace(), isMark(), isLetter(), isNumber(), isLetterOrNumber(), isDigit(), isSymbol(), isLower(), and isUpper() For instance, to check if a character is either a digit or an uppercase letter, you can use the condition: if (ch.isDigit() || ch.isUpper()).
The code snippet works for any alphabet that distinguishes between uppercase and lowercase, including Latin, Greek, and Cyrillic.
Once a Unicode string is obtained, it can be utilized in any part of Qt's API that requires a QString Qt will then ensure proper display and handle the necessary conversions to relevant encodings for communication with the operating system.
When handling text files, it's crucial to be aware of the various encodings they may use, as determining a file's encoding from its content can be challenging By default, QTextStream utilizes the system's local 8-bit encoding, typically Latin-1 for American and West European locales, for both reading and writing operations.
To create a custom file format that supports arbitrary Unicode characters, initialize the QTextStream by setting the codec to UTF-16 with the command `stream.setCodec("UTF-16")` and enable the generation of a byte order mark using `stream.setGenerateByteOrderMark(true)` before writing data This ensures that the information is saved in UTF-16 format.
UTF-16 is a character encoding format that uses two bytes per character and begins with a Unicode byte order mark (0xFFFE) to indicate its encoding and byte order (little-endian or big-endian) This format closely resembles the memory representation of a QString, enabling quick reading and writing of Unicode strings However, it introduces overhead when saving pure ASCII data, as it requires two bytes for each character instead of one.
You can specify different encodings in Qt by using the setCodec() function along with an appropriate QTextCodec A QTextCodec serves as a converter between Unicode and specific encodings, playing a crucial role in various contexts within Qt, such as supporting fonts, input methods, the clipboard, drag and drop, and file names Additionally, QTextCodecs are accessible for use when developing Qt applications.
QTextStream automatically detects Unicode in text files that begin with a byte order mark, a feature that can be disabled by using setAutoDetectUnicode(false) If the presence of a byte order mark is uncertain, it's advisable to call setCodec() with "UTF-16" prior to reading the file.
Multithreading
◆ Communicating with the Main Thread
◆ Using Qt’s Classes in Secondary Threads
Conventional GUI applications operate on a single thread, executing one task at a time, which often leads to the user interface freezing during time-consuming operations To address this issue, Chapter 7 (Event Processing) explores various solutions, including the implementation of multi-threading as an effective approach to enhance user experience.
In multithreaded applications, the graphical user interface (GUI) operates in a dedicated thread, while processing tasks are handled by additional threads This architecture ensures that the GUI remains responsive, even during heavy processing tasks Furthermore, multithreading enhances performance on multiprocessor systems by allowing multiple threads to execute concurrently across different processors.
This chapter covers the process of subclassing QThread and utilizing QMutex, QSemaphore, and QWaitCondition for thread synchronization It also explores methods for secondary threads to communicate with the main thread during an active event loop Additionally, we will conclude with an overview of the Qt classes that are permissible in secondary threads and those that are not.
Multithreading is a complex subject with extensive literature available, but this article assumes you have a basic understanding of multithreaded programming The emphasis here is on developing multithreaded applications using Qt, rather than on the fundamental concepts of threading.
Creating multiple threads in a Qt application is easy by subclassing QThread and overriding the run() function For illustration, we will examine a basic QThread subclass that continuously outputs a specified string to the console.
Thread(); void setMessage(const QString &message); void stop(); protected: void run(); private:
QString messageStr; volatile bool stopped;
TheThreadclass inherits fromQThreadand reimplements therun()function It provides two additional functions:setMessage()andstop().
Thestoppedvariable is declared volatile because it is accessed from different threads and we want to be sure that it is freshly read every time it is needed.
If we omitted thevolatilekeyword, the compiler might optimize access to the variable, possibly leading to incorrect results.
We setstoppedtofalsein the constructor. void Thread::run()
{ while (!stopped) cerr setText(tr("Stop A"));
When the user clicks the button for Thread A, the function startOrStopThreadA() will either stop the thread if it is currently running or start it if it is not Additionally, this function updates the button's text to reflect the current state of the thread.
{ if (threadB.isRunning()) { threadB.stop(); threadBButton->setText(tr("Start B"));
} else { threadB.start(); threadBButton->setText(tr("Stop B"));
The code forstartOrStopThreadB()is very similar. void ThreadDialog::closeEvent(QCloseEvent *event)
{ threadA.stop(); threadB.stop(); threadA.wait(); threadB.wait(); event->accept();
When a user clicks "Quit" or closes the window, the application halts any active threads and waits for their completion using QThread::wait() before invoking QCloseEvent::accept() This process guarantees a clean exit for the application, even though it may not significantly impact this particular example.
If you run the application and clickStart A, the console will be filled with ‘A’s.
If you clickStart B, it will now fill with alternating sequences of ‘A’s and ‘B’s.ClickStop A, and now it will only print ‘B’s.
A common requirement for multithreaded applications is that of synchroniz- ing several threads Qt provides the following synchronization classes:QMutex, QReadWriteLock,QSemaphore, andQWaitCondition.
The QMutex class is designed to ensure that only one thread can access a variable or section of code at any given time, thereby providing thread safety It features a lock() function that allows a thread to lock the mutex; if the mutex is available, the thread locks it immediately, but if it is already locked, the thread will be blocked until it is released Once the lock() function completes, the thread retains ownership of the mutex until it calls unlock() Additionally, the QMutex class includes a tryLock() function, which attempts to lock the mutex and returns immediately if it is already in use.
For example, let’s suppose that we wanted to protect thestoppedvariable of theThreadclass from the previous section with aQMutex We would then add the following data member toThread: private:
Therun()function would change to this: void Thread::run()
{ forever { mutex.lock(); if (stopped) { stopped = false; mutex.unlock(); break;
} mutex.unlock(); cerr setControl("{22D6F312-B0F6-11D0-94AB-0080C74C7E95}");
In the constructor, we initialize a QAxWidget object to encapsulate the Windows Media Player ActiveX control The QAxContainer module comprises three classes: QAxObject, which encapsulates a COM object; QAxWidget, which encapsulates an ActiveX control; and QAxBase, which implements the essential COM functionality for both QAxObject and QAxWidget.
Embedded Programming
Creating software for mobile devices like PDAs and smartphones presents significant challenges due to the limitations of embedded systems, which typically feature slower processors, reduced permanent storage options, less memory, and smaller displays compared to desktop computers.
Qtopia Core, formerly known as Qt/Embedded, is an optimized version of Qt designed specifically for embedded Linux systems It offers the same API and tools as the desktop versions of Qt, including Qt/Windows, Qt/X11, and Qt/Mac, while also incorporating additional classes and tools tailored for embedded programming Available under a dual licensing model, Qtopia Core supports both open source and commercial development.
Qtopia Core is compatible with any hardware that operates on Linux, including various architectures like Intel x86, MIPS, ARM, StrongARM, Motorola 68000, and PowerPC It features a memory-mapped frame buffer and supports a C++ compiler, distinguishing itself from Qt/X11 by eliminating the need for the X Window System Instead, it utilizes its own window system, QWS, which leads to considerable savings in storage and memory.
Qtopia Core can be optimized to minimize memory usage by recompiling it to exclude unnecessary features By identifying the specific applications and components required for a device beforehand, these can be combined into a single executable that statically links to the Qtopia Core libraries, enhancing efficiency.
Qtopia Core incorporates key features from the desktop versions of Qt, such as efficient implicit data sharing through "copy on write" for improved memory management, customizable widget styles via QStyle, and an adaptive layout system that optimizes screen space utilization.
Qtopia Core serves as the foundation of Trolltech's embedded solutions, encompassing the Qtopia Platform, Qtopia PDA, and Qtopia Phone These offerings include specialized classes and applications tailored for portable devices and support integration with various third-party Java virtual machines.
Qtopia Core applications can be developed on any platform with a multi-platform toolchain, typically using a GNU C++ cross-compiler on a Unix system This process is streamlined by a script and patches from Dan Kegel at http://kegel.com/crosstool/ Since Qtopia Core includes the Qt API, developers can often utilize desktop versions of Qt, like Qt/X11 or Qt/Windows, for the majority of their development work.
Qtopia Core’s configuration system supports cross-compilers, through thecon- figurescript’s-embeddedoption For example, to build for the ARM architecture we would type
We can create custom configurations by adding new files to Qt’s mkspecs/ qwsdirectory.
Qtopia Core interacts directly with the Linux frame buffer, which is the memory area linked to the video display To utilize the frame buffer, it may be necessary to provide write permissions for the /dev/fb0 device.
To run Qtopia Core applications, it is essential to initiate a server process that allocates screen regions to clients and generates mouse and keyboard events Any Qtopia Core application can function as a server by using the `-qw` option in its command line or by providing `QApplication::GuiServer` as the third parameter in the `QApplication` constructor.
Client applications interact with the Qtopia Core server through shared memory, allowing them to manage their own window decorations and minimizing communication with the server This design enhances the responsiveness of the user interface While Qtopia Core applications typically utilize QPainter for rendering, they also have the option to directly access video hardware via QDirectPainter.
Clients can communicate with each other using the QCOP procotol A client can listen on a named channel by creating aQCopChannelobject and connecting to itsreceived()signal For example:
QCopChannel *channel = new QCopChannel("System", this); connect(channel, SIGNAL(received(const QString &, const QByteArray &)), this, SLOT(received(const QString &, const QByteArray &)));
A QCOP message consists of a name and an optionalQByteArray The static QCopChannel::send()broadcasts a message on a channel For example:
QDataStream out(&data, QIODevice::WriteOnly); out