Calling a QML item method from C++

This is another one of those “it’s all in the documentation”, but for whatever reason, I had a heck of a time finding the info when I needed it. (The Qt documentation is generally great — I’m not beefing about that, but rather my personal inability to remember bits of it and not have enough remembered that I can search on things efficiently).

There’s times where you want to have something in C++ call a method defined by a QML item. In my case, I had a ListView that I wanted to manually reposition when its model (a C++ object derived from QStandardItemModel) changed under certain circumstances. I began with a ListView that looked something like this:

    ListView {
        id: events
        objectName: "events"

        property bool moved: false

        model: appmodel
        delegate: MyItemDelegate {}
        Component.onCompleted: positionViewAtIndex(count - 1, ListView.End)
        onMovementStarted: moved = true
        function positionListAtEnd() {
            if (!moved) events.positionViewAtIndex(events.count - 1, ListView.End)
        }
    }

Here, I’m going to use the moved property to track whether the user’s interacted with the list view at all; if she’s moved the list view, I don’t want to start yanking it around when the model updates, but if it’s just sat unused and the model updates, I want the list to always show the last item in the model. When the list view first loads, I do that manually using the onCompleted signal handler; I also want to do it when the model changes, by having the code that updates the model invoke the ListView‘s positionListAtEnd method.

The trick is actually getting a reference to the QObject that corresponds to my ListView. In QML, items are referenced by their id property; in Qt’s meta-object system, they’re referenced by object name—denoted by the objectName property, a string bearing the object’s name.

Don’t confuse the two! You can’t link QML things using object names, and, as near as I can tell, the Qt meta-object system’s C++ implementation doesn’t care one whit about QML ids.

So I give my ListView an objectName property, which for sanity’s sake is the same as the id I’ve assigned: "events". It’s a string, not a name, so those quotes in the QML are important. Once I’ve done that, I can find this object by name in my QML hierarchy using the following bit of C++:

    QObject *object = mView.rootObject();
    if (!object) return;

    QObject* listView = object->findChild<QObject *>("events");
    QVariant returnedValue;
    QMetaObject::invokeMethod(listView, "positionListAtEnd",
            Q_RETURN_ARG(QVariant, returnedValue));

The mView field is just the QDeclarativeView that renders my QML; you can see how I originally linked my C++ and QML in my previous post, Adding C++ Objects to your QML. It has a root object, a QObject descendant (really, a QGraphicsObject that corresponds to the top level of the QML object hierarchy being displayed). To invoke my QML method, I use Qt’s meta-object protocol to first find the child named "events" and then invoke its positionListAtEnd method, discarding the value it returns.

There’s no magic here—as long as you remember that QML items can have an objectName property, and that property is used internally by Qt’s object hierarchy as the object’s name.

Using an Image Provider to Share Images from a C++-hosted Data Model to QML

In a previous post, I demonstrated how to use an image provider for a relatively simple use case: loading QML from the application’s resources. Another common question I’ve heard is how to do the same from an application model; say you’ve got an existing C++ model that carries images for each item; how do you share those images with QML? You may want to do that if you have existing C++ code for a data model that renders the image data for each item (say, by compositing several different things from a model, such as its description and date).

It’s not hard: again, you need to write a QDeclarativeImageProvider that returns the appropriate images given names that the QML will derive from your model. Of course, you also need to extend your model’s roles to include a role for the image name; your model code will populate this role with a unique name for each item’s image, and the QML will use this role’s value in Image elements’ source property to reference the image. In turn, the declarative runtime will use the resulting source values to request the images from the image provider, which then provides the desired images for rendering.

Here’s what I did for a pixmap provider in a recent project, naming images in my model image://model/---, where — is a unique identifier for the item in the model.

The image provider provides only pixmaps, and is a little more sophisticated than the last one I showed you, because it needs to work with a model, whose data can change underneath us:

class ModelIndexProvider : public QObject, public QDeclarativeImageProvider
{
    Q_OBJECT
public:
    ModelIndexProvider(QAbstractItemModel& model, int pathRole, int pixmapRole, 
        QObject* parent = 0);
    ~ModelIndexProvider();
    QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize);

public slots:
    void dataUpdated(const QModelIndex & topLeft, const QModelIndex & bottomRight);
    void dataDeleted(const QModelIndex & parent, int start, int end);
    void dataReset();

private:
    QAbstractItemModel& mModel;
    int mPathRole;
    int mPixmapRole;
    QMap mPixmapIndex;
};

The class uses a QMap to provide an index from the name of each image in the model to its model index; this is used by requestPixmap when it’s passed an id originally from the model and needs to determine the image’s index into the model. requestPixmap looks like this:

QPixmap ModelIndexProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize)
{
    QString key = QString("image://model/%1").arg(id);
    QModelIndex index = mPixmapIndex[key];
    QPixmap image = mModel.data(index, mPixmapRole).value<QPixmap>();
    QPixmap result;

    if (requestedSize.isValid()) {
        result = image.scaled(requestedSize, Qt::KeepAspectRatio);
    } else {
        result = image;
    }
    *size = result.size();
    return result;
}

This code just looks up the model index of the image given the image name, and then scales the image already in the model before returning it to the caller.

Of course, the mapping between name and index must be maintained; the code performs the necessary registration to watch the model at construction time:

ModelIndexProvider::ModelIndexProvider(QAbstractItemModel& model, 
    int pathRole, int pixmapRole, 
    QObject* parent) :
    QObject(parent),
    QDeclarativeImageProvider(QDeclarativeImageProvider::Pixmap),
    mModel(model),
    mPathRole(pathRole),
    mPixmapRole(pixmapRole)
{
    // For each pixmap already in the model, get a mapping between the name and the index
    for(int row = 0; row < mModel.rowCount(); row++) {
        QModelIndex index = mModel.index(row, 0);
        QString path = mModel.data(index, mPathRole).value<QString>();
        mPixmapIndex[path] = index;
    }
    connect(&mModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
            this, SLOT(dataUpdated(QModelIndex,QModelIndex)));
    connect(&mModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
            this, SLOT(dataDeleted(QModelIndex,int,int)));
    connect(&mModel, SIGNAL(modelReset()),
            this, SLOT(dataReset()));
}

Notice how the constructor takes the roles for the image path and image itself; this way instances of the ModelIndexProvider can be used in more than one project, or more than once in a project, each for different model roles that contain QPixmap instances. The constructor begins by creating the initial index of image names and model indices, and then connects to the model's signals that indicate when the model has changed.

Handling changes isn't difficult; in my case because the model doesn't carry that many items I just recreate the index any time the data is changed or deleted. If you have a complex model that changes a lot, you might want to be smarter about cache invalidation --- or drop the cache entirely and just sequentially search the model for the bitmap data when it's requested, as the QML viewer's pretty good about caching the resulting bitmaps anyway.

void ModelIndexProvider::dataUpdated(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
    // For each pixmap already in the model, get a mapping between the name and the index
    for(int row = 0; row < mModel.rowCount(); row++) {
        QModelIndex index = mModel.index(row, 0);
        QString path = mModel.data(index, mPathRole).value<QString>();
        mPixmapIndex[path] = index;
    }
}

void ModelIndexProvider::dataDeleted(const QModelIndex&, int start, int end)
{
    // For each pixmap already in the model, get a mapping between the name and the index
    for(int row = 0; row < mModel.rowCount(); row++) {
        QModelIndex index = mModel.index(row, 0);
        QString path = mModel.data(index, mPathRole).value<QString>();
        mPixmapIndex[path] = index;
    }
}

void ModelIndexProvider::dataReset()
{
    mPixmapIndex.clear();
}

All that remains is to register the image provider with the Qt declarative runtime, as you saw here!

One important thing to remember is that QML tracks your images by the image source, which is a string, not the bitmap behind the source. Even if your bitmap data is in the model in a role as it is here, changing the bitmap won't update the QML! Instead, you need to change the underlying source name to indicate to the QML that it needs to load a new bitmap. In my code, I do this by appending a timestamp in milliseconds to the source attribute; whenever my bitmap changes I update the model with the same image path and a new timestamp, which triggers a model invalidation in the QML declarative view and a subsequent bitmap fetch and redraw.

Here's the code if you want to try it for yourself.

Using an Image Provider to Supply Images to QML Applications from Qt Resources

Your hybrid QML/C++ application can load its QML from a Qt resource, but what about images? If you load the QML from a Qt resource, then all of your images are loaded from the Qt resource segment as well. But what if you want to supply your QML as files (or deliver them over the network)?

Your C++ application can provide images to the Qt declarative runtime through an image provider, so that in your QML you can write something like

Image {
    source: "image://myprovider/image.png"
}

and the QML runtime will query your application’s image provider for a provider registered to the name myprovider, asking that provider for the image named image.png. Using the image provider framework, it’s easy to load images from your application’s resource segment: all you need to do is provide a custom image provider that loads images from the resource segment and register it with the Qt declarative runtime.

Qt image providers are subclasses of QDeclarativeImageProvider, a class that has methods to return a QPixmap or a QImage given the image’s name. It’s up to you to decide how to organize and fetch those images when you implement an image provider; an obvious way when fetching images from the resource segment is to use the resource name. Our resource-based image provider looks like this:

class ResourceImageProvider : public QDeclarativeImageProvider
{
public:
    ResourceImageProvider(QDeclarativeImageProvider::ImageType type);
    ~ResourceImageProvider();
    QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
    QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize);
};

The constructor accepts an indicator as to whether the returned images provided are pixmaps or images; while pixmaps are faster to draw, they reside in the graphics subsystem’s memory, which is typically a scarce resource, so you should always be sure to use this judiciously.

The image provider gives images to the Qt declarative runtime through the requestImage and requestPixmap methods, which return a QImage or QPixmap, respectively. Each of these methods take the identifier of the image to return, a desired size for the image, and a pointer where to place the actual size of the image that’s returned. The image provider should try to match the requested size if it’s able, although depending on the QML, can accept a smaller or larger image and reflow the QML layout—that depends, of course, on the QML element’s anchors property, of course.

The actual image provider is simple:

ResourceImageProvider::ResourceImageProvider(QDeclarativeImageProvider::ImageType type) :
    QDeclarativeImageProvider(type)
{
    // This space intentionally left blank.
}

ResourceImageProvider::~ResourceImageProvider()
{
    // This space intentionally left blank.
}

QImage ResourceImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize)
{
    QString rsrcid = ":/" + id;
    QImage image(rsrcid);
    QImage result;

    if (requestedSize.isValid()) {
        result = image.scaled(requestedSize, Qt::KeepAspectRatio);
    } else {
        result = image;
    }
    *size = result.size();
    return result;
}

QPixmap ResourceImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize)
{
    QString rsrcid = ":/" + id;
    QPixmap image(rsrcid);
    QPixmap result;

    if (requestedSize.isValid()) {
        result = image.scaled(requestedSize, Qt::KeepAspectRatio);
    } else {
        result = image;
    }
    *size = result.size();
    return result;
}

The image and pixmap loaders do essentially the same thing: create a Qt resource identifier from the incoming image id, load the image, scale it to the requested size, and return the requested image (as a pixmap in the requestPixmap case) along with its size.

Once you’ve created an image provider, you need to make it available to the QDeclarativeView displaying your QML. In a previous post, I showed you how to create a QDeclarativeView that displays your QML in a QMainWindow; you can use the same code plus the following snippet to add your image provider to the Qt declarative runtime:

    QDeclarativeView* view = new QDeclarativeView(window);
    ...
    view->engine()->addImageProvider(QLatin1String("qrc"), new ResourceImageProvider(QDeclarativeImageProvider::Pixmap));

This code pokes at the QDeclarativeView‘s QDeclarativeEngine, giving it an instance of your image provider that returns pixmaps for images in the namespace beginning with qrc. Once you’ve done this, you can now load images in your QML from your Qt resource segment using things like:

Image {
    source: "image://qrc/image.png"
}

In a later post, I’ll show you how you can use an image provider for another common C++ integration point: loading images directly from a C++-hosted data model.

Although you probably already know this, it’s worth pointing out an important detail about Qt resources: they’re in your application’s read-only segment. If your application is running on a memory-contrained platform like a mobile device, it’s worth choosing carefully whether you place your resources in a Qt resource segment or as additional files on the file system packaged with your application. If they’re in the data segment, it’s possible they’ll be loaded into memory when your application is launched (for example, when started from a memory card on Symbian, which only performs demand-paging from the internal store). Consequently, putting a lot of images in your application’s resources can lead to slower load times and even out-of-memory errors on some platforms, so it’s best to do this with small resources that simply must be loaded quickly like bitmaps for small user interface controls and the like.

You can download the code, too!.

Lisp on a Nokia Booklet 3G…

At the office, managed to wheedle a Nokia Booklet 3G, last year’s entry by Nokia into the netbook category. I’ll not review the device here; there are plenty of reviews of the product on the Web. Suffice it to say that the reviews are pretty accurate; it’s a pretty sweet piece of kit hobbled by the choice of Windows Starter and a slow hard disk. Addressing the performance challenges by putting a better operating system on it leads to a very nice subnotebook for light browsing, note taking, and such. Perhaps surprisingly, I actually reach to it before the iPad for some casual computing, especially at the office.

The operating system I chose is Jolicloud; I wanted to see the UI, and it has built-in compatibility for the GMA500 graphics controller in the Nokia Booklet and didn’t want to mess with patching Ubuntu once I got started. Reports were correct—installing Jolicloud was a snap and just works on the Booklet. What follows here should work on any Ubuntu derivative, and more broadly, probably any Debian-based Linux distribution.

So what about Lisp? I don’t get paid to do Lisp programming, but like playing in Lisp; it sharpens my thinking about the algorithms and code I write, and I’m thinking about introducing my son to Lisp through the excellent book Land of Lisp by Conrad Barski (which you can read about here). It’s also been a while since I’ve worked any of the exercises in Peter Seibel’s Practical Common Lisp (see the web site).

Because Jolicloud was running a Debian fork of Linux (Jolicloud’s parent is Ubuntu, derived from Debian), getting a Lisp was as easy as running the APT package manager. I chose CLISP, because I’d not used it before but it’s the Lisp Barski uses in his book. Bring up a command prompt (Alt-F1), and type:

kf6gpe@kf6gpe-jolicloud:~$ sudo apt-get install clisp
kf6gpe@kf6gpe-jolicloud:~$ sudo apt-get install clisp-doc
kf6gpe@kf6gpe-jolicloud:~$ sudo apt-get install clisp-dev
kf6gpe@kf6gpe-jolicloud:~$ sudo apt-get install emacs
kf6gpe@kf6gpe-jolicloud:~$ sudo apt-get install slime

This gets you both CLISP and its documentation for playing, and emacs and slime if you want to do serious stuff. You can now bring up CLISP from the terminal by typing clisp at the shell. To finish the configuration, put something like the following in your .emacs file:

;;; ================================================================
;;; SLIME configuration
;;; Modified from http://lispm.dyndns.org/news?ID=NEWS-2008-08-27-1

(require 'slime-autoloads)
(slime-setup `(slime-asdf slime-fancy slime-tramp))

(setq slime-autodoc-use-multiline-p t)
(setq slime-repl-history-size 1000)
(setq slime-startup-animation t)
(setq slime-default-lisp 'clisp)
(setq slime-truncate-lines nil)
(setq slime-lisp-implementations
     `((clisp   ("/usr/bin/clisp")              :coding-system utf-8-unix)
       ;;(ccl   ("/usr/bin/ccl")                :coding-system utf-8-unix)
       ;;(abcl  ("abcl")                        :coding-system utf-8-unix)
       ;;(cmucl ("/usr/bin/cmucl" "-quiet")     :coding-system iso-latin-1-unix)
       ;;(ecl   ("/usr/local/bin/ecl")          :coding-system iso-latin-1-unix)
       ;;(gcl   ("gcl")                         :coding-system iso-latin-1-unix)
       ;;(sbcl  ("/usr/bin/sbcl")               :coding-system utf-8-unix)
       ))
; do m-- m-x slime ccl to start Clozure Common Lisp from the list above

(global-set-key "\C-cs" 'slime-selector)

(add-hook 'lisp-mode-hook (lambda () (slime-mode t)))
(add-hook 'inferior-lisp-mode-hook (lambda () (inferior-slime-mode t)))

(defmacro defslime-start (name lisp args coding-system)
  `(defun ,name ()
     (interactive)
     (require 'slime)
     (slime-start :program ',lisp :coding-system ',coding-system :program-args ',args)))

(defslime-start clisp   "/usr/local/bin/clisp -K full"  nil         utf-8-unix)

This work is shamelessly borrowed from Rainer Joswig; he shows how to set up SLIME on the Macintosh using Aquaemacs. In our case, we comment out all the other Lisp implementations—although I’d bet if you wanted a different Lisp (say, SBCL), it’s no more than an apt-get away.

Transparent windows with QML…

QMLViewer doesn’t do it — what if you want your QML rendered in a transparent window (say, over the desktop)? The short answer is that you’ll need to write your own QML player app, something I’ve talked about before. However, you need to tweak a few things in the process.

Before you begin, it’s worth noting that this doesn’t work everywhere — transparency is subject to the vagaries of the platform’s windowing system and windowing manager. For example, this bit of code I’m about to show you worked fine on Windows 7, Maemo, and MeeGo (netbook), but not Ubuntu 10.04 with my particular graphics card, but did on the Ubuntu machine next to me. (I’ve not yet taken the time to try it on Mac OS X at all—a curious state of affairs, given that Mac OS X is my “home” platform of choice. But anyway…) It’s also worth noting that transparency can slow things down, as you’re going to be asking the graphics subsystem to do the necessary blending and compositing, which means more processing spent in rendering. This translates to lower battery life on mobile devices, of course.

Caveats aside, what we’re trying to do is something like this:

Translucent QML-rendering window running on an N900 with PR 1.3.
Screen shot of translucent window & QML.

My test QML is very simple:

import Qt 4.7
Rectangle {
    id: window
    width: 800
    height: 480
    color: "transparent"
    Rectangle {
        width: 400
        height: 240
        anchors.centerIn: parent
        color: "red"
        opacity: 0.5
    }
}

The key here is that the items you want to be transparent—such as the main item in the QML containing other items—should have their color property set to "transparent".

However, to do this, the main window needs to be transparent as well. Moreover, On X systems like MeeGo and Maemo, not only does it need to be transparent, but frameless as well. To do this, you need to configure the QMainWindow appropriately at construction, and set the Qt::WA_TranslucentBackground attribute for the QML view. You also need to set its palette to render using transparency. Thus, the code to display the QML might look something like this:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QMainWindow window(0, Qt::FramelessWindowHint);
    QDeclarativeView view;
    QPalette palette;

    // We'd sure like this window to be translucent if we're able
    window.setAttribute(Qt::WA_TranslucentBackground, true);
    view.setAttribute(Qt::WA_TranslucentBackground);
    palette.setColor(QPalette::Base, Qt::transparent);
    // The only thing we show is the declarative view.
    window.setCentralWidget(&view);
    view.setPalette(palette);
    view.setSource(QUrl("qml/translucent/main.qml"));
    window.show();
    // Pass control to Qt's event loop
    return app.exec();
}

(To test this, I just created a new Qt Quick Application, and replaced the main.cpp’s main function with the code you see above and the QML with my QML in the previous listing. If you try this, don’t forget to include the necessary headers like QDeclarativeView,, too!)

The code’s pretty simple: create an instance of the main window, indicating that it has no parent and should be frameless. Next, set the WA_TranslucentBackground for both the main window and the QDeclarativeView responsible for rendering the QML. Finally, configure a transparent palette for the QML rendering control, and then set its palette to that palette before giving it the QML to render and showing the window.