In this chapter, we will review some of the platform-specific options available to Qt programmers. We begin by looking at how to access native APIs such as the Win32 API on Windows, Carbon on Mac OS X, and Xlib on X11. We then move on to explore the ActiveQt extension, showing how to use ActiveX controls within Qt/Windows applications and how to create applications that act as ActiveX servers. In the last section, we explain how to make Qt applications cooperate with the session manager under X11.
In addition to the features presented here, Trolltech offers several platform-specific Qt Solutions, including the Qt/Motif and Qt/MFC migration frameworks to ease the migration of Motif/Xt and MFC applications to Qt. A similar extension for Tcl/Tk applications is provided by froglogic, and a Microsoft Windows resource converter is available from Klarälvdalens Datakonsult. See the following web pages for details:
For embedded development, Trolltech offers the Qtopia application platform, which we cover in Chapter 24.
Some Qt functionality that might have been expected to be platform-specific is available in a platform-independent manner. For example, there is a Qt Solution for creating services (daemons) on Windows, Unix, and Mac OS X.
Qt’s comprehensive API caters for most needs on all platforms, but in some circumstances, we may want to use the underlying platform-specific APIs. In this section, we will show how to use the native APIs for the different platforms supported by Qt to accomplish particular tasks.
On every platform, QWidget
provides a winId()
function that returns the window ID or handle. QWidget
also provides a static function called find()
that returns the QWidget
with a particular window ID. We can pass this ID to native API functions to achieve platform-specific effects. For example, the following code uses winId()
to move the title bar of a tool window to the left using native Mac OS X (Carbon) functions (see Figure 23.1):
#ifdef Q_WS_MAC ChangeWindowAttributes(HIViewGetWindow(HIViewRef(toolWin.winId())), kWindowSideTitlebarAttribute, kWindowNoAttributes); #endif
On X11, here’s how we would modify a window property:
#ifdef Q_WS_X11 Atom atom = XInternAtom(QX11Info::display(), "MY_PROPERTY", False); long data = 1; XChangeProperty(QX11Info::display(), window->winId(), atom, atom, 32, PropModeReplace, reinterpret_cast<uchar *>(&data), 1); #endif
The #ifdef
and #endif
directives around the platform-specific code ensure that the application will still compile on other platforms.
For a Windows-only application, here’s an example of how we can use GDI calls to draw on a Qt widget:
void GdiControl::paintEvent(QPaintEvent * /* event */) { RECT rect; GetClientRect(winId(), &rect); HDC hdc = GetDC(winId()); FillRect(hdc, &rect, HBRUSH(COLOR_WINDOW + 1)); SetTextAlign(hdc, TA_CENTER | TA_BASELINE); TextOutW(hdc, width() / 2, height() / 2, text.utf16(), text.size()); ReleaseDC(winId(), hdc); }
For this to work, we must also reimplement QPaintDevice::paintEngine()
to return a null pointer and set the Qt::WA_PaintOnScreen
attribute in the widget’s constructor.
The next example shows how to combine QPainter
and GDI calls in a paint event handler using QPaintEngine
’s getDC()
and releaseDC()
functions:
void MyWidget::paintEvent(QPaintEvent * /* event */) { QPainter painter(this); painter.fillRect(rect().adjusted(20, 20, -20, -20), Qt::red); #ifdef Q_WS_WIN HDC hdc = painter.paintEngine()->getDC(); Rectangle(hdc, 40, 40, width() - 40, height() - 40); painter.paintEngine()->releaseDC(); #endif }
Mixing QPainter
and GDI calls like this can sometimes lead to strange results, especially when QPainter
calls occur after GDI calls, because QPainter
makes some assumptions about the state of the underlying drawing layer.
Qt defines one of the following four window system symbols: Q_WS_WIN
, Q_WS_X11
, Q_WS_MAC
, or Q_WS_QWS
(Qtopia). We must include at least one Qt header before we can use them in applications. Qt also provides preprocessor symbols to identify the operating system:
Q_OS_AIX
Q_OS_BSD4
Q_OS_BSDI
Q_OS_CYGWIN
Q_OS_DGUX
Q_OS_DYNIX
Q_OS_FREEBSD
Q_OS_HPUX
Q_OS_HURD
Q_OS_IRIX
Q_OS_LINUX
Q_OS_LYNX
Q_OS_MAC
Q_OS_NETBSD
Q_OS_OPENBSD
Q_OS_OS2EMX
Q_OS_OSF
Q_OS_QNX6
Q_OS_QNX
Q_OS_RELIANT
Q_OS_SCO
Q_OS_SOLARIS
Q_OS_ULTRIX
Q_OS_UNIXWARE
Q_OS_WIN32
Q_OS_WIN64
We can assume that at most one of these will be defined. For convenience, Qt also defines Q_OS_WIN
when either Win32 or Win64 is detected, and Q_OS_UNIX
when any Unix-based operating system (including Linux and Mac OS X) is detected. At run-time, we can check QSysInfo::WindowsVersion
or QSysInfo::MacintoshVersion
to distinguish between different versions of Windows (2000, ME, etc.) or Mac OS X (10.2, 10.3, etc.).
In addition to the operating system and window system macros, there is also a set of compiler macros. For example, Q_CC_MSVC
is defined if the compiler is Microsoft Visual C++. These can be useful for working around compiler bugs.
Several of Qt’s GUI-related classes provide platform-specific functions that return low-level handles to the underlying object. These are listed in Figure 23.2.
|
|
|
|
|
|
|
|
|
|
|
|
Windows | |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
X11 | |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Figure 23.2. Platform-specific functions to access low-level handles
On X11, QPixmap::x11Info()
and QWidget::x11Info()
return a QX11Info
object that provides various pointers or handles, such as display()
, screen()
, colormap()
, and visual()
. We can use these to set up an X11 graphics context on a QPixmap
or QWidget
, for example.
Qt applications that need to interface with other toolkits or libraries frequently need to access the low-level events (XEvent
s on X11, MSG
s on Windows, EventRef
on Mac OS X, QWSEvent
s on QWS) before they are converted into QEvent
s. We can do this by subclassing QApplication
and reimplementing the relevant platform-specific event filter, one of x11EventFilter()
, winEventFilter()
, macEventFilter()
, and qwsEventFilter()
. Alternatively, we can access the platform-specific events that are sent to a given QWidget
by reimplementing one of x11Event()
, winEvent()
, macEvent()
, and qwsEvent()
. This can be useful for handling certain types of events that Qt normally ignores, such as joystick events.
For more information about platform-specific issues, including how to deploy Qt applications on different platforms, see http://doc.trolltech.com/4.3/winsystem.html.
Microsoft’s ActiveX technology allows applications to incorporate user interface components provided by other applications or libraries. It is built on Microsoft COM and defines one set of interfaces for applications that use components and another set of interfaces for applications and libraries that provide components.
The Qt/Windows Desktop Edition provides the ActiveQt framework to seamlessly combine ActiveX and Qt. ActiveQt consists of two modules:
The QAxContainer module allows us to use COM objects and to embed ActiveX controls in Qt applications.
The QAxServer module allows us to export custom COM objects and ActiveX controls written using Qt.
Our first example will embed the Windows Media Player in a Qt application using the QAxContainer module (see Figure 23.3). The Qt application adds an Open button, a Play/Pause button, a Stop button, and a slider to the Windows Media Player ActiveX control.
The application’s main window is of type PlayerWindow
:
class PlayerWindow : public QWidget { Q_OBJECT Q_ENUMS(ReadyStateConstants) public: enum PlayStateConstants { Stopped = 0, Paused = 1, Playing = 2 }; enum ReadyStateConstants { Uninitialized = 0, Loading = 1, Interactive = 3, Complete = 4 }; PlayerWindow(); protected: void timerEvent(QTimerEvent *event); private slots: void onPlayStateChange(int oldState, int newState); void onReadyStateChange(ReadyStateConstants readyState); void onPositionChange(double oldPos, double newPos); void sliderValueChanged(int newValue); void openFile(); private: QAxWidget *wmp; QToolButton *openButton; QToolButton *playPauseButton; QToolButton *stopButton; QSlider *seekSlider; QString fileFilters; int updateTimer; };
The PlayerWindow
class is derived from QWidget
. The Q_ENUMS()
macro (just below Q_OBJECT
) is necessary to tell moc
that the ReadyStateConstants
type used in the onReadyStateChange()
slot is an enum type. In the private section, we declare a QAxWidget *
member variable.
PlayerWindow::PlayerWindow() { wmp = new QAxWidget; wmp->setControl("{22D6F312-B0F6-11D0-94AB-0080C74C7E95}");
In the constructor, we start by creating a QAxWidget
object to encapsulate the Windows Media Player ActiveX control. The QAxContainer module consists of three classes: QAxObject
encapsulates a COM object, QAxWidget
encapsulates an ActiveX control, and QAxBase
implements the core COM functionality for QAxObject
and QAxWidget
. The relationships between these classes are illustrated in Figure 23.4.
We call setControl()
on the QAxWidget
with the class ID of the Windows Media Player 6.4 control. This will create an instance of the required component. From then on, all the properties, events, and methods of the ActiveX control are available as Qt properties, signals, and slots through the QAxWidget
object.
The COM data types are automatically converted into the corresponding Qt types, as summarized in Figure 23.5. For example, an in-parameter of type VARIANT_BOOL
becomes a bool
, and an out-parameter of type VARIANT_BOOL
becomes a bool &
. If the resulting type is a Qt class (QString
, QDateTime
, etc.), the in-parameter is a const reference (e.g., const QString &
).
Table 23.5. Relationship between COM types and Qt types
COM Types | Qt Types |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
User defined type |
|
To obtain the list of the properties, signals, and slots available in a QAxObject
or QAxWidget
with their Qt data types, call QAxBase::generateDocumentation()
or use Qt’s dumpdoc
command-line tool, located in Qt’s toolsactiveqtdumpdoc
directory.
Let’s continue with the PlayerWindow
constructor:
wmp->setProperty("ShowControls", false); wmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(wmp, SIGNAL(PlayStateChange(int, int)), this, SLOT(onPlayStateChange(int, int))); connect(wmp, SIGNAL(ReadyStateChange(ReadyStateConstants)), this, SLOT(onReadyStateChange(ReadyStateConstants))); connect(wmp, SIGNAL(PositionChange(double, double)), this, SLOT(onPositionChange(double, double)));
After calling QAxWidget::setControl()
, we call QObject::setProperty()
to set the ShowControls
property of the Windows Media Player to false
, since we provide our own buttons to manipulate the component. QObject::setProperty()
can be used both for COM properties and for normal Qt properties. Its second parameter is of type QVariant
.
Next, we call setSizePolicy()
to make the ActiveX control take all the available space in the layout, and we connect three ActiveX events from the COM component to three slots.
... stopButton = new QToolButton; stopButton->setText(tr("&Stop")); stopButton->setEnabled(false); connect(stopButton, SIGNAL(clicked()), wmp, SLOT(Stop())); ... }
The rest of the PlayerWindow
constructor follows the usual pattern, except that we connect some Qt signals to slots provided by the COM object (Play()
, Pause()
, and Stop()
). Since the buttons are similar, we have shown only the Stop button’s implementation here.
Let’s leave the constructor and look at the timerEvent()
function:
void PlayerWindow::timerEvent(QTimerEvent *event) { if (event->timerId() == updateTimer) { double curPos = wmp->property("CurrentPosition").toDouble(); onPositionChange(-1, curPos); } else { QWidget::timerEvent(event); } }
The timerEvent()
function is called at regular intervals while a media clip is playing. We use it to advance the slider. This is done by calling property()
on the ActiveX control to obtain the value of the CurrentPosition
property as a QVariant
and calling toDouble()
to convert it to a double
. We then call onPositionChange()
to perform the update.
We will not review the rest of the code because most of it isn’t directly relevant to ActiveX and doesn’t show anything that we haven’t covered already. The code is included with the book’s examples.
In the .pro
file, we need this entry to link with the QAxContainer module:
CONFIG += qaxcontainer
One frequent need when dealing with COM objects is to be able to call a COM method directly (as opposed to connecting it to a Qt signal). The easiest way to do this is to invoke QAxBase::dynamicCall()
with the name and signature of the method as the first parameter and the arguments to the method as additional parameters. For example:
wmp->dynamicCall("TitlePlay(uint)", 6);
The dynamicCall()
function takes up to eight parameters of type QVariant
and returns a QVariant
. If we need to pass an IDispatch *
or an IUnknown *
this way, we can encapsulate the component in a QAxObject
and call asVariant()
on it to convert it to a QVariant
. If we need to call a COM method that returns an IDispatch *
or an IUnknown *
, or if we need to access a COM property of one of those types, we can use querySubObject()
instead:
QAxObject *session = outlook.querySubObject("Session"); QAxObject *defaultContacts = session->querySubObject("GetDefaultFolder(OlDefaultFolders)", "olFolderContacts");
If we want to call methods that have unsupported data types in their parameter list, we can use QAxBase::queryInterface()
to retrieve the COM interface and call the method directly. As usual with COM, we must call Release()
when we have finished using the interface. If we often need to call such methods, we can subclass QAxObject
or QAxWidget
and provide member functions that encapsulate the COM interface calls. Be aware that QAxObject
and QAxWidget
subclasses cannot define their own properties, signals, or slots.
We will now review the QAxServer module. This module enables us to turn a standard Qt program into an ActiveX server. The server can be either a shared library or a stand-alone application. Servers built as shared libraries are often called in-process servers; stand-alone applications are called out-of-process servers.
Our first QAxServer example is an in-process server that provides a widget that shows a ball bouncing left and right. We will also see how to embed the widget in Internet Explorer.
Here’s the beginning of the class definition of the AxBouncer
widget:
class AxBouncer : public QWidget, public QAxBindable { Q_OBJECT Q_ENUMS(SpeedValue) Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(SpeedValue speed READ speed WRITE setSpeed) Q_PROPERTY(int radius READ radius WRITE setRadius) Q_PROPERTY(bool running READ isRunning)
AxBouncer
, shown in Figure 23.6, is derived from both QWidget
and QAxBindable
. The QAxBindable
class provides an interface between the widget and an ActiveX client. Any QWidget
can be exported as an ActiveX control, but by subclassing QAxBindable
we can notify the client when a property’s value changes, and we can implement COM interfaces to supplement those already implemented by QAxServer.
When doing multiple inheritance involving a QObject
-derived class, we must always put the QObject
-derived class first so that moc
can pick it up.
We declare three read-write properties and one read-only property. The Q_ENUMS()
macro is necessary to tell moc
that the SpeedValue
type is an enum type. The enum is declared in the public section of the class:
public: enum SpeedValue { Slow, Normal, Fast }; AxBouncer(QWidget *parent = 0); void setSpeed(SpeedValue newSpeed); SpeedValue speed() const { return ballSpeed; } void setRadius(int newRadius); int radius() const { return ballRadius; } void setColor(const QColor &newColor); QColor color() const { return ballColor; } bool isRunning() const { return myTimerId != 0; } QSize sizeHint() const; QAxAggregated *createAggregate(); public slots: void start(); void stop(); signals: void bouncing();
The AxBouncer
constructor is a standard constructor for a widget, with a parent
parameter. The QAXFACTORY_DEFAULT()
macro, which we will use to export the component, expects a constructor with this signature.
The createAggregate()
function is reimplemented from QAxBindable
. We will explain it in a moment.
protected: void paintEvent(QPaintEvent *event); void timerEvent(QTimerEvent *event); private: int intervalInMilliseconds() const; QColor ballColor; SpeedValue ballSpeed; int ballRadius; int myTimerId; int x; int delta; };
The protected and private sections of the class are the same as those we would have if this was a standard Qt widget.
AxBouncer::AxBouncer(QWidget *parent) : QWidget(parent) { ballColor = Qt::blue; ballSpeed = Normal; ballRadius = 15; myTimerId = 0; x = 20; delta = 2; }
The AxBouncer
constructor initializes the class’s private variables.
void AxBouncer::setColor(const QColor &newColor) { if (newColor != ballColor && requestPropertyChange("color")) { ballColor = newColor; update(); propertyChanged("color"); } }
The setColor()
function sets the value of the color
property. It calls update()
to repaint the widget.
The unusual part is the requestPropertyChange()
and propertyChanged()
calls. These functions are inherited from QAxBindable
and should ideally be called whenever we change a property. The requestPropertyChange()
asks the client’s permission to change a property, and returns true
if the client allows the change. The propertyChanged()
function notifies the client that the property has been changed.
The setSpeed()
and setRadius()
setters also follow this pattern, and so do the start()
and stop()
slots, since they change the value of the running
property.
One interesting AxBouncer
member function is left:
QAxAggregated *AxBouncer::createAggregate() { return new ObjectSafetyImpl; }
The createAggregate()
function is reimplemented from QAxBindable
. It allows us to implement COM interfaces that the QAxServer module doesn’t already implement or to bypass QAxServer’s default COM interfaces. Here, we do it to provide the IObjectSafety
interface, which Internet Explorer uses to access a component’s safety options. This is the standard trick to get rid of Internet Explorer’s infamous “Object not safe for scripting” error message.
Here’s the definition of the class that implements the IObjectSafety
interface:
class ObjectSafetyImpl : public QAxAggregated, public IObjectSafety { public: long queryInterface(const QUuid &iid, void **iface); QAXAGG_IUNKNOWN HRESULT WINAPI GetInterfaceSafetyOptions(REFIID riid, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions); HRESULT WINAPI SetInterfaceSafetyOptions(REFIID riid, DWORD pdwSupportedOptions, DWORD pdwEnabledOptions); };
The ObjectSafetyImpl
class is derived from both QAxAggregated
and IObjectSafety
. The QAxAggregated
class is an abstract base class for implementations of additional COM interfaces. The COM object that the QAxAggregated
extends is accessible through controllingUnknown()
. The QAxServer module creates this COM object behind the scenes.
The QAXAGG_IUNKNOWN
macro provides standard implementations of QueryInterface()
, AddRef()
, and Release()
. These implementations simply call the same functions on the controlling COM object.
long ObjectSafetyImpl::queryInterface(const QUuid &iid, void **iface) { *iface = 0; if (iid == IID_IObjectSafety) { *iface = static_cast<IObjectSafety *>(this); } else { return E_NOINTERFACE; } AddRef(); return S_OK; }
The queryInterface()
function is a pure virtual function of QAxAggregated
. It is called by the controlling COM object to give access to the interfaces provided by the QAxAggregated
subclass. We must return E_NOINTERFACE
for interfaces that we don’t implement and for IUnknown
.
HRESULT WINAPI ObjectSafetyImpl::GetInterfaceSafetyOptions( REFIID /* riid */, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions) { *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA | INTERFACESAFE_FOR_UNTRUSTED_CALLER; *pdwEnabledOptions = *pdwSupportedOptions; return S_OK; } HRESULT WINAPI ObjectSafetyImpl::SetInterfaceSafetyOptions( REFIID /* riid */, DWORD /* pdwSupportedOptions */, DWORD /* pdwEnabledOptions */) { return S_OK; }
The GetInterfaceSafetyOptions()
and SetInterfaceSafetyOptions()
functions are declared in IObjectSafety
. We implement them to tell the world that our object is safe for scripting.
Let’s now review main.cpp
:
#include <QAxFactory> #include"axbouncer.h" QAXFACTORY_DEFAULT(AxBouncer, "{5e2461aa-a3e8-4f7a-8b04-307459a4c08c}", "{533af11f-4899-43de-8b7f-2ddf588d1015}", "{772c14a5-a840-4023-b79d-19549ece0cd9}", "{dbce1e56-70dd-4f74-85e0-95c65d86254d}", "{3f3db5e0-78ff-4e35-8a5d-3d3b96c83e09}")
The QAXFACTORY_DEFAULT()
macro exports an ActiveX control. We can use it for ActiveX servers that export only one control. The next example in this section will show how to export many ActiveX controls.
The first argument to QAXFACTORY_DEFAULT()
is the name of the Qt class to export. This is also the name under which the control is exported. The other five arguments are the class ID, the interface ID, the event interface ID, the type library ID, and the application ID. We can use standard tools such as guidgen
or uuidgen
to generate these identifiers. Because the server is a library, we don’t need a main()
function.
Here’s the .pro
file for our in-process ActiveX server:
TEMPLATE = lib CONFIG += dll qaxserver HEADERS = axbouncer.h objectsafetyimpl.h SOURCES = axbouncer.cpp main.cpp objectsafetyimpl.cpp RC_FILE = qaxserver.rc DEF_FILE = qaxserver.def
The qaxserver.rc
and qaxserver.def
files referred to in the .pro
file are standard files that can be copied from Qt’s srcactiveqtcontrol
directory.
The makefile or Visual C++ project file generated by qmake
contains rules to register the server in the Windows registry. To register the server on end-user machines, we can use the regsvr32
tool available on all Windows systems.
We can then include the Bouncer component in an HTML page using the <object>
tag:
<object id="AxBouncer" classid="clsid:5e2461aa-a3e8-4f7a-8b04-307459a4c08c"> <b>The ActiveX control is not available. Make sure you have built and registered the component server.</b> </object>
We can create buttons that invoke slots:
<input type="button" value="Start" onClick="AxBouncer.start()"> <input type="button" value="Stop" onClick="AxBouncer.stop()">
We can manipulate the widget using JavaScript or VBScript just like any other ActiveX control. See the demo.html
file included with the book’s examples for a rudimentary page that uses the ActiveX server.
Our last example is a scriptable Address Book application. The application can serve as a standard Qt/Windows application or an out-of-process ActiveX server. The latter possibility allows us to script the application using, say, Visual Basic.
class AddressBook : public QMainWindow { Q_OBJECT Q_PROPERTY(int count READ count) Q_CLASSINFO("ClassID", "{588141ef-110d-4beb-95ab-ee6a478b576d}") Q_CLASSINFO("InterfaceID", "{718780ec-b30c-4d88-83b3-79b3d9e78502}") Q_CLASSINFO("ToSuperClass", "AddressBook") public: AddressBook(QWidget *parent = 0); ~AddressBook(); int count() const; public slots: ABItem *createEntry(const QString &contact); ABItem *findEntry(const QString &contact) const; ABItem *entryAt(int index) const; private slots: void addEntry(); void editEntry(); void deleteEntry(); private: void createActions(); void createMenus(); QTreeWidget *treeWidget; QMenu *fileMenu; QMenu *editMenu; QAction *exitAction; QAction *addEntryAction; QAction *editEntryAction; QAction *deleteEntryAction; };
The AddressBook
widget is the application’s main window. The widget’s property and its public slots will be available for scripting. The Q_CLASSINFO()
macro is used to specify the class and interface IDs associated with the class. These were generated using a tool such as guid
or uuid
.
In the previous example, we specified the class and interface IDs when we exported the QAxBouncer
class using the QAXFACTORY_DEFAULT()
macro. In this example, we want to export several classes, so we cannot use QAXFACTORY_DEFAULT()
. Two options are available to us:
We can subclass QAxFactory
, reimplement its virtual functions to provide information about the types we want to export, and use the QAXFACTORY_EXPORT()
macro to register the factory.
We can use the QAXFACTORY_BEGIN()
, QAXFACTORY_END()
, QAXCLASS()
, and QAXTYPE()
macros to declare and register the factory. This approach requires us to specify the class and interface IDs using Q_CLASSINFO()
.
Back to the AddressBook
class definition: The third occurrence of Q_CLASSINFO()
may seem a bit mysterious. By default, ActiveX controls expose not only their own properties, signals, and slots to clients, but also those of their ancestors up to QWidget
. The ToSuperClass
attribute lets us specify the highest ancestor in the inheritance tree that we want to expose. Here, we specify the class name of the component (AddressBook
) as the highest ancestor to export, meaning that properties, signals, and slots inherited from AddressBook
’s own ancestors will not be exported.
class ABItem : public QObject, public QTreeWidgetItem { Q_OBJECT Q_PROPERTY(QString contact READ contact WRITE setContact) Q_PROPERTY(QString address READ address WRITE setAddress) Q_PROPERTY(QString phoneNumber READ phoneNumber WRITE setPhoneNumber) Q_CLASSINFO("ClassID", "{bc82730e-5f39-4e5c-96be-461c2cd0d282}") Q_CLASSINFO("InterfaceID", "{c8bc1656-870e-48a9-9937-fbe1ceff8b2e}") Q_CLASSINFO("ToSuperClass", "ABItem") public: ABItem(QTreeWidget *treeWidget); void setContact(const QString &contact); QString contact() const { return text(0); } void setAddress(const QString &address); QString address() const { return text(1); } void setPhoneNumber(const QString &number); QString phoneNumber() const { return text(2); } public slots: void remove(); };
The ABItem
class represents one entry in the address book. It is derived from QTreeWidgetItem
so that it can be shown in a QTreeWidget
and from QObject
so that it can be exported as a COM object.
int main(int argc, char *argv[]) { QApplication app(argc, argv); if (!QAxFactory::isServer()) { AddressBook addressBook; addressBook.show(); return app.exec(); } return app.exec(); }
In main()
, we check whether the application is being run stand-alone or as a server. The -activex
command-line option is recognized by QApplication
and makes the application run as a server. If the application isn’t run as a server, we create the main widget and show it as we would normally do in any stand-alone Qt application.
In addition to -activex
, ActiveX servers understand the following command-line options:
When the application is run as a server, we must export the AddressBook
and ABItem
classes as COM components:
QAXFACTORY_BEGIN("{2b2b6f3e-86cf-4c49-9df5-80483b47f17b}", "{8e827b25-148b-4307-ba7d-23f275244818}") QAXCLASS(AddressBook) QAXTYPE(ABItem) QAXFACTORY_END()
The preceding macros export a factory for creating COM objects. Since we want to export two types of COM objects, we cannot simply use QAXFACTORY_DEFAULT()
as we did in the previous example.
The first argument to QAXFACTORY_BEGIN()
is the type library ID; the second argument is the application ID. Between QAXFACTORY_BEGIN()
and QAXFACTORY_END()
, we specify all the classes that can be instantiated and all the data types that we want to make accessible as COM objects.
This is the .pro
file for our out-of-process ActiveX server:
TEMPLATE = app CONFIG += qaxserver HEADERS = abitem.h addressbook.h editdialog.h SOURCES = abitem.cpp addressbook.cpp editdialog.cpp main.cpp FORMS = editdialog.ui RC_FILE = qaxserver.rc
The qaxserver.rc
file referred to in the .pro
file is a standard file that can be copied from Qt’s srcactiveqtcontrol
directory.
Look in the example’s vb
directory for a Visual Basic project that uses the Address Book server.
This completes our overview of the ActiveQt framework. The Qt distribution includes additional examples, and the documentation contains information about how to build the QAxContainer and QAxServer modules and how to solve common interoperability issues.
When we log out on X11, some window managers ask us whether we want to save the session. If we say yes, the applications that were running are automatically restarted the next time we log in, with the same screen positions and, ideally, with the same state as they had when we logged out. An example of this is shown in Figure 23.7.
The X11-specific component that takes care of saving and restoring the session is called the session manager. To make a Qt/X11 application aware of the session manager, we must reimplement QApplication::saveState()
and save the application’s state there.
Microsoft Windows and some Unix systems offer a different mechanism called hibernation. When the user puts the computer into hibernation, the operating system simply dumps the computer’s memory onto disk and reloads it when it wakes up. Applications do not need to do anything or even be aware that this happens.
When the user initiates a shutdown, we can take control just before the shutdown occurs by reimplementing QApplication::commitData()
. This allows us to save any unsaved data and to interact with the user if required. This part of session management is supported on both X11 and Windows.
We will explore session management by going through the code of the session-aware Tic-Tac-Toe application shown in Figure 23.8. First, let’s look at the main()
function:
int main(int argc, char *argv[]) { Application app(argc, argv); TicTacToe toe; toe.setObjectName("toe"); app.setTicTacToe(&toe); toe.show(); return app.exec(); }
We create an Application
object. The Application
class is derived from QApplication
and reimplements both commitData()
and saveState()
to support session management.
Next, we create a TicTacToe
widget, make the Application
object aware of it, and show it. We have called the TicTacToe
widget “toe”. We must give unique object names to top-level widgets if we want the session manager to restore the windows’ sizes and positions.
Here’s the definition of the Application
class:
class Application : public QApplication { Q_OBJECT public: Application(int &argc, char *argv[]); void setTicTacToe(TicTacToe *toe); void saveState(QSessionManager &sessionManager); void commitData(QSessionManager &sessionManager); private: TicTacToe *ticTacToe; };
The Application
class keeps a pointer to the TicTacToe
widget as a private variable.
void Application::saveState(QSessionManager &sessionManager) { QString fileName = ticTacToe->saveState(); QStringList discardCommand; discardCommand << "rm" << fileName; sessionManager.setDiscardCommand(discardCommand); }
On X11, the saveState()
function is called when the session manager wants the application to save its state. The function is available on other platforms as well, but it is never called. The QSessionManager
parameter allows us to communicate with the session manager.
We start by asking the TicTacToe
widget to save its state to a file. Then we set the session manager’s discard command. A discard command is a command that the session manager must execute to delete any stored information regarding the current state. For this example, we set it to
rm sessionfile
where sessionfile
is the name of the file that contains the saved state for the session, and rm
is the standard Unix command to remove files.
The session manager also has a restart command. This is the command that the session manager must execute to restart the application. By default, Qt provides the following restart command:
appname -session id_key
The first part, appname
, is derived from argv[0]
. The id
part is the session ID provided by the session manager; it is guaranteed to be unique among different applications and among different runs of the same application. The key
part is added to uniquely identify the time at which the state was saved. For various reasons, the session manager can call saveState()
multiple times during the same session, and the different states must be distinguished.
Because of limitations in existing session managers, we must ensure that the application’s directory is in the PATH
environment variable if we want the application to restart correctly. In particular, if you want to try out the Tic-Tac-Toe example for yourself, you must install it in, say, /usr/local/bin
and invoke it as tictactoe
.
For simple applications, including Tic-Tac-Toe, we could save the state as an additional command-line argument to the restart command. For example:
tictactoe -state OX-XO-X-O
This would save us from storing the data in a file and providing a discard command to remove the file.
void Application::commitData(QSessionManager &sessionManager) { if (ticTacToe->gameInProgress() && sessionManager.allowsInteraction()) { int r = QMessageBox::warning(ticTacToe, tr("Tic-Tac-Toe"), tr("The game hasn't finished. " "Do you really want to quit?"), QMessageBox::Yes | QMessageBox::No); if (r == QMessageBox::Yes) { sessionManager.release(); } else { sessionManager.cancel(); } } }
The commitData()
function is called when the user logs out. We can reimplement it to pop up a message box warning the user about potential data loss. The default implementation closes all top-level widgets, which results in the same behavior as when the user closes the windows one after another by clicking the close button in their title bars. In Chapter 3, we saw how to reimplement closeEvent()
to catch this and pop up a message box.
For the purposes of this example, we reimplement commitData()
and pop up a message box asking the user to confirm the logout if a game is in progress and if the session manager allows us to interact with the user (see Figure 23.9). If the user clicks Yes, we call release()
to tell the session manager to continue logging out; if the user clicks No, we call cancel()
to cancel the logout.
Now let’s look at the TicTacToe
class:
class TicTacToe : public QWidget { Q_OBJECT public: TicTacToe(QWidget *parent = 0); bool gameInProgress() const; QString saveState() const; QSize sizeHint() const; protected: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *event); private: enum { Empty = '-', Cross = 'X', Nought = 'O' }; void clearBoard(); void restoreState(); QString sessionFileName() const; QRect cellRect(int row, int column) const; int cellWidth() const { return width() / 3; } int cellHeight() const { return height() / 3; } bool threeInARow(int row1, int col1, int row3, int col3) const; char board[3][3]; int turnNumber; };
The TicTacToe
class is derived from QWidget
and reimplements sizeHint()
, paintEvent()
, and mousePressEvent()
. It also provides the gameInProgress()
and saveState()
functions that we used in our Application
class.
TicTacToe::TicTacToe(QWidget *parent) : QWidget(parent) { clearBoard(); if (qApp->isSessionRestored()) restoreState(); setWindowTitle(tr("Tic-Tac-Toe")); }
In the constructor, we clear the board, and if the application was invoked with the -session
option, we call the private function restoreState()
to reload the old session.
void TicTacToe::clearBoard() { for (int row = 0; row < 3; ++row) { for (int column = 0; column < 3; ++column) { board[row][column] = Empty; } } turnNumber = 0; }
In clearBoard()
, we clear all the cells and set turnNumber
to 0.
QString TicTacToe::saveState() const { QFile file(sessionFileName()); if (file.open(QIODevice::WriteOnly)) { QTextStream out(&file); for (int row = 0; row < 3; ++row) { for (int column = 0; column < 3; ++column) out << board[row][column]; } } return file.fileName(); }
In saveState()
, we write the state of the board to disk. The format is straightforward, with ‘X’ for crosses, ‘O’ for noughts, and ‘-’ for empty cells.
QString TicTacToe::sessionFileName() const { return QDir::homePath() + "/.tictactoe_" + qApp->sessionId() + "_" + qApp->sessionKey(); }
The sessionFileName()
private function returns the file name for the current session ID and session key. This function is used for both saveState()
and restoreState()
. The file name is derived from the session ID and session key.
void TicTacToe::restoreState() { QFile file(sessionFileName()); if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); for (int row = 0; row < 3; ++row) { for (int column = 0; column < 3; ++column) { in >> board[row][column]; if (board[row][column] != Empty) ++turnNumber; } } } update(); }
In restoreState()
, we load the file that corresponds to the restored session and fill the board with that information. We deduce the value of turnNumber
from the number of X’s and O’s on the board.
In the TicTacToe
constructor, we called restoreState()
if QApplication::isSessionRestored()
returned true
. In that case, sessionId()
and sessionKey()
return the same values as when the application’s state was saved, and so sessionFileName()
returns the file name for that session.
Testing and debugging session management can be frustrating, because we need to log in and out all the time. One way to avoid this is to use the standard xsm
utility provided with X11. The first time we invoke xsm
, it pops up a session manager window and a terminal. The applications we start from that terminal will all use xsm
as their session manager instead of the usual, system-wide session manager. We can then use xsm
’s window to end, restart, or discard a session, and see whether our application behaves as it should. For details about how to do this, see http://doc.trolltech.com/4.3/session.html.