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.

Adding C++ Objects to your QML

There’s many times that you may want to access a C++ object from your QML. One obvious example is if you’ve already written a data model in C++; you can leverage that model with QML-based user interface. Any other time you need to access things you can only touch from C++, like hardware integration not provided by Qt Mobility, is another example.

As a simple example, consider a case where you want to expose a C++-based application controller (think model-view-controller) to your QML. You’ve got a class, AppController, with some Qt metaobject invocable methods, either expressed as properties, slots or using Q_INVOKABLE. Mine looks like this:

class AppController : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int width READ width NOTIFY sizeChanged)
    Q_PROPERTY(int height READ height NOTIFY sizeChanged)

public:
    explicit AppController(QObject *parent = 0);
    void setSize(const QSize& size) { mSize = size; emit sizeChanged(); };
    int width() { return mSize.width(); };
    int height() { return mSize.height(); };

   Q_INVOKABLE void magicInvocation();

signals:
    void sizeChanged();

private:
    QSize mSize;
};

Because QML binds using Qt’s metaobject system, the C++ object you want to expose to QML must be a descendant of QObject. Our AppController class is pretty simple; it’s just carrying the size of the window displaying the QML view, along with some C++ method my QML invokes named magicInvocation. (If I told you what it did… you get the idea.)

Of course, we need to fire up a QML viewer with our QML, and add an instance of AppController to the object hierarchy in the QML engine. I do that in my application’s main, which looks like this:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QRect screen = QApplication::desktop()->screenGeometry();
    QMainWindow* window = new QMainWindow(0, Qt::FramelessWindowHint );
    QDeclarativeView* view = new QDeclarativeView(window);
    AppController* controller(window);

    // The only thing we show is the declarative view.
    window->setCentralWidget(&view);

    // Size the window to be as big as it can be, except we don't
    // want it too big on our dev workstations.
    if (screen.width() <= kDesiredWidth) {
        window->showMaximized();
    } else {
        window->setGeometry(QRect(
            (screen.width()-kDesiredWidth)/2, (screen.height()-kDesiredHeight)/2, 
            kDesiredWidth, kDesiredHeight));
        window->show();
    }
    controller.setSize(window.size());

    // Proxy in our app controller so QML get its properties and show our QML
    view->rootContext()->setContextProperty("controller", controller);
    view->setSource(QUrl("qml/main.qml"));

    int result =  app.exec();
    delete window;
    return result;
}

Pretty basic stuff here:

  1. I get the screen size, used by the AppController for its own nefarious purposes.
  2. I create a full-screen main window with no window chrome.
  3. I create a QDeclarativeView to display the QML, and make the main window’s main widget the new QDeclarativeView.
  4. I create an instance of AppController.
  5. I do some funny stuff with the main window’s size so I don’t go crazy working on my desktop’s 22″ monitor, restricting the maximum possible size of the main window for test purposes.
  6. Using the QDeclarativeView‘s QDeclarativeEngine, I add the AppController instance to the QML context, giving it the name controller.
  7. I set the initial source of the QML to the QML entry point for my user interface, included as a file in my application’s package (not as a resource, but you could also choose to package it as a Qt application resource if you want.)
  8. Finally, I pass control to QApplication‘s main loop, and return its result code when its event loop exits.

The magic is QDeclarativeEngine::setContextProperty, which binds a QObject-derived instance to a specific name in the QML context. Once I do this, in my QML I can access this just as I would any QML or JavaScript object; its name is controller. So I might write controller.magicInvocation() to invoke my magic function in an onPressed signal handler, for instance.

(This is well-documented, but I found it handy to break this point out into a separate example to refer to. It’s also a predecessor for several upcoming posts, so it’s here so that those posts can refer back to this one.)

Compiling QtDBus for Windows…

I keep needing to do this. Each time I do, I have to rediscover how to do it.

Worse, I’m doing it slightly differently each time.

For interested parties, here’s what I just did for Qt 4.7.1. Worked for Qt 4.7.0, too.

I’m using MinGW, cmake, libexpat, and of course Qt. To begin, you need to build dbus itself for Windows, and then you can rebuild

  1. Make sure you have MinGW installed and correctly configured. Its bin directory should be in your path.
  2. Download cmake. Install.
  3. Download and install libexpat from http://www.winkde.org/pub/kde/ports/win32/repository-4.4/win32libs/(as suggested in the dbus documentation). You’ll want the lib archive. You’ll need the include directory no matter what for when you build QtDBus. Unpack these zips to C:\Program Files\win32libs.
  4. Grab dbus from the repository, put it in c:\dbus-1.4.0.
  5. Edit dbus-1.4.0\cmake\CMakeLists.txt, add
    set(LIBEXPAT_LIBRARIES "C:/Program Files/win32libs/lib/libexpat.lib")
    set(LIBEXPAT_INCLUDE_DIR "C:/Program Files/win32libs/include")
    set(LIBEXPAT_FOUND ON)
    

    after
    project(dbus)

  6. Start a Windows Command Prompt
  7. mdkir c:\dbus && cd c:\dbus
  8. cmake -G "MinGW Makefiles" ..\dbus-1.4.0\cmake -DDBUS_USE_EXPAT=on
  9. mingw32-make
  10. Add c:\dbus\bin to your path.
  11. Edit c:\qt\4.7.1\src\dbus\dbus.pro:
    win32 {
        wince*:LIBS_PRIVATE += -lws2
        else:LIBS_PRIVATE += -lws2_32 \
        -ladvapi32 \
        -lnetapi32 \
        -luser32
        CONFIG(debug, debug|release):LIBS_PRIVATE += -ldbus-1 -Lc:/dbus/bin
        else:LIBS_PRIVATE += -ldbus-1 -Lc:/dbus/bin
        INCLUDEPATH += c:/dbus-1.4.0 c:/dbus
    }
    
  12. You don’t need to build QtDBus separately to begin with the way I do here (step 13) — but it’s much faster to do that and know it’s working than it is to start the configure / mingw32-make step, walk away, and find out an hour later that it died someplace in the middle because you messed up the path to dbus or something.

  13. Start a Qt Command Shell.
  14. cd c:\qt\4.7.1\src\dbus && qmake && mingw32-make
  15. cd ..\.. && configure -dbus && mingw32-make

Your mileage may vary. I seem to remember fixing at least one compilation error in dbus at one point, but don’t remember precisely where or why. It wasn’t hard to do, though — something about a missing argument.

Stay tuned for some Qt tips!

Over the last several months, I’ve been really busy with my coauthor getting my latest book, Beginning Nokia Apps Development finished. Although not a project sponsored by my employer, it’s been exciting working on a book that connects so closely with what I do in the office day-to-day, and my management at Nokia Research Center has been very supportive of the effort, letting me do some of the last-minute edits and tweaks as the schedule got down to the wire. Now that we’re in the last phases of editing (galley proofs, anyone?), I’ve got more time to talk about what I’ve been doing with Qt on mobile devices and other stuff.

As a result, expect a slow but steady stream of posts here in the next several weeks, largely focused on small tips and tricks for working with Qt, especially the Qt Meta-Object Language (QML).

Finally… updates to the Mac OS X eReader scripts!

I’ve been using Kovid Goyal’s excellent Calibre application for a few months now to manage ebooks on my Sony eReader. It’s a great tool for managing my library of ebooks, and can also automatically download, convert, and install news from any RSS feed you throw at it. I’ve converted a lot of stuff I had in LIT format using it, and been very pleased with Kovid’s work overall.

At the same time, I’ve used Feedbooks as a source for ebooks, especially classic literature. Feedbooks provides all the formats you’d expect, and its ePub output looks really nice on the eReader. It also provides reading lists so I can tag content I want to download later, and has the makings of a social network for bookworms through those lists. I frequently download ePubs from Feedbooks and drop them on the eReader via Calibre, so I can keep both current events and literature on the device.

Last night, I took a look at the News section of Feedbooks, and was I impressed! They have a lot of RSS feeds they’re aggregating and formatting, and it looks great on the eReader. Sadly, since I’m using Mac OS X, I can’t use their News Stand application to automate content downloads when I attach the eReader.

But I liked the idea of getting the news formatted content straight from a server; not only would it be faster, but I wouldn’t have to either leave Calibre running or launch it every morning. Since automating the download and installation of Feedbooks news content is an extension of what I’d already done previously for PDF printing, I figured it was time to do a bit more hacking.
Continue reading Finally… updates to the Mac OS X eReader scripts!

Whither thee launchd?

Unfortunately, for a number of reasons, I’ve been far too busy to follow up my previous post to use Mac OS X’s launchd to detect when the Sony eReader connects. There’s a lot of good launchd stuff on the Web, though, and it promises to be pretty easy once I sit down and actually write a plist for it.

In the mean time, here’s a revised script for moving the printed PDF files from a spooling directory to the eReader on connection. The setup is the same as before; simply kick the script with a Folder Action.

#!/usr/bin/env sh
PATH=/opt/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
export PATH

readermain="/Volumes/PRS 700"
readercard="/Volumes/700 CARD"
spool="/Users/kf6gpe/.cups-pdf-spool"
pdfdest="$readercard/PDFs/"

growl()
{
    message="$*"
    which growlnotify > /dev/null
    if [ $? -eq 0 ]; then
        echo $message | growlnotify -t "eReader" -p -2 -a /Applications/Preview.app
    fi
}

if [ -f "/tmp/prs-busy" ]; then
    exit 0;
fi

if [ -d "$readercard" ]; then
    touch /tmp/prs-busy
    growl "Device detetected.";
    for f in `ls $spool/*.pdf`
    do
        cp $f "$pdfdest"
        if [ $? -eq 0 ]; then
            rm -f $f
            growl "Transferred `basename $f`."
        fi
    done
    growl "PDF transfer complete.";
    rm /tmp/prs-busy
else
    rm /tmp/pre-busy 2>&1 /dev/null
fi
exit 0

The changes are pretty self-explanatory. In brief:

  1. Have use Preview’s icon, in case Calibre isn’t installed.
  2. Only look for the eReader’s card; there’s really no reason to look for both volumes. 
  3. Use the lock file /tmp/prs-busy to ensure that the actual movement of the files doesn’t occur while it’s actually running. 

Of these changes, (3) was certainly the most important, because if you mounted another volume (say, a digital camera) while the script was doing things, weird things could happen. In practice, I never saw anything amiss, but this makes me sleep better at night.

Better printing to the Sony eReader using CUPS-PDF

After the recent work I’ve done integrating my Sony eReader with Mac OS X, I still wanted to be able to optimize page layout for the device. 

After some digging around and realizing that I don’t have ppdc I grabbed a PPD file for a generic Postscript printer and started hacking. 

Download ereader.ppd

The results work pretty well, although I see goofy margins from some applications such as TextMate.

Next up? Ditching the Folder Action script I wrote about yesterday in favor of launchd.

Improving Interaction Between the Sony eReader and Mac OS X

I have always read a ridiculous amount. Sadly, in recent years as I’ve taken greater advantage of resources such as the Association for Computing Machinery‘s Digital Library, what that really means is that I print a lot.  There’s a definite advantage to paper; you can stick it in your bag, or put your feet up and lean back more so than with a laptop or desktop PC. At the same time, I don’t archive what I read on paper; I either make notes citing the papers I’ve read, or if the paper is really important to me, I archive a copy of the PDF itself. As a result, my professional reading workflow is one of researching, printing, reading, and recycling the results. This is a tremendous waste of paper.

While I’ve tried various ebook solutions on PDAs over the years, I’ve never been particularly happy with PDF handling on mobile devices.  I’ve watched with interest the growing market for ebooks on devices including the Sony eReader and the Amazon Kindle, although until recently neither has had particularly good PDF handling either.

Last month I laid hands on a Sony eReader, and really liked what I saw. PDF presentation with later firmware in the PRS 505 is much improved over previous firmware releases, and both the PRS 505 and PRS 700 support Secure Digital cards. As USB devices, these products show up as a mass storage device when mounted on Mac OS X. As an added bonus, Kovid Goyal’s Calibre application supports both format translation and automatic harvesting of Web content from RSS feeds. I have to admit that it’s pretty spiffy to get up in the morning, grab the eReader from my desk and have the latest content from both The New York Times and The Economist at my fingertips. In conjunction with the large number of freely available books from Feedbooks, it’s a leisure reader’s dream.

Although the eReader’s appearance as a mass storage device in the Mac OS X Finder is useful, I quickly tired of downloading papers and manually copying them to the device. Intuitively, it seemed that what I wanted to be able to do was to treat the eReader as another printer.

Upon reflection, this idea is quite compelling. It doesn’t require me to change my work flow at all; moreover, there is something cool about the idea of an eInk device being directly accessible as a printer.

Continue reading Improving Interaction Between the Sony eReader and Mac OS X