Trolltech | Documentation | Qt Quarterly | « Laying out MDI Children | Signal Multiplexing »

Providing Context-Sensitive Help
by Trenton Schulz
Qt offers many ways of providing online help. These include tool tips, "What's This?" messages, and access to dedicated help progams like Qt Assistant. Sometimes we want to provide more help on a particular widget than can comfortably fit in a tool tip or "What's This?" message, and we want to be able to take the user to the relevant help without forcing them to search or scroll for it. The solution is to provide context-sensitive help for the current widget.

In this article we present the HelpClient class. This class provides a mechanism for relating widgets to help files. The class also has an event filter which it uses to detect when the user asks for help (for example by pressing F1), and to invoke Qt Assistant with the help file appropriate for the widget the user is on. Let's start by looking at the HelpClient's definition:

    typedef QMap<QWidget *, QString> HelpMap;
    
    class HelpClient : public QObject
    {
        Q_OBJECT
    public:
        HelpClient(QObject *parent, const char *name = 0);
        void installHelpFiles(HelpMap helpMap);
    
    protected:
        bool eventFilter(QObject *obj, QEvent *event);
    
    private slots:
        void handleError(const QString &msg);
    
    private:
        QAssistantClient *mClient;
        HelpMap mHelpMap;
    };
    

The code includes a convenience typedef to give us a QWidget * -> QString map. The HelpClient class makes use of the QAssistantClient class which provides an interface to Qt Assistant. The QAssistantClient is provided in a separate library so you must add the following line to your .pro file so that your build system can find it:

    LIBS += -lqassistantclient
    

Now we're ready to look at the HelpClient's implementation.

    const QString AssistantPath = "/usr/X11R6/bin";
    

To be able to run Qt Assistant we must know where it is. We've hardcoded a path here, but it could easily be held in QSettings and set when the application is first installed.

    HelpClient::HelpClient(QObject *parent, const char *name)
        : QObject(parent, name)
    {
        parent->installEventFilter(this);
        mClient = new QAssistantClient(AssistantPath, this);
        connect(mClient, SIGNAL(error(const QString&)),
                this, SLOT(handleError(const QString &)));
    }
    

In the constructor, the HelpClient instance installs itself as an event filter on its parent. This means that every event that is dispatched to the parent must first go through our HelpClient::eventfilter() function, which we'll look at in a moment. The rest of the constructor deals with initialization of the QAssistantClient. The QAssistantClient takes the path to Qt Assistant as its first argument and a parent QObject as its second argument. As usual with Qt, the QAssistantClient will be deleted when its parent is deleted, and in its destructor it will close any running Qt Assistant that it is communicating with. We also make a connection between the QAssistantClient's error() signal and our own handleError() slot.

    void HelpClient::handleError(const QString &msg)
    {
        QMessageBox::information(0, tr("Error"), msg);
    }
    

This function is mostly useful for debugging.

    void HelpClient::installHelpFiles(HelpMap helpMap)
    {
        mHelpMap = helpMap;
    }
    

The installHelpFiles() function is simple. Now let's look at the event filter which provides the heart of the functionality.

    bool HelpClient::eventFilter(QObject *obj, QEvent *event)
    {
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *ke = static_cast<QKeyEvent *>(event);
            if (ke->key() == Key_F1 || ke->key() == Key_Help) {
                QWidget *widget = 0;
                QString page = "index.html";
                if (obj->isWidgetType())
                    widget = static_cast<QWidget *>(obj)-> focusWidget();
                HelpMap::const_iterator it = mHelpMap.find(widget);
                if (it != mHelpMap.constEnd())
                    page = *it;
    
                mClient->showPage(qApp->applicationDirPath() + "/help/" + page);
                return true;
            }
        }
        return false;
    }
    

An eventFilter() function receives the QEvent that is dispatched and the QObject that the the event is destined for. It returns a boolean indicating what should be done with the event after the function has finished.

We start by checking the type of the event. If it is a KeyPress event we cast it to a KeyEvent. Then we check to see that the key was either F1 (a standard key to invoke help) or the Help key (a special key found on both Macs and Sparcs). If a help key was pressed, we check to see if the object that will receive this event is a widget and if it is we ask for the widget's focus widget. We use the focus widget as a key to retrieve the page we need from the HelpMap. If there is no matching widget we default to the index.html page. Once we have a page to display we call HelpClient::showPage() which will launch Qt Assistant with the appropriate page. We have assumed that the application's help files are in the help subdirectory of the directory where the application's binary resides.

Normally we call accept() or ignore() on a QKeyEvent to indicate whether the event should be propagated, but in an event filter we return true or false to tell Qt what to do. When we handle the help key press we return true to tell Qt that we've taken care of the event so it should not be passed on; otherwise we return false which tells Qt to continue looking for an event handler that can handle the event.

Using the HelpClient

It is easy to use a HelpClient. We simply create a HelpClient object in each widget we want to provide help for, and pass in a HelpMap that relates the child widgets to the help files. For example, suppose we want to provide help for a configuration dialog like the one shown below.

Helpclient-Basic

Helpclient-Adv

To make use of HelpClient we simply include helpclient.h and create a HelpMap in the widget's constructor or init() function.

    #include "helpclient.h"
    
    void ConfigureForm::init()
    {
        HelpClient *helpClient = new HelpClient(this);
        HelpMap hMap;
        hMap.insert(helpButton, "config.html");
        hMap.insert(okButton, "config.html");
        hMap.insert(cancelButton, "config.html");
        hMap.insert(saveCheckBox, "config.html#save");
        hMap.insert(restoreCheckBox, "config.html#restore");
        hMap.insert(clipboardCheckBox, "config.html#clipboard");
        hMap.insert(webButton, "browser.html");
        hMap.insert(emailButton, "email.html");
        helpClient->installHelpFiles(hMap);
    }
    

In the case of the checkboxes we have put the help information in the same file and use the standard HTML # syntax to indicate a location (for example, <a name="restore">) in the help file.

Note that in order for this to work well, all the widgets should have a StrongFocus or a WheelFocus focus policy. This ensures that Tabbing or clicking to a widget and pressing F1 will provide context-sensitive help.

Suggested Enhancements

The HelpClient is useful as it is, but there are some improvements that you might like to consider:


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2003 Trolltech Trademarks Signal Multiplexing »