Home · All Namespaces · All Classes · Grouped Classes · Modules · Functions codeless banner

Tutorial: Listening for Content Changes


The Change Listener Tutorial demonstrates using the changed() signal of QContentSet to listen for newly added content. It also demonstrates creating a QContent for a non-user document, and using categories to identify content.

The Change Listener application displays a slide show of images that updates every three seconds. At three second intervals the application creates a new QContent for one of its images and commits it to the document system, and then uninstalls the previous one. Images are displayed when a QContentSet::changed() signal is received indicating that the new QContent has been added. A category is used to identify the image displayed as one created by the application.

ChangeListener Class Definition

    class ChangeListener : public QLabel

        ChangeListener( QWidget *parent = 0, Qt::WindowFlags flags = 0 );

    private slots:
        void timeout();
        void changed( const QContentIdList &contentIds, QContent::ChangeType type );

        int nextIndex;
        QContentId lastContentId;
        QString categoryId;
        QFileInfoList imageFiles;

The ChangeListener class is composed of a single widget; a QLabel used to display the images, so for convenience we'll inherit a QLabel rather than embed one in a QWidget.

There are two private slots; a timeout() slot which is called each time the timer expires, and a changed() slot which is called when changes to the document system are made.

ChangeListener Class Implementation

In the constructor we first initialize the QLabel so it scales images to fit its dimensions, and so it has a context menu bar. The context menu will only have the help entry which is automatically added so there's no need to do more than initialize the menu.

    ChangeListener::ChangeListener( QWidget *parent, Qt::WindowFlags flags )
        : QLabel( parent, flags )
        , nextIndex( 0 )
        , lastContentId( QContent::InvalidId )
        setScaledContents( true );

         QSoftMenuBar::menuFor( this );

Next we'll construct a list of the images we're going to display, we use resource paths here so we don't have to worry about where the application is installed. QFileInfo::absoluteFilePath() will resolve to the actual file path so we won't actually be using the resource path when installing to the document system.

        imageFiles.append( QFileInfo( ":image/Bubble.png" ) );
        imageFiles.append( QFileInfo( ":image/Clouds.png" ) );
        imageFiles.append( QFileInfo( ":image/Splatters.png" ) );
        imageFiles.append( QFileInfo( ":image/Water.png" ) );

When we install content we'll assign it a category, before we can do that we need to ensure the category exists. We'll name our category Change Listener and put it in the Examples scope and we'll let QCategoryManager assign it an ID. We could alternatively make this a system category and assign our own ID, in which case we'd use QCategoryManager::ensureSystemCategory() here.

        QCategoryManager categoryManager( "Examples" );

        categoryId = categoryManager.idForLabel( "Change Listener" );

        if( categoryId.isEmpty() )
            categoryId = categoryManager.add( "Change Listener" );

Now we'll construct the QContentSet we'll use to listen for newly added content and connect to its changed() signal. We filter it on our Change Listener category so it only contains content we added in this application. The QContentSet should be empty at this point, if it's not then the application did not exit cleanly when it was last run and we'll need to uninstall the images it failed to clean up.

        QContentSet *contentSet = new QContentSet( QContentFilter::category( categoryId ), this );

        connect( contentSet, SIGNAL(changed(QContentIdList,QContent::ChangeType)),
                this, SLOT(changed(QContentIdList,QContent::ChangeType)) );

        for ( int i = 0; i < contentSet->count(); i++ ) {
            QContent::uninstall( contentSet->contentId( i ) );

Finally we'll construct a QTimer and set it trigger the timeout() slot every 3 seconds.

        QTimer *timer = new QTimer( this );

        connect( timer, SIGNAL(timeout()), this, SLOT(timeout()) );

        timer->start( 3000 );

The ChangeListener destructor simply uninstalls the last image installed to the document system so no content records for the images are persisted after exiting the application.

        if( lastContentId != QContent::InvalidId )
            QContent::uninstall( lastContentId );

The timeout() slot periodically creates a new QContent for an image and commits it to the document system. We get the name, and the absolute file path from the QFileInfo, and all of the images are of the image/png type. We assign the QContent::Data role instead of the usual QContent::Document so that the images don't appear in any of the document selectors, and assign the Change Listener category to identify it later.

    void ChangeListener::timeout()
        QFileInfo fileInfo = imageFiles.at( nextIndex );

        QContent image;
        image.setName( fileInfo.baseName() );
        image.setFile( fileInfo.absoluteFilePath() );
        image.setType( "image/png" );
        image.setRole( QContent::Data );
        image.setCategories( QStringList( categoryId ) );

If the image QContent is successfully committed we'll uninstall the QContent for the previous image, and record the ID of the current image.

        if ( image.commit() ) {
            if ( lastContentId != QContent::InvalidId ) {
                QContent::uninstall( lastContentId );
            lastContentId = image.id();
        } else {
            qWarning("Could not commit the new content object!! Document generator exits.");

        nextIndex = (nextIndex + 1) % imageFiles.count();

The changed() slot is called whenever a change is made to content in the document system. Here we use it to identify when a QContent in the Change Listener category has been added.

    void ChangeListener::changed(const QContentIdList &idList,QContent::ChangeType changeType)
        if ( changeType == QContent::Added ) {
            foreach ( QContentId id, idList ) {
                QContent content( id );
                if ( content.isValid() && content.categories().contains( categoryId ) ) {

Once a new QContent has been identified we want to set its name as the window title and display the image in the body of the window. To display the image we open the QContent in read-only mode using QContent::open() and load the data from the returned QIODevice into the image. The caller takes ownership of the QIODevice returned by QContent::open() so we'll need to delete it once we've closed it.

                    setWindowTitle( content.name() );
                    QIODevice *ioDevice = content.open( QIODevice::ReadOnly );
                    if ( ioDevice ) {
                        QImage image;
                        image.load( ioDevice,"PNG" );
                        setPixmap( QPixmap::fromImage( image ) );

                        delete ioDevice;

We only expect a single QContent in a notfication so once we've handled one we can abort checking the rest of the content IDs.


Building the Change Listener application.

To install and run the Change Listener application, carry out the following steps.

  1. Create a new directory (e.g. $HOME/src/changelistener) and copy all the example files to that directory.
        mkdir $HOME/src/changelistener
        cd $HOME/src/changelistener
        cp -r <Qt-Extended-source-directory>/examples/content/changelistener/* .
        chmod +w *
  2. Build the new application.
        export QPEDIR=<Qt-Extended-build-directory>
        $QPEDIR/bin/qbuild image
  3. Run Qt Extended.
  4. Go into the list of Applications and scroll down until you find the Change Listener application.

When you run the Change Listener application, you should see a blank screen which updates with a new picture display every three seconds.

Copyright © 2009 Trolltech Trademarks
Qt Extended 4.4.3