Chapter 23. Platform-Specific Features

Platform-Specific Features

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.

Interfacing with Native APIs

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
A Mac OS X tool window with the title bar on the side

Figure 23.1. A Mac OS X tool window with the title bar on the side

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.

Mac OS X

ATSFontFormatRef

QFont::handle()

CGImageRef

QPixmap::macCGHandle()

GWorldPtr

QPixmap::macQDAlphaHandle()

GWorldPtr

QPixmap::macQDHandle()

RgnHandle

QRegion::handle()

HIViewRef

QWidget::winId()

Windows

HCURSOR

QCursor::handle()

HDC

QPaintEngine::getDC()

HDC

QPrintEngine::getPrinterDC()

HFONT

QFont::handle()

HPALETTE

QColormap::hPal()

HRGN

QRegion::handle()

HWND

QWidget::winId()

X11

Cursor

QCursor::handle()

Font

QFont::handle()

Picture

QPixmap::x11PictureHandle()

Picture

QWidget::x11PictureHandle()

Pixmap

QPixmap::handle()

QX11Info

QPixmap::x11Info()

QX11Info

QWidget::x11Info()

Region

QRegion::handle()

Screen

QCursor::x11Screen()

SmcConn

QSessionManager::handle()

Window

QWidget::handle()

Window

QWidget::winId()

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 (XEvents on X11, MSGs on Windows, EventRef on Mac OS X, QWSEvents on QWS) before they are converted into QEvents. 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.

Using ActiveX on Windows

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 Media Player application

Figure 23.3. The Media Player application

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.

Inheritance tree for the QAxContainer module

Figure 23.4. Inheritance tree for the QAxContainer module

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

VARIANT_BOOL

bool

char, short, int, long

int

unsigned char, unsigned short, unsigned int, unsigned long

uint

float, double

double

CY

qlonglong, qulonglong

BSTR

QString

DATE

QDateTime, QDate, QTime

OLE_COLOR

QColor

SAFEARRAY(VARIANT)

QList<QVariant>

SAFEARRAY(BSTR)

QStringList

SAFEARRAY(BYTE)

QByteArray

VARIANT

QVariant

IFontDisp *

QFont

IPictureDisp *

QPixmap

User defined type

QRect, QSize, QPoint

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.

The AxBouncer widget in Internet Explorer

Figure 23.6. The AxBouncer widget in Internet Explorer

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:

  • -regserver registers the server in the system registry.

  • -unregserver unregisters the server from the system registry.

  • -dumpidl file.idl writes the server’s IDL to the specified file.

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.

Handling X11 Session Management

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.

Logging out on KDE

Figure 23.7. Logging out on KDE

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();
}
The Tic-Tac-Toe application

Figure 23.8. The Tic-Tac-Toe application

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.

“Do you really want to quit?”

Figure 23.9. “Do you really want to quit?”

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset