Loving what you do

March 29, 2015

The years 2010 and 2011 were a major turning point in my life. I went from a career that I had grown to hate (Mechanical Engineering) to one that inspires me (Software Engineering). I’ve been very fortunate to be able to make this switch, and I still find myself inspired with the day job as an embedded Linux coder.

Though, in 2010-2011 my open-source presence seemed to take a nose-dive. At the start of 2010 I had grand plans for Composite and was making good progress on it. But for most of 2010 and 2011 I had taken on a 2nd job and couldn’t work on Composite at all. And in the summer of 2011 I got a day job with TI. When I wanted to to some extra hacking, it was more apropos to do work-related hacking than my own. I was just as interested with the problems at work as I was with hobby software.

And this is still true today.

However, it makes me sad that I more or less vanished from the open source community — because that was a place I really enjoyed being in. But with way device manufacturers approach IP these days, it more or less discourages community involvement because it’s an IP liability. So, I played it safe for a few years.

Will this change for me in 2015? I hope so. Today I moved Composite from gabe.is-a-geek.org to riggable.com (something I planned to do 4 years ago). I also want to make some progress on Composite — though I may need to re-think the strategy and motivations. The original motivation was mainly obvious, but there was also a subversive motivation of having a publicly available work that demonstrates my software skills (remember, I was trying to change careers at the time).

Also, Composite didn’t go as planned because it didn’t attract developers… neither front-end UI developers nor back-end audio developers. And I absolutely totally hate UI development. If you look at the commit log, things got really slow and unclear whenever I turned to the UI work. Looking to the future, I’ll need to scale back the “innovative UI” ideas in order for it to be something that _I_ can produce. (I’m all in to the “innovative back-end” stuff. :-))

Meanwhile, even Garage Band offers a lot of the features that Composite set out to do. So, there’s not much innovative in the idea any more. It’s now just a “me, too” project. An OSS alternative. So, it’s an open question as to whether Composite should even continue.

Anyway, I want to hammer some of this out in the coming year.

Usually after I hit “Publish” on a post like this, things get really heated up at work. So, I can’t really say what tomorrow will look like. Will Composite continue? Can’t say. Will I blog more? Dunno. Will I actually read my “Linux Audio Developers” mailing list mail? Who knows. But I’m taking a moment to reflect and also think about the future.

Advertisements

Handling UNIX/POSIX signals is hard. If you don’t know that they’re hard, then you aren’t paying attention. In Linux there’s a system call signalfd() that makes handling them much less error-prone and fits right in to a poll()-based event-loop. This article demonstrates why signals are hard, and how to use signalfd(2) with poll(2).[1]

The Problem

Why are signals hard? Suppose that a thread has mutex `m‘ locked. When your process receives the signal, the thread is interrupted to handle the signal. In the signal handler, you try to lock `m‘. Your program just deadlocked. The signal handler waits until `m‘ unlocked. But `m‘ is never unlocked because the thread holding the lock was suspended to handle the signal. In contrast, a multi-threaded program wouldn’t usually deadlock because both threads are still active (running).

Thus, signals programming is harder than multi-threaded programming.

If you read the man page for signal(7), it contains a list of functions that POSIX requires be “async-safe.” In other words, reentrant functions that don’t lock mutexes or semaphores. Functions that are safe to call from from a signal handler. If a function is not on the list, it could lead to deadlocks and undefined behavior. Note that functions like fprintf(), snprintf(), syslog(), etc. are not on the list.

Also, since you can’t lock a mutex, manipulating any global variables in your program is hard. However, it’s common for programmers to ignore this and illicitly do dangerous things in signal handlers (lock mutexes, use an un-safe function, write to variables without regard for atomicity). Usually they get away with it because signals are usually for exceptional (rare) situations — so they usually don’t hit the error space.

But after a developer learns how dangerous it is to do anything useful in a signal handler,[2] it’s not uncommon to see something like this:

volatile sig_atomic_t shutdown_program;

void signal_handler(int sig)
{
        if (sig == SIGTERM)
                shutdown_program = 1;
}

…and then let your program’s main loop poll the variable regularly to catch the shutdown signal. (See glibc’s recommendation)

Using signalfd(2) as a solution

The man page for signal(7) lists some synchronous solutions to this problem:

  1. sigwaitinfo(2), sigtimedwait(2), sigwait(3) – These suspend execution until a signal is received.  This is useful (for instance) if you have real work being done on threads, and you use your main() function exclusively to handle signals.
  2. signalfd(2) – Provides a file descriptor that will provide information about the signals when they occur.

The reason why I think signalfd(2) is cool is that you can make the file descriptor generate events for your main event loop. For example, if you use poll(2), ppoll(2), select(2), pselect(2), epoll(7), or the GLib GMainLoop… it’s easy to add file-descriptor-bound events to those loops.

Example using poll(2)

The code that follows is a simple example of how to use signalfd(2) with poll(2).  (Note: the man page for signalfd usually includes an example where you use read(2) to block until you get a signal.)  The procedure is as follows:

  1. Block the signals that you’re interested in. (Yes, block them.)
  2. Create the signalfd for the signals that you’re interested in.  (If you don’t block them, then signalfd won’t receive them.)
  3. When the file descriptor is ready to be read, read a `signalfd_siginfo’ structure from the file descriptor to get info about the signal.

Here’s an example:

/* Simple program using signalfd to watch for SIGINT
 * Compile: gcc -o main main.c
 * Execute: ./main
 * Warning: You must use kill(1) to terminate the program.
 *     Ctrl+C won't terminate it.
 */
#include <assert.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <sys/signalfd.h>
#include <unistd.h>

#define NFDS 1

int main(int argc, char* argv[])
{
        int err;
        sigset_t sigset;
        int fd;

        /* Create a sigset of all the signals that we're interested in */
        err = sigemptyset(&sigset);
        assert(err == 0);
        err = sigaddset(&sigset, SIGINT);
        assert(err == 0);
        err = sigaddset(&sigset, SIGHUP);
        assert(err == 0);

        /* We must block the signals in order for signalfd to receive them */
        err = sigprocmask(SIG_BLOCK, &sigset, NULL);
        assert(err == 0);

        /* Create the signalfd */
        fd = signalfd(-1, &sigset, 0);
        assert(fd != -1);

        /* This is the main loop */
        struct pollfd pfd[NFDS];
        int ret;
        ssize_t bytes;

        pfd[0].fd = fd;
        pfd[0].events = POLLIN | POLLERR | POLLHUP;

        for (;;) {
                printf("Waiting.\n");
                ret = poll(pfd, NFDS, -1);
                printf("I'm awake!\n");

                /* Bail on errors (for simplicity) */
                assert(ret > 0);
                assert(pfd[0].revents & POLLIN);

                /* We have a valid signal, read the info from the fd */
                struct signalfd_siginfo info;
                bytes = read(fd, &info, sizeof(info));
                assert(bytes == sizeof(info));

                unsigned sig = info.ssi_signo;
                unsigned user = info.ssi_uid;

                if (sig == SIGINT)
                        printf("Got SIGINT from user %u\n", user);
                if (sig == SIGHUP)
                        printf("Got SIGHUP from user %u\n", user);
        }

        return 0;
}

Try it out by running it and hitting `Ctrl+C’ or sending it a signal using kill(1). Because all our handling is happening in the main thread’s context, we don’t have hidden gotchas with respect to handling the signal.


[1] – Whenever you see `foo(2)’, that’s a reference to a man page that you can view by typing `man 2 foo’. The number is like saying “I want to read about `foo’ from chapter 2.” This is important because some man pages appear in more than one chapter (e.g. signal(2), signal(7)).

[2] – More precisely: “…in a signal handler that returns.”

Can you find the bug in the following code?

#include <pthread.h>

unsigned u = 0;

void* thread_main(void *arg)
{
        u = 1;
        return 0;
}

int main(int argc, char* argv[])
{
        pthread_t thread;
        pthread_create(&thread, 0, thread_main, &u);

        while (!u);

        pthread_join(thread, 0);
        return 0;
}

This is a simplified version of some test code I was writing. I was kicking off N threads and did a busy wait until all N threads were up and ready to go. Because of some function inlines, the compiler was accessing these thread variables directly… and eventually optimized them away.

If you compile this code with -O0 (using GCC), it will work fine.  If you compile it -O1, -O2, or -O3, then GCC will convert `while (!u)’ into:

0x00000000004004ea <+26>: mov 0x200b50(%rip),%eax # 0x601040 <u>
0x00000000004004f0 <+32>: test %eax,%eax
0x00000000004004f2 <+34>: jne 0x400500 <main+48>
0x00000000004004f4 <+36>: jmp 0x4004f4 <main+36>

Which is the assembly language equivalent of:

        if (!u) {
                for (;;);
        }

That is: GCC ignored the fact that the variable is a global (and could change in another thread), and it optimized away the check.  My first reaction is that this is a bug in GCC. However, after talking with some peers the consensus is that GCC is doing the right thing (whether I agree or not).

I know of 3 ways to fix the code…

Keyword `volatile’

You can declare `u’ as a volatile:

volatile unsigned u = 0;

By doing this, you tell the compiler to not optimize access to this variable at all.  The C and C++ standards imply that this would be used for things like memory-mapped hardware (meaning it could change unexpectedly).  Thus it is unclear if this is needed for multi-threaded programs.  It appears to be needed.

Memory barrier

You can place a memory barrier in the while() loop:

while (!u) __sync_synchronize(); /* GCC built-in instruction */

In a simplified sense, this will ensure that the cache is fully updated before continuing.  It is both a processor instruction and compiler directive.  It tells the compiler not to cache variable values across the barrier.

You don’t have to use __sync_synchronize() explicitly. Using a mutex, for instance, will cause a memory barrier to happen.

Call an `extern’ function (caveat emptor!)

If you call an `extern’ function inside the loop, then GCC doesn’t optimize away the `u’ check.

while (!u) usleep(1);

Notice that I said doesn’t, and not won’t or shouldn’t. I don’t know of any standard or convention that causes GCC to not optimize here.  Therefore, you should not depend on this behavior. Use one of the other methods.  However, this explains why this method usually works… because it’s usually written with a sleep() (or some other action) in the loop.

Conclusion

Some will say that the code was broken from the start because it didn’t access the integer through a mutex and that accessing integers isn’t always atomic. That’s not right. For one, on every platform that anyone cares about, reading or writing a single integer is atomic. But even if it isn’t, it’s usually 1 single bit that matters (0x00000000 and 0x00000001).  If you read bytes out of order it doesn’t matter because you’re only writing a 0 or a 1. Even if I read the new MSB and the old MSB-1… it doesn’t matter because they’re both 0x00. And cache synchronization usually doesn’t matter here, either. If I read a slightly out-of-date value, then I just go through the loop a couple extra times. Not a problem.

For the common case of using an integer as a lock-free mechanism to shut down a thread — `volatile’ is absolutely necessary. (The memory barrier is equivalent, but usually not portable.) This surprised me because I have been inundated by various sources that the keyword `volatile’ was not only The Wrong Thing — but it’s unnecessary unless you had some kind of deep interactions (e.g. memory-mapped hardware).

With that said, if you’re doing anything more complicated than this… do it right. Use mutexes and atomic operations. Lock-free algorithms are hard to get right, so don’t go there unless you’ve proven that you really need it. In other words, don’t optimize away the mutexes unless you really need to (and unless you’re ready for some tricky debugging).

Last time I talked about getting the X server to send XInput 2 multitouch events in your Qt program. In this post we decode them and send them to the widget of interest.

XIDeviceEvent

For TouchBegin, TouchUpdate, and TouchEnd the “cookie” in our XGenericEventCookie struct is a struct XIDeviceEvent. It looks like this:[1]

typedef struct {
    int           type;         /* GenericEvent */
    unsigned long serial;       /* # of last request processed by server */
    Bool          send_event;   /* true if this came from a SendEvent request */
    Display       *display;     /* Display the event was read from */
    int           extension;    /* XI extension offset */
    int           evtype;       /* TouchBegin, TouchEnd, ... */
    Time          time;         /* Timestamp of event */
    int           deviceid;     /* Device generating event */
    int           sourceid;     /* *actual* device that gen'd event */
    int           detail;       /* Touch ID / Finger ID */
    Window        root;         /* Window ID of root window (usu. 0) */
    Window        event;        /* Window ID of Window that should receive event */
    Window        child;        /* Window ID of Window where the event occured */
    double        root_x;       /* (x,y) coords in root window */
    double        root_y;
    double        event_x;      /* (x,y) coords in event window */
    double        event_y;
    int           flags;        /* misc modifiers, see spec */
    XIButtonState       buttons;
    XIValuatorState     valuators;
    XIModifierState     mods;
    XIGroupState        group;
} XIDeviceEvent;

Usually we’re only interested in event,[2] event_x, event_y, and detail. The detail allows you to track individual fingers. The XInput extension promises that you’ll always get this sequence for each finger:

TouchBegin -> [TouchUpdate -> ] TouchEnd

…that is to say, you’ll always get a TouchEnd event, even if some other window grabs the finger from you. Furthermore, the events you get are individual. The events for each finger come in independently… not all grouped together in one event.

Sending Events to a QWidget

If we decide to handle an event inside TApplication::x11EventFilter() then we do the handling immediately and return true. Handling it means sending it to the window. Fortunately, since the touch events give us a window ID, it’s pretty easy:

    XIDeviceEvent *de = (XIDeviceEvent*)xev->data;
    QEvent *qev = translate_to_some_kind_of_qevent(xideviceevent);
    QWidget *target = QWidget::find(de->event);
    return notify(target, qev);

The only trick is… what kind of event should we send it?

Translating Touch Events

Ideally, we would find a way to send QTouchEvent‘s to our QWidget. That way the widget logic uses only one touch API. And QTouchEvent is a fairly nice API… since the event maintains state of all fingers that are currently in play.

We can do this, but it would raise the ire of the Qt gods. QTouchEvent has a sub-class called QTouchEvent::TouchPoint that is intended to be used as a read-only object (the “write” methods are all marked “internal”).[3] So we do it at our own risk… which isn’t a great idea for your production/stable code.

As an alternative, we can create our own custom touch events that can be used by the QWidget. If it were me, I would make something that looks very similar to QTouchEvent.

Sending a custom QEvent

Both options are a bit of work to implement. Since this is a bit of a rabbit trail, I’m not going to create (or translate) the touch events. Instead, I’ll just send the widget a custom QEvent and call it a day. The event is pretty simple:

class TouchEvent : public QEvent
{
public:
    enum {
        TouchEventId = QEvent::User + 1
    };

    TouchEvent(int id);
    virtual ~TouchEvent();

    int id() { return m_id; }

private:
    int m_id;
};

With one critical piece:

TouchEvent::TouchEvent(int id) :
    QEvent((QEvent::Type)TouchEvent::TouchEventId),
    m_id(id)
{
}

If we don’t initialize QEvent with the event id… then it won’t return the right number for QEvent::type().

We’re careful to make sure that our event ID number is within the range that Qt has allocated: QEvent::User + 1. Note that all of the documentation says that our event number must be greater than QEvent::User. I suspect this is a mis-print, though.

Now, when we send the event it goes to virtual QWidget::event() we check the type and then cast it back to our original type:

bool ScribbleArea::event(QEvent *event)
{
    switch (event->type()) {
    case TouchEvent::TouchEventId:
    {
        TouchEvent *tev = static_cast(event);
        qDebug() << "Received TouchEvent #" <id();
    }   break;
    /* ... */

Conclusion

So, we didn’t meet our goal of getting fingerpaint refactored… but the end is in sight (and I’m stopping so that I can move on to larger goals). For Composite I will probably stick with Ubuntu 12.04’s patch for as long as I can get away with it… instead of rolling my own XInput 2 code. If I really need to add the support, I will probably go ahead and cheat… using the private API of QTouchEvent::TouchPoint.

References

I dropped the code <a href=”http://gabe.is-a-geek.org/blog_content/2012/07/xinput2-part2/. See the previous post for links to docs.


[1] – source: /usr/include/X11/extensions/XInput2.h, with extra annotation from the protocol spec added by me.

[2]root is the root window of the entire X Display… and the value is usually 0. child is usually None, but sometimes if the event actually happened in a window that is a child of the event window, but they have a relationship that redirects the event.

[3] – See /usr/include/qt4/QtGui/qevent.h

So last time, I discovered that Ubuntu 12.04 has patched Qt to support multitouch. But since Qt doesn’t support MT on X11, I’m considering having a layer that allows me to either use Qt’s MT events or to handle XInput 2 events. Like last time… we start with Qt’s fingerpaint application and try to get it working with XInput 2.

To do this, we’re going to sub-class QApplication so that we can re-implement the virtual function QApplication::x11EventFilter().  This allows us to see, inspect, handle, intercept, or pass on any events that the X server sends us. We’ll call our class TApplication and make the function print something out when we get on of XInput’s touch events XI_TouchBegin, XI_TouchUpdate, and XI_TouchEnd:

bool TApplication::x11EventFilter(XEvent *ev)
{
    bool handled = false;
    XGenericEventCookie *cookie;

    // All XI2 events are GenericEvents
    if (ev->type != GenericEvent)
        return false;

    cookie = &ev->xcookie;

    if (cookie->extension != m_xi_opcode)
        return false;

    /**********************************************
     * BEYOND THIS POINT ARE ONLY XINPUT EVENTS
     **********************************************/

    switch (cookie->evtype)
    {
    case XI_TouchBegin:
        qDebug() << "XI_TouchBegin";
        break;
    case XI_TouchUpdate:
        qDebug() << "XI_TouchUpdate";
        break;
    case XI_TouchEnd:
        qDebug() << "XI_TouchEnd";
        break;
    default:
        qDebug() << "XI_somethingelse";
    }

    return handled;
}

Are you going to eat that cookie?

You might ask, “what’s the deal with the cookie?”  An XEvent isn’t actually a C struct, it’s a union of all the different event structs that the X sever uses.  All of them have a ‘type‘ as their first member so that you can discern what kind of struct this is.  We’re only interested in the type GenericEvent.  For these events it means that the ‘ev‘ pointer is a pointer to an XGenericEventCookie struct.  When we do ‘cookie = ev->xcookie‘, it’s equivalent to ‘cookie = (XGenericEventCookie*)ev‘ — but much more elegant.

The GenericEvent is the event type used for extensions to the X Server — things that weren’t thought of when they originally set up the specification.  While the XGenericEventCookie struct has some useful info… what you really want is the data in its ‘data‘ member.  Normally we have to call XGetEventData(), but in Ubuntu’s Qt it already fetches (and frees) it for us.  (See [2] for more details.)

Taking Responsibility

Because Ubuntu is patched with XInput 2 support… it was kind enough to set up everything for us in the background. For example, it tells the X server that we want touch events, pre-fetches our cookie, and then cleans up our cookie crumbs afterwards. However, since we can’t always depend on our mommy (Ubuntu) looking out for us… we need to be Big Boys and set up all the background stuff for ourselves.  We start by disabling multitouch from Qt in the ScribbleArea:

 ScribbleArea::ScribbleArea(QWidget *parent)
     : QWidget(parent)
 {
-    setAttribute(Qt::WA_AcceptTouchEvents);
+    /*setAttribute(Qt::WA_AcceptTouchEvents);*/
     setAttribute(Qt::WA_StaticContents);

If you re-run the application after doing this, you won’t see any X11 events. All events that you want to receive from the X server must be explicitly enabled (even mouse clicks). There is no notion of ‘cascading’ events. Every window. No exceptions. You can’t just set these flags to your top-most parent window and have all the windows get the events. Every single window has to do this for itself.[1]

So instead of setAttribute(), we call our own function:

    TApplication::sendTouchEventsTo(this);

And inside that function we declare that we want touch events for this window:

/* static */
void TApplication::sendTouchEventsTo(QWidget *w)
{
    XIEventMask mask;
    Window win;
    Display *dpy;

    dpy = QX11Info::display();
    win = w->winId();

    memset(&mask, 0, sizeof(XIEventMask));
    mask.deviceid = XIAllMasterDevices;
    mask.mask_len = XIMaskLen(XI_LASTEVENT);
    mask.mask = (unsigned char*) calloc(mask.mask_len, sizeof(char));

    XISetMask(mask.mask, XI_TouchBegin);
    XISetMask(mask.mask, XI_TouchUpdate);
    XISetMask(mask.mask, XI_TouchEnd);

    XISelectEvents(dpy, win, &mask, 1);

    free(mask.mask);
}

This must be run for every widget that you want to have touch events. Using setAttribute() more or less does the same thing.

Notice that we got the Window ID from the widget so that we could give that back to the XServer. The documentation for QWidget::winId() says:

Portable in principle, but if you use it you are probably about to do something non-portable. Be careful.

…This value may change at run-time. An event with type QEvent::WinIdChange will be sent to the widget following a change in window system identifier.

…and they’re not joking, either.  From the time that this code is run in the constructor to the time you receive your first touch event… the window ID changes twice for me.  In fact, I think this may be part of the reason why the pre-compiled fingerpaint demo will sometimes work and sometimes not.  So, obeying the docs, we handle the event:

bool ScribbleArea::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::WinIdChange:
    {
        TApplication::sendTouchEventsTo(this);
    }   break;
    /* ... */

Wrapping Up

You can download the code in the file xinput2-part1.tar.bz2 (or browse it here).  Next time we’ll talk about decoding the events and sending them to our widget.

Resources


[1] – …and every QWidget is an X11 Window (or two or three). That’s one advantage to QGraphicsView and QtQuick — they’re typically all contained in a single window.

[2] – The Qt convention seems to be that XGetEventData() is called before entering QApplication::x11EventFilter() and that XFreeEventData() is called in QApplication::x11ProcessEvent(). This is what the Ubuntu patch does, too. However, I also wrote the code to handle the non-Ubuntu-patched case. See tapplication.cpp for detailed comments in the code.

Qt does not support for XInput 2.2’s multitouch protocol.[1] So instead of whining, I approached it as an opportunity to learn XInput2 and possibly give a more awesome MT experience in Composite. Qt allows you to catch and handle raw X11 events using QApplication::x11EventFilter().

As a starting project, I took the Qt fingerpaint demo to make it work with XInput2. With Ubuntu 12.04:

$ sudo apt-get install xinput libxi-dev qt4-demos

N.B. I already have Qt4 tool and -dev packages and a bunch of stuff installed, so YMMV.

Next, using my Lenovo Ideapad, let’s confirm that fingerpaint is broken:

$ /usr/lib/qt4/examples/touch/fingerpaint/fingerpaint

And contrary to what I expected… multitouch works!! However, sometimes it works and sometimes it doesn’t.[2] Usually if you wait until “Fingerpaint” appears in the title bar you’re good to go.

As it happens, Ubuntu 12.04 has a patch that enables Qt’s multitouch. 🙂

So next, I compiled the Ivory application that I did for MeeGo. And:

Got touch without getting TouchBegin for id 58
Got touch without getting TouchBegin for id 58
Got touch without getting TouchBegin for id 58
Got touch without getting TouchBegin for id 58
Got touch without getting TouchBegin for id 58
Got touch without getting TouchBegin for id 58

Turns out that this is Ubuntu bug #1007847 and is easily fixed like this:

diff --git a/src/Application.cpp b/src/Application.cpp
index 05e1a92..4fc590b 100644
--- a/src/Application.cpp
+++ b/src/Application.cpp
@@ -59,7 +59,7 @@ namespace Ivory
         setFrameShape(QFrame::NoFrame);

         setScene(_scene);
-        setViewport(new QGLWidget);
+        setViewport(new QWidget);

         _scene->set_octave(0);
         QStringList ports;

…and it works!

This isn’t the blog I expected to write — so it’s been a pleasant surprise.  However, I am still interested in doing raw XInput processing… so I’ll still explore that in the next blog.


[1] – …as far as I know. Last I heard the status was that there was no support (nor plans to support) XInput 2.2 or anything else new in X11.  (…but patches welcome.)

[2] – I think I have an idea why it sometimes doesn’t work. More on that next time.

[cross roads]This series has been fun and frustrating, but leaves me a bit worried about using QtQuick/QML for the UI of Composite.  While a lack of personal free time was one reason for the 4-month delay between posts… other things bothered me:

  1. I didn’t expect that I would have to roll my own QML version of QTableView.
  2. The way data is passed between C++ and QML is still fuzzy to me.
  3. Internet literature on QML is heavily slanted towards Javascript implementations rather than C++.
  4. Searching for “qml sucks” lands a lot of people to this blog. (I’m the #1 hit! …ironic, since that article speaks well of QML.)
  5. In Qt5, QtQuick sees the most changes and I wonder what porting C++ components will be like.
  6. I don’t really like GUI coding, and am inclined to stick with what I know (things like QWidget).

But these things make me want to keep on with QML:

  1. I’m going to need lots of custom widgets.  With Qt5 I want to be able to port the drawing logic as fast as possible.  It looks like the QML-written parts should port quickly.
  2. I like the prospect of benefiting from QtQuick’s new SceneGraph implementation.
  3. QML makes it easier for users to re-skin the UI without serious coding-fu (a big deal for hipster musicians)
  4. My 2nd choice is QGraphicsView — and I’m not sure my troubles will be much easier there.

Anyway… it’s time for Composite to start moving again, and currently this (in)decision is the major showstopper.

What’s your opinion? What would you do?

When last we left our hero, we set up a simple (but not very scalable) Model/View using a C++ calculator engine and a QML UI. The “big” problem with it when you think of scalability is the fact that the UI is static. If the Engine wants to add buttons or change the function/role of the buttons — the UI has to be updated by a programmer. Instead, the UI should query query the Engine (or an abstraction of the Engine) to know the button layout.

In C++, we could do this by implementing a QAbstractTableModel and feed it to a pre-made (and extensible) widget like QTableView. In QML, the Model/View
options are less mature. For example, there is nothing in QML that is like a QTableView.[1] Therefore, we have to create our own.

Our strategy will be to create new classes ButtonLayoutModel (derived from QAbstractTableModel) and GridView (our custom QML Component). Our custom GridView will replace Qt’s Grid inside of Calculator.qml, and ButtonLayoutModel will be placed between Engine and the GridView. The messages passed will now be based on a “button ID” instead of the text that appears on the face of the button.

ButtonLayoutModel and Engine

The new class ButtonLayoutModel is pretty simple.

Excerpt of ButtonLayoutModel.hpp:

class ButtonLayoutModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    ButtonLayoutModel(QObject *parent = 0);
    ~ButtonLayoutModel();

public:
    /* Reimplemented virtual methods */
    virtual int columnCount(const QModelIndex& parent = QModelIndex()) const;
    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
    virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
    void setEngine(Engine *eng) {
        m_engine = eng;
    }

public slots:
    void buttonPressed(int id);

private:
    Engine *m_engine;

}; // class ButtonLayoutModel

Pretty simple… we implement only what we have to from QAbstractTableModel, an interface to set the underlying model, and a slot that makes things a little easier later.

Non-trivial excerpt of ButtonLayoutModel.cpp:

int ButtonLayoutModel::columnCount(const QModelIndex& /*parent*/) const
{
    return m_engine->columns();
}

int ButtonLayoutModel::rowCount(const QModelIndex& /*parent*/) const
{
    return m_engine->rows();
}

void ButtonLayoutModel::buttonPressed(int button_id)
{
    m_engine->event(button_id, Engine::E_BUTTON_CLICK);
}

QVariant ButtonLayoutModel::data(const QModelIndex& index, int role) const
{
    int button_id;

    button_id = m_engine->button_id(index.row(), index.column());
    switch (role) {
    case Qt::DisplayRole:
        return m_engine->button_text(button_id);
        break;
    case Qt::UserRole:
        return QVariant(button_id);
        break;
    }
    return QVariant();
}

The less obvious part here is how ButtonLayoutModel::data() works. We need to pass 2 different kinds of data from the Engine to the UI for layout: (a) the text to
display on the button (role == Qt::DisplayRole) and (b) the button’s ID (role == Qt::UserRole). While this doesn’t buy us much now… it could allow for the text on the button to be totally different than the buttons function (e.g. a non-ascii symbol, an icon, etc).

This requires some different support from Engine. Here are the changes:

diff -Nurp a/Engine.hpp b/Engine.hpp
--- a/Engine.hpp	2012-02-19 20:38:39.000000000 -0600
+++ b/Engine.hpp	2012-07-04 11:21:52.263084183 -0500
@@ -37,10 +37,28 @@ public:
         M_DIV,
     } mode_t;

+    typedef enum {
+        E_NONE = 0,
+        E_BUTTON_CLICK = 1,
+    } event_t;
+
     QString get_display();

+    int columns() {
+        return 4;
+    }
+    int rows() {
+        return 4;
+    }
+    /* zero-offset for row and col */
+    int button_id(int row, int col) {
+        return row*columns() + col;
+    }
+    QString button_text(int button_id);
+
 public slots:
     void keypress(QString val);
+    void event(int button_id, int event);

 signals:
     void content_changed(QString val);

diff -Nurp a/Engine.cpp b/Engine.cpp
--- a/Engine.cpp
+++ b/Engine.cpp
@@ -38,6 +38,42 @@ QString Engine::get_display()
     return m_display;
 }

+void Engine::event(int button_id, int ev)
+{
+    if (ev == E_BUTTON_CLICK) {
+        QString text = button_text(button_id);
+        keypress(text);
+    }
+    return;
+}
+
+QString Engine::button_text(int button_id)
+{
+    switch (button_id) {
+    case 0: return QString("7"); break;
+    case 1: return QString("8"); break;
+    case 2: return QString("9"); break;
+    case 3: return QString("+"); break;
+
+    case 4: return QString("4"); break;
+    case 5: return QString("5"); break;
+    case 6: return QString("6"); break;
+    case 7: return QString("-"); break;
+
+    case 8: return QString("1"); break;
+    case 9: return QString("2"); break;
+    case 10: return QString("3"); break;
+    case 11: return QString("*"); break;
+
+    case 12: return QString("C"); break;
+    case 13: return QString("0"); break;
+    case 14: return QString("="); break;
+    case 15: return QString("/"); break;
+    }
+    return QString();
+}
+
+
 void Engine::keypress(QString val)
 {
     qDebug() << "Got keypress " << val;

Nothing special here. Just added the start of an event-based system.

GridView

For this next part, let’s clarify three concepts for this specific part of the project:

  • Model – The abstract interface to the core application logic (e.g. the data of a spreadsheet).
  • View – A container that provides a graphical representation of the contents of the Model (e.g. the rows and columns that you see in a spreadsheet).
  • Delegate – A graphical widget that is used to display and interact with an specific item in the Model (e.g. the widget used for each cell in the spreadsheet).

The View must be given two things: a Model and a Delegate. For every item in the Model, it will create a new instance of the Delegate for interacting with the model. In our case, the Model is the ButtonLayoutModel presented above, and the Delegate is the Button QML element.

And since our UI is in QML, our View must be a QML Element.

Here’s the meat of GridView.hpp:

class GridView : public QDeclarativeItem
{
    Q_OBJECT
    Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged);
    Q_PROPERTY(QDeclarativeComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged);
    Q_PROPERTY(float margin READ margin WRITE setMargin);

public:
    GridView(QDeclarativeItem* parent = 0);
    ~GridView();

    QVariant model() const;
    void setModel(const QVariant& mod);

    QDeclarativeComponent* delegate() const;
    void setDelegate(QDeclarativeComponent* del);

    float margin() const;
    void setMargin(float m);

signals:
    void modelChanged();
    void delegateChanged();
    void cellWidthChanged();
    void cellHeightChanged();

private:
    QAbstractItemModel *m_model;
    QDeclarativeComponent *m_delegate;
    float m_margin;

protected slots:
    /* Slots to be handled QAbstractItemModel */
    void columnsInserted(const QModelIndex& parent, int start, int end);
    void columnsMoved(const QModelIndex& sourceParent, int sourceStart, int sourceEnd,
                      const QModelIndex& destinationParent, int destinationColumn);
    void columnsRemoved(const QModelIndex& parent, int start, int end);
    void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
    void headerDataChanged(Qt::Orientation orientation, int first, int last);
    void layoutChanged();
    void modelReset();
    void rowsInserted(const QModelIndex& parent, int start, int end);
    void rowsMoved(const QModelIndex& sourceParent, int sourceStart, int sourceEnd,
                   const QModelIndex& destinationParent, int destinationRow);
    void rowsRemoved(const QModelIndex& parent, int start, int end);

protected:
    virtual void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry);

private:
    void update_layout();

}; // class GridView

Most of this is scaffolding and functions that were required to be re-implemented. The most important part is the properties added at the top: model, delegate, and margin. These translate exactly to QML properties and will be passed to us at run-time from the QML script.

One of the tricky parts is getting/setting the model:

QVariant GridView::model() const
{
    return *reinterpret_cast<QVariant*>(m_model);
}

Note: I think using reinterpret_cast<> is a mistake and that it should be some manner of qvariant_cast<> like in setModel(). (I don’t recall, it’s been a few months since I wrote this part!)

#define M_CONNECT(sender, sig) connect((sender), SIGNAL(sig), this, SLOT(sig))
#define M_DISCONNECT(sender, sig) connect((sender), SIGNAL(sig), this, SLOT(sig))

void GridView::setModel(const QVariant& model)
{
    QObject *obj;
    QAbstractItemModel *mm;
    std::cout << __func__ << std::endl;

    obj = qvariant_cast<QObject*>(model);
    mm = qobject_cast<QAbstractItemModel*>(obj);

    if (!mm) {
        std::cerr << "Can not set model... is not a QAbstractItemModel" << std::endl;
        return;
    }

    if (m_model) {

        M_DISCONNECT(m_model,
                     columnsInserted(const QModelIndex&, int, int));
        M_DISCONNECT(m_model,
                     columnsMoved(const QModelIndex&, int, int, const QModelIndex&, int));
        M_DISCONNECT(m_model,
                     columnsRemoved(const QModelIndex&, int, int));
        M_DISCONNECT(m_model,
                     dataChanged(const QModelIndex&, const QModelIndex&));
        M_DISCONNECT(m_model,
                     headerDataChanged(Qt::Orientation, int, int));
        M_DISCONNECT(m_model,
                     layoutChanged());
        M_DISCONNECT(m_model,
                     modelReset());
        M_DISCONNECT(m_model,
                     rowsInserted(const QModelIndex&, int, int));
        M_DISCONNECT(m_model,
                     rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int));
        M_DISCONNECT(m_model,
                     rowsRemoved(const QModelIndex&, int, int));
    }

    m_model = mm;

    M_CONNECT(m_model,
              columnsInserted(const QModelIndex&, int, int));
    M_CONNECT(m_model,
              columnsMoved(const QModelIndex&, int, int, const QModelIndex&, int));
    M_CONNECT(m_model,
              columnsRemoved(const QModelIndex&, int, int));
    M_CONNECT(m_model,
              dataChanged(const QModelIndex&, const QModelIndex&));
    M_CONNECT(m_model,
              headerDataChanged(Qt::Orientation, int, int));
    M_CONNECT(m_model,
              layoutChanged());
    M_CONNECT(m_model,
              modelReset());
    M_CONNECT(m_model,
              rowsInserted(const QModelIndex&, int, int));
    M_CONNECT(m_model,
              rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int));
    M_CONNECT(m_model,
              rowsRemoved(const QModelIndex&, int, int));

    update_layout();
    emit modelChanged();
}

I.e. the tricky part is the casting, and then we connect all the signals and slots to our own model. Notice, too, that there is nothing here that is specific to our ButtonLayoutModel.

void GridView::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
{
    qDebug() << oldGeometry << " ==> " << newGeometry;
    setImplicitWidth(newGeometry.width());
    setImplicitHeight(newGeometry.height());
    update_layout();
}

void GridView::update_layout()
{
    QObjectList kids = children();
    int rows, cols, r, c;
    float x, y, margin;
    float cell_height, cell_width;
    QObject *obj;
    QModelIndex ix;

    if (!m_model || !m_delegate)
        return;

    /* clear out existing children */
    foreach(obj, kids) {
        obj->setParent(0);
        delete obj;
    }

    rows = m_model->rowCount();
    cols = m_model->columnCount();
    cell_height = implicitHeight() / rows;
    cell_width = implicitWidth() / cols;
    margin = GridView::margin();

    y = margin/2;
    for (r = 0 ; r < rows ; ++r) {
        x = margin/2;
        for (c = 0 ; c < cols ; ++c) {
            obj = m_delegate->create();
            obj->setParent(this);
            obj->setProperty("parent", QVariant::fromValue<QDeclarativeItem*>(this));
            obj->setProperty("x", x);
            obj->setProperty("y", y);
            obj->setProperty("width", cell_width - margin);
            obj->setProperty("height", cell_height - margin);
            ix = m_model->index(r, c);
            obj->setProperty("text", m_model->data(ix, Qt::DisplayRole));
            obj->setProperty("button_id", m_model->data(ix, Qt::UserRole));
            connect(obj, SIGNAL(buttonPressed(int)),
                    m_model, SLOT(buttonPressed(int)));
            x += cell_width;
        }
        y += cell_height;
    }

}

This update_layout() function deletes all the delegates and then allocates new ones every time it is called. Obviously this is not ideal, but it was simple to implement. However, after creating each delegate:

  • It sets its location and size (layout) via properties
  • It sets some properties that are specific to our application… button_id and text. Note that these are very specific to our Button.qml implementation.
  • It connects a signal to the m_model slot. This, too, is very specific to our Button and ButtonLayoutModel implementations.

Most of the functions not shown simply call update_layout() — not ideal, but it works for now. 🙂

Changes to Button

Button almost works as-is. The only change is to add the ‘button_id’ property and change the signal to send an integer instead of text.

diff -Nurp a/Button.qml b/Button.qml
--- a/Button.qml
+++ b/Button.qml
@@ -10,6 +10,7 @@ import QtQuick 1.0
 Item {
     /* This should be set by the parent element */
     property string text: "X"
+    property int button_id: 0

     /* These should EXIST in the parent element, or be set
      * BY the parent element.  They are referred to by the
@@ -26,9 +27,9 @@ Item {
     property int radius: (height < width) ? height/8 : width/8;

     /* This signal will fire when we get clicked, and contain the
-     * text of the button.
+     * id
      */
-    signal postValue(string val);
+    signal buttonPressed(int id);

     /* Main geometry of button */
     Rectangle {
@@ -57,7 +58,7 @@ Item {
 	id: iMouseArea;
 	anchors.fill: parent;
 	onClicked: {
-	    postValue(text);
+	    buttonPressed(button_id);
 	}
     }
 }

Changes to Calculator

In Calculator we do the following:

  • Declare our custom components as ‘Local’
  • Remove all our hard-coded button stuff
  • Replace Grid with Local.GridView
  • Set up a delegate (Button) for the GridView
diff -Nurp a/Calculator.qml b/Calculator.qml
--- a/Calculator.qml
+++ b/Calculator.qml
@@ -2,9 +2,11 @@
  */

 import QtQuick 1.0
+import "." as Local
+import Foo 1.0 as Local

 Rectangle {
     id: iRoot;
@@ -54,43 +56,29 @@ Rectangle {
 	}
     }

-    Grid {
-	columns: 4;
-	property int button_width: (width - 2 * anchors.margins)/columns;
-	property int button_height: button_width;
-	property color button_color_top: "#8888FF";
-	property color button_color_bot: "blue";
+    Component {
+	id: iButtonDelegate;
+
+	Button {
+	    
+	}
+    }
+
+    Local.GridView {
+	id: button_grid;
+	property color button_color_top: "white";
+	property color button_color_bot: "gray";
 	property color text_color: "black";
-	spacing: parent.anchors.margins;
+
 	anchors.top: iDisplay.bottom;
 	anchors.left: iRoot.left;
 	anchors.right: iRoot.right;
 	anchors.bottom: iRoot.bottom;
 	anchors.margins: parent.anchors.margins;

-	/* Row 0 */
-	Button { id: i7; text: "7"; }
-	Button { id: i8; text: "8"; }
-	Button { id: i9; text: "9"; }
-	Button { id: iPlus; text: "+"; }
-
-	/* Row 1 */
-	Button { id: i4; text: "4"; }
-	Button { id: i5; text: "5"; }
-	Button { id: i6; text: "6"; }
-	Button { id: iMinus; text: "-"; }
-
-	/* Row 2 */
-	Button { id: i1; text: "1"; }
-	Button { id: i2; text: "2"; }
-	Button { id: i3; text: "3"; }
-	Button { id: iMultiply; text: "*"; }
-
-	/* Row 3 */
-	Button { id: iC; text: "C"; }
-	Button { id: i0; text: "0"; }
-	Button { id: iEquals; text: "="; }
-	Button { id: iDivide; text: "/"; }
+	model: button_model;
+	delegate: iButtonDelegate;
+	margin: 7;

     }

@@ -151,25 +139,4 @@ Rectangle {
             break;
 	}
     }
-
-    /* Component.onCompleted() is more or less a constructor */
-    Component.onCompleted: {
-	/* N.B. Using 'iRoot.' here is redundant. */
-	iC.postValue.connect(iRoot.data);
-	iPlus.postValue.connect(iRoot.data);
-	iMinus.postValue.connect(iRoot.data);
-	iMultiply.postValue.connect(iRoot.data);
-	iDivide.postValue.connect(iRoot.data);
-	iEquals.postValue.connect(iRoot.data);
-	i0.postValue.connect(iRoot.data);
-	i1.postValue.connect(iRoot.data);
-	i2.postValue.connect(iRoot.data);
-	i3.postValue.connect(iRoot.data);
-	i4.postValue.connect(iRoot.data);
-	i5.postValue.connect(iRoot.data);
-	i6.postValue.connect(iRoot.data);
-	i7.postValue.connect(iRoot.data);
-	i8.postValue.connect(iRoot.data);
-	i9.postValue.connect(iRoot.data);
-    }
 }

Putting it all together

In order to use our ‘Local’ QML Component library, we must declare it early in our main() function. We also need to plug our model into the Calculator QML.

diff -Nurp a/main.cpp b/main.cpp
--- a/main.cpp	2012-02-19 20:38:39.000000000 -0600
+++ b/main.cpp	2012-07-04 09:36:43.527800857 -0500
@@ -19,9 +19,12 @@

 #include <QtGui/QApplication>
 #include <QtDeclarative/QDeclarativeView>
+#include <QtDeclarative/QDeclarativeContext>

 #include "Delegate.hpp"
 #include "Engine.hpp"
+#include "ButtonLayoutModel.hpp"
+#include "GridView.hpp"

 int main(int argc, char* argv[])
 {
@@ -29,8 +32,17 @@ int main(int argc, char* argv[])
     QDeclarativeView view;
     Engine engine;
     Delegate del;
+    ButtonLayoutModel blm;
+    QDeclarativeContext *ctxt;

+    qmlRegisterType<GridView>("Foo", 1, 0, "GridView");
+
+    blm.setEngine(&engine);
+
+    ctxt = view.rootContext();
+    ctxt->setContextProperty("button_model", &blm);
     view.setSource(QUrl::fromLocalFile("Calculator.qml"));
+
     del.set_view(&view);
     del.set_engine(&engine);
     del.init();

And once again we have our underwhelming calculator working. 🙂

Critical Thinking

Again, what are the shortcomings of what we’ve done?

  • GridView still needs special knowledge of both the Model and the View in order for this to work. On the one hand it would be best if GridView were a perfectly pure container (like QTableView) — on the other hand a semi-specialized container can also have its benefits.
  • The GridView is automatically connecting signals and slots. This is typically bad form and totally circumvents the Delegate class that was originally set up for this kind of thing. It might have been better for the delegate to connect its
    signals to the parent (Calculator, channeling all the events through that interface.
  • The way that data is handled between the Button delegate and the Model seems a little clunky. It might have been better to pass the Button special access to the Model to figure out what data it needs. But I’m not sure how to do
    this since Button is a QML object. And again, this is simply an attempt to make GridView fully generic.
  • But when you get right down to it… I’m still not happy with the fact that I had to write my own GridView.

While some of the interactions in this example are a tad hack-ish, we’ve accomplished the goal of having the core logic in the Engine and having the UI adapt as the Engine changes.

Resources

All of the sources can be found at http://gabe.is-a-geek.org/blog_content/2012/07/04-qml-cpp-link/ The tarball calculator.tar.bz2 has everything… but you’ll also find individual files.


The code in this article is a mixture of Public Domain code and
GPL code. Please see the files in the Resources section for
specifics.

[1] – There is a Grid Element, but it expects
the underlying model to be 1D, and it lays them out in a 2D array. We
need the underlying model to be 2D. In general, QML “Models” are all
1D. There might be some things that could be used in Qt Desktop
Components or even the MeeGo Components… but the documentation on
those is sparse and you would have to compile them yourself.

QtQuick: QML-to-C++ Link

January 9, 2012

Last time we set up a QML scaffolding for a Calculator UI. The top-level UI set up the following signals and slots to communicate with the Calculator engine:

  • data(string val) – (UI to Engine) communicates which button is pushed
  • contentChanged(string val) – (Engine to UI) indicates that the display text has changed

So, our application is going to be architectured like this:

  • View – The QML UI
  • Model – The calculator Engine
  • main() – This will glue everything together

The Engine Class

For the purpose of communicating with the QML UI… the main content of class Engine is:

class Engine : public QObject
{
    Q_OBJECT

    /* ... */

public slots:
    void keypress(QString val);

signals:
    void content_changed(QString val);

    /* ... */
};

Everything else is just implementation of the logic. When the user presses a button, it is communicated to the engine via the keypress(QString) slot. Whenever the engine decides that the UI’s display should be changed, it communicates it with the content_changed(QString) signal.

See the link at the end for the full implementation, but the calculator is implemented as a state machine. The current operation is the current state (or “mode”). It changes behavior based on the current mode and whether or not the mode has recently changed. However, since the focus is C++/QML interaction… we won’t go into the Engine details.

main(): Making Connections

To glue together the Engine and the UI, we need to set things up inside the main() function:

/*
 * main.cpp (calculator)
 *
 * This code was written by Gabriel M. Beddingfield <gabrbedd@gmail.com>
 * in 2011-2012.  It is placed in the public domain.
 */

#include <QtGui/QApplication>
#include <QtGui/QGraphicsObject>
#include <QtDeclarative/QDeclarativeView>

#include "Engine.hpp"

int main(int argc, char* argv[])
{
    QApplication qapp(argc, argv);
    QDeclarativeView view;
    Engine engine;

    view.setSource(QUrl::fromLocalFile("Calculator.qml"));

    /* This sets up the connections between the view and engine */
    {
        QObject *v = view.rootObject();
        QObject *e = &engine;

        QObject::connect(v, SIGNAL(data(QString)),
                         e, SLOT(keypress(QString)));
        QObject::connect(e, SIGNAL(content_changed(QString)),
                         v, SLOT(contentChanged(QString)));
        QObject::connect(v, SIGNAL(quit()),
                         &view, SLOT(close()));
    }

    view.show();

    return qapp.exec();
}

This simply sets up an Engine, sets up the QML View (QDeclarativeView), and then sets up the signals and slots.

There’s two Gotcha!‘s in the code:

  1. Connections to the QML UI are not made with the QDeclarativeView, but with QDeclarativeView::rootObject() (which has type QGraphicsView).
  2. Because of the QGraphicsView object, you need to include the <QGraphicsView> header,[1] or else your compiler will give you strange errors about casting.
  3. The close() slot, however, is in the QDeclarativeView. In this case we connect our QML UI to its QDeclarativeView container.

QML Changes

On the QML side of things, there’s a few changes necessary in order to get the ball rolling.

  • Remove scaffolding: The stuff that we marked as “THIS IS TEMPORARY” last time needs to be removed since we have a bona fide Engine, now.
  • Button changes: Our implementation last time didn’t really have enough buttons to even be a simple “adding only” calculator. We’ve added more buttons and rearranged them.
  • Keyboard accelerators: With the loss of things like QAction, what do we do for touch-typing-junkies like me?

The first two (scaffolding and buttons) is pretty easy:

--- 00.Calculator.qml
+++ 01.Calculator.qml
@@ -10,25 +10,18 @@
 Rectangle {
     id: iRoot;
     color: "gray";
-    width: 200; height: 310;
+    width: 200; height: 252;
     anchors.margins: 5;
     property string text: "0";

     signal data(string val);
     onData: {
 	console.log("iRoot: " + val)
-	/* XXX THIS IS TEMPORARY */
-	contentChanged(val);
     }

     signal contentChanged(string val);
     onContentChanged: {
-	/* XXX THIS IS TEMPORARY */
-	if (text == "0") {
-	    text = val;
-	} else {
-	    text += val;
-	}
+	text = val;
     }

     signal clear;
@@ -59,8 +52,8 @@
     }

     Grid {
-	columns: 3;
-	property int button_width: (width - 2 * anchors.margins)/3;
+	columns: 4;
+	property int button_width: (width - 2 * anchors.margins)/columns;
 	property int button_height: button_width;
 	property color button_color_top: "#8888FF";
 	property color button_color_bot: "blue";
@@ -76,29 +69,37 @@
 	Button { id: i7; text: "7"; }
 	Button { id: i8; text: "8"; }
 	Button { id: i9; text: "9"; }
+	Button { id: iPlus; text: "+"; }

 	/* Row 1 */
 	Button { id: i4; text: "4"; }
 	Button { id: i5; text: "5"; }
 	Button { id: i6; text: "6"; }
+	Button { id: iMinus; text: "-"; }

 	/* Row 2 */
 	Button { id: i1; text: "1"; }
 	Button { id: i2; text: "2"; }
 	Button { id: i3; text: "3"; }
+	Button { id: iMultiply; text: "*"; }

 	/* Row 3 */
 	Button { id: iC; text: "C"; }
 	Button { id: i0; text: "0"; }
-	Button { id: iPlus; text: "+"; }
+	Button { id: iEquals; text: "="; }
+	Button { id: iDivide; text: "/"; }

     }

     /* Component.onCompleted() is more or less a constructor */
     Component.onCompleted: {
 	/* N.B. Using 'iRoot.' here is redundant. */
-	iC.postValue.connect(iRoot.clear);
+	iC.postValue.connect(iRoot.data);
 	iPlus.postValue.connect(iRoot.data);
+	iMinus.postValue.connect(iRoot.data);
+	iMultiply.postValue.connect(iRoot.data);
+	iDivide.postValue.connect(iRoot.data);
+	iEquals.postValue.connect(iRoot.data);
 	i0.postValue.connect(iRoot.data);
 	i1.postValue.connect(iRoot.data);
 	i2.postValue.connect(iRoot.data);

We simply deleted some stuff, added the extra buttons, and plugged up the internal signal/slot connections. Actually, these changes alone will give you a fine, functioning calculator.

But support for keystroke entry is important to people like me. That’s one reason why I really miss QAction. However, it’s not terribly difficult to add in support for keystrokes in our calculator, using the Keys element:

--- 01.Calculator.qml
+++ Calculator.qml
@@ -13,6 +13,7 @@
     width: 200; height: 252;
     anchors.margins: 5;
     property string text: "0";
+    focus: true; /* for keyboard input */

     signal data(string val);
     onData: {
@@ -29,6 +30,8 @@
 	text = "0";
     }

+    signal quit;
+
     Rectangle {
 	id: iDisplay;
 	color: "white";
@@ -91,6 +94,64 @@

     }

+    Keys.onPressed: {
+	switch (event.key) {
+	case Qt.Key_0:
+            iRoot.data("0");
+            break;
+	case Qt.Key_1:
+            iRoot.data("1");
+            break;
+	case Qt.Key_2:
+            iRoot.data("2");
+            break;
+	case Qt.Key_3:
+            iRoot.data("3");
+            break;
+	case Qt.Key_4:
+            iRoot.data("4");
+            break;
+	case Qt.Key_5:
+            iRoot.data("5");
+            break;
+	case Qt.Key_6:
+            iRoot.data("6");
+            break;
+	case Qt.Key_7:
+            iRoot.data("7");
+            break;
+	case Qt.Key_8:
+            iRoot.data("8");
+            break;
+	case Qt.Key_9:
+            iRoot.data("9");
+            break;
+	case Qt.Key_C:
+            iRoot.data("C");
+            break;
+	case Qt.Key_Plus:
+            iRoot.data("+");
+            break;
+	case Qt.Key_Minus:
+            iRoot.data("-");
+            break;
+	case Qt.Key_Asterisk:
+            iRoot.data("*");
+            break;
+	case Qt.Key_Slash:
+            iRoot.data("/");
+            break;
+	case Qt.Key_Equal:
+	case Qt.Key_Enter:
+	case Qt.Key_Return:
+            iRoot.data("=");
+            break;
+	case Qt.Key_Escape:
+            iRoot.quit();
+            break;
+	}
+    }
+
     /* Component.onCompleted() is more or less a constructor */
     Component.onCompleted: {
 	/* N.B. Using 'iRoot.' here is redundant. */

We simply set up a look-up table, so that when a key is pressed it does the same thing as if you had pushed a button. We’ve also mapped the Escape key to a quit signal (which we connect in main()).

Critical Thinking

But if we sit down and think about this Calculator implementation… what are its shortcomings? (And remember, we’re focusing on code relationships… not how sexy it looks.)

  • The UI and Engine communicate through an ASCII protocol. We know that this doesn’t scale well, nor does it lend itself to flexibility or internationalization. Nor does it handle the addition or removal of buttons (e.g. for different calculator modes).
  • The Engine always communicates its entire state to the UI. This state is passed via signal/slot parameters. As the state gets larger, this will bog things down. (I.e. this doesn’t scale well, either.)
  • The connection between the UI and Engine are managed by main(), where we manually connected all the signals and slots. This is a bit error-prone and is not dynamic.

Can you think of any other shortcomings in this architecture? We’ll talk about them in following blog posts.

Resources

calculator.tar.bz2 — All the code (tarball).

calculator.pro — The QMake project file

Calculator.qml — The QML UI

Button.qml — The Button Component

Engine.hpp — The Engine declaration

Engine.cpp — The Engine implementation

main.cpp — The main module

[1] You might notice that my headers are <QtGui/QGraphicsObject>, including the module. I’ve found that this makes things a little easier when you’re not using QMake. For example, CMake 2.8 doesn’t currently support configuration for the QDeclarative module. Adding the module to the header makes that a little easier.

This is not going to be a tutorial on QML. There are plenty of those. (See [2])

However, this will discuss some of the problems I encountered while implementing a calculator application. The purpose of this application is /not/ to make a calculator. The purpose is to explore C++/QtQuick interactions as they would happen in a large project.

So, the goal is to implement this ugly little calculator:

We’ll implement as much of it as we can in QML, but we won’t actually make it functional. (E.g. button presses with do “something”… but not necessarily the “right” thing.) So, here’s a quick scaffolding:[1]

/*                                      -*- mode:javascript -*-
 * Calculator.00.qml - a scaffolding for a calculator UI.
 *
 * This code was written by Gabriel M. Beddingfield <gabrbedd@gmail.com>
 * in 2011.  It is placed in the public domain.
 */

import QtQuick 1.0

Rectangle {
    id: iRoot;
    color: "gray";
    width: 200; height: 310;
    anchors.margins: 5;
    property string text: "0";

    Rectangle {
	id: iDisplay;
	color: "white";
	anchors.top: parent.top;
	anchors.left: parent.left;
	anchors.right: parent.right;
	anchors.margins: parent.anchors.margins;
	height: 42;
	radius: height/10;

	Text {
	    id: iDisplayText;
	    text: iRoot.text;
	    color: "black";
	    font.pixelSize: 24;
	    anchors.verticalCenter: parent.verticalCenter;
	    anchors.right: parent.right;
	    anchors.margins: 10;

	}
    }

    Grid {
	columns: 3;
	property int bw: (width - 2 * anchors.margins)/3;
	property int bh: bw;
	spacing: parent.anchors.margins;
	anchors.top: iDisplay.bottom;
	anchors.left: iRoot.left;
	anchors.right: iRoot.right;
	anchors.bottom: iRoot.bottom;
	anchors.margins: parent.anchors.margins;

	/* Row 0 */
	Rectangle { id: i7; width: parent.bw; height: parent.bh; }
	Rectangle { id: i8; width: parent.bw; height: parent.bh; }
	Rectangle { id: i9; width: parent.bw; height: parent.bh; }

	/* Row 1 */
	Rectangle { id: i4; width: parent.bw; height: parent.bh; }
	Rectangle { id: i5; width: parent.bw; height: parent.bh; }
	Rectangle { id: i6; width: parent.bw; height: parent.bh; }

	/* Row 2 */
	Rectangle { id: i1; width: parent.bw; height: parent.bh; }
	Rectangle { id: i2; width: parent.bw; height: parent.bh; }
	Rectangle { id: i3; width: parent.bw; height: parent.bh; }

	/* Row 3 */
	Rectangle { id: iC; width: parent.bw; height: parent.bh; }
	Rectangle { id: i0; width: parent.bw; height: parent.bh; }
	Rectangle { id: iPlus; width: parent.bw; height: parent.bh; }
    }
}

This is something that anyone should be able to do after going through one of the QtQuick tutorials.[2] So, if this part makes your head spin, then you should go visit a more basic tutorial first.

You may notice that I’m using Hungarian notation for all of the Item id’s. (E.g. “id: iRoot;”). The reason is that this id name can be used anywhere, any time to refer to this object. Even in another QML file. I find this really confusing, and the “i” helps clear things up pretty quick.[3]

Abstracting the Buttons

Every button on the calculator has several things in common:

  • They should all have an identical style.
  • They all represent one thing (like the number ‘5’)
  • When the user presses them, we want an event to notify us. Otherwise, we don’t really care how the button works.
  • We know that we need at least 12 of them.

If you’ve done Qt programming, you might think, “Easy… just use a QPushButton.” However, there is not such animal in QML. So right off the bat, we’re needing to make a custom widget. At first this may seem like a pain, but the truth is that in C++ we would also need a custom widget. We just wouldn’t write it until later because it’s extra work.

The design is pretty simple. We want the button to configure itself and send us a signal when it gets clicked. In other words here’s all we really care about:

Item {
    property string text: "X"

    signal postValue(string val);

    /* ...*/
}

Beyond that, we don’t really care what it does or how it works, right? The rest of it is really internal. So a no-frills button would look like this. Note: it’s very important that the file be named “Button.qml”. It’s not just the file-name, that’s how you name your custom element. Here’s the implementation:

/*                                      -*- mode:javascript -*-
 * Button.qml
 *
 * This code was written by Gabriel M. Beddingfield <gabrbedd@gmail.com>
 * in 2011.  It is placed in the public domain.
 */

import QtQuick 1.0

Item {
    /* This should be set by the parent element */
    property string text: "X"

    /* These should EXIST in the parent element, or be set
     * BY the parent element.  They are referred to by the
     * child elements.
     */
    height: parent ? parent.button_height : 42;
    width: parent ? parent.button_width : 42;
    property color button_color_top: parent ? parent.button_color_top : "white";
    property color button_color_bot: parent ? parent.button_color_bot : "gray";
    property color text_color: parent ? parent.text_color : "black";

    /* These properties may be set by the parent element */
    property int font_pixel_size: 24;
    property int radius: (height < width) ? height/8 : width/8;

    /* This signal will fire when we get clicked, and contain the
     * text of the button.
     */
    signal postValue(string val);

    /* Main geometry of button */
    Rectangle {
	id: iRectangle;
	anchors.fill: parent; /* parent is iRoot */
	radius: parent.radius;

	gradient: Gradient {
	    /* parent.button_color_* is implied */
	    GradientStop { position: 0.0; color: button_color_top; }
	    GradientStop { position: 1.0; color: button_color_bot; }
	}
    }

    /* Text display of button */
    Text {
	id: iText;
	text: parent.text; /* without 'parent.' this would be ambiguous */
	color: text_color; /* parent.text_color implied */
	font.pixelSize: font_pixel_size;
	anchors.centerIn: parent;
    }

    /* This enables mouse interaction with button */
    MouseArea {
	id: iMouseArea;
	anchors.fill: parent;
	onClicked: {
	    postValue(text);
	}
    }
}

This implementation shouldn’t be too much of a surprise. Here’s a few things to note:

  • The base thing is Item. The Rectangle is actually an implementation detail. By making the base an Item, we’re free to (for example) rotate the Rectangle 90° in order to get a left-to-right gradient. Now the Item is a meta-object the contains mostly meta-data (including the signal).
  • Every button needs an identical height, width, color, etc. Instead of copying the code over and over in Calculator.qml, I have the Button query the parent object for what these properties should be.
  • QML’s scoping rules allow us to access the parent’s properties and variables without qualifying them with “parent.”. However, in some cases this is ambiguous and we have to explicitly reference ‘parent.’ Whether you always use “parent” or not is a matter of style.
  • When the MouseArea “clicked” signal fires, we override its “onClicked” method to fire our own custom signal. We need to do this so that the button’s value can be sent in the signal.

Try out Button.qml using the qmlviewer test app:

    $ qmlviewer Button.qml

Putting the Buttons in the Calculator

Now we plug it in to our calculator. We also add a ‘color’ property to the grid to make it easier for the buttons to auto-configure themselves.[4] Here’s the changes we make:

--- Calculator.00.qml
+++ Calculator.01.qml
@@ -1,5 +1,5 @@
 /*                                      -*- mode:javascript -*-
- * Calculator.00.qml - a scaffolding for a calculator UI.
+ * Calculator.01.qml - Uses the Button element.
  *
  * This code was written by Gabriel M. Beddingfield <gabrbedd@gmail.com>
  * in 2011.  It is placed in the public domain.
@@ -38,8 +38,11 @@

     Grid {
 	columns: 3;
-	property int bw: (width - 2 * anchors.margins)/3;
-	property int bh: bw;
+	property int button_width: (width - 2 * anchors.margins)/3;
+	property int button_height: button_width;
+	property color button_color_top: "#8888FF";
+	property color button_color_bot: "blue";
+	property color text_color: "black";
 	spacing: parent.anchors.margins;
 	anchors.top: iDisplay.bottom;
 	anchors.left: iRoot.left;
@@ -48,23 +51,23 @@
 	anchors.margins: parent.anchors.margins;

 	/* Row 0 */
-	Rectangle { id: i7; width: parent.bw; height: parent.bh; }
-	Rectangle { id: i8; width: parent.bw; height: parent.bh; }
-	Rectangle { id: i9; width: parent.bw; height: parent.bh; }
+	Button { id: i7; text: "7"; }
+	Button { id: i8; text: "8"; }
+	Button { id: i9; text: "9"; }

 	/* Row 1 */
-	Rectangle { id: i4; width: parent.bw; height: parent.bh; }
-	Rectangle { id: i5; width: parent.bw; height: parent.bh; }
-	Rectangle { id: i6; width: parent.bw; height: parent.bh; }
+	Button { id: i4; text: "4"; }
+	Button { id: i5; text: "5"; }
+	Button { id: i6; text: "6"; }

 	/* Row 2 */
-	Rectangle { id: i1; width: parent.bw; height: parent.bh; }
-	Rectangle { id: i2; width: parent.bw; height: parent.bh; }
-	Rectangle { id: i3; width: parent.bw; height: parent.bh; }
+	Button { id: i1; text: "1"; }
+	Button { id: i2; text: "2"; }
+	Button { id: i3; text: "3"; }

 	/* Row 3 */
-	Rectangle { id: iC; width: parent.bw; height: parent.bh; }
-	Rectangle { id: i0; width: parent.bw; height: parent.bh; }
-	Rectangle { id: iPlus; width: parent.bw; height: parent.bh; }
+	Button { id: iC; text: "C"; }
+	Button { id: i0; text: "0"; }
+	Button { id: iPlus; text: "+"; }
     }
 }

The calculator doesn’t do anything yet, but it now looks the way that we want it. We simply replaced the Rectangle items with our custom Button elements.

I’ve not yet decided if using parent.button_height and friends is good design or not, but it at least saves a bunch of copy-and-paste.

Try out Calculator.01.qml using the qmlviewer test app:

      $ qmlviewer Calculator.01.qml

Note that you must also have Button.qml in the same folder.

Prototyping the interface

To get this data to our C++ app, we’ll need to set up the signals and slots that we’ll use to communicate with the core model of the application. What we need is:

  • A signal from the UI to the engine when a button is pressed, and which will carry the data that the button represents. We’ll call the signal ‘data
  • A signal from the engine to the UI to that contains the display text. We’ll call the signal ‘contentChanged
  • A signal from the UI to the engine when the “clear” button has been pressed. We’ll call the signal ‘clear
  • Something that sets up a connection from each button to the main ‘data‘ signal.

Let’s start with just the data signal. When you declare a signal (named ‘foo‘) then there is automatically a slot created called ‘onFoo‘).[5] Not only that, but if the signal was declared with an argument, the argument (and its name) is assumed when you implement the slot. So, for data:

    signal data(string val);
    onData: {
	console.log("iRoot: " + val)
    }

    /* ... */

    /* Component.onCompleted() is more or less a constructor */
    Component.onCompleted: {
	iPlus.postValue.connect(data);
	i0.postValue.connect(data);
	i1.postValue.connect(data);
	i2.postValue.connect(data);
	i3.postValue.connect(data);
	i4.postValue.connect(data);
	i5.postValue.connect(data);
	i6.postValue.connect(data);
	i7.postValue.connect(data);
	i8.postValue.connect(data);
	i9.postValue.connect(data);
    }

This is pretty simple… create the signal ‘data‘, on the slot log which button was pushed, and connect the signal/slot in the Component.onCompleted constructor-like-slot.

So, we finish out the design by implementing them all. All the slots will do something, just so that we get an indication that they are firing correctly. Building on top of Calculator.01.qml:

--- Calculator.01.qml
+++ Calculator.qml
@@ -1,5 +1,5 @@
 /*                                      -*- mode:javascript -*-
- * Calculator.01.qml - Uses the Button element.
+ * calculator.qml
  *
  * This code was written by Gabriel M. Beddingfield <gabrbedd@gmail.com>
  * in 2011.  It is placed in the public domain.
@@ -14,6 +14,28 @@
     anchors.margins: 5;
     property string text: "0";

+    signal data(string val);
+    onData: {
+	console.log("iRoot: " + val)
+	/* XXX THIS IS TEMPORARY */
+	contentChanged(val);
+    }
+
+    signal contentChanged(string val);
+    onContentChanged: {
+	/* XXX THIS IS TEMPORARY */
+	if (text == "0") {
+	    text = val;
+	} else {
+	    text += val;
+	}
+    }
+
+    signal clear;
+    onClear: {
+	text = "0";
+    }
+
     Rectangle {
 	id: iDisplay;
 	color: "white";
@@ -69,5 +91,23 @@
 	Button { id: iC; text: "C"; }
 	Button { id: i0; text: "0"; }
 	Button { id: iPlus; text: "+"; }
+
+    }
+
+    /* Component.onCompleted() is more or less a constructor */
+    Component.onCompleted: {
+	/* N.B. Using 'iRoot.' here is redundant. */
+	iC.postValue.connect(iRoot.clear);
+	iPlus.postValue.connect(iRoot.data);
+	i0.postValue.connect(iRoot.data);
+	i1.postValue.connect(iRoot.data);
+	i2.postValue.connect(iRoot.data);
+	i3.postValue.connect(iRoot.data);
+	i4.postValue.connect(iRoot.data);
+	i5.postValue.connect(iRoot.data);
+	i6.postValue.connect(iRoot.data);
+	i7.postValue.connect(iRoot.data);
+	i8.postValue.connect(iRoot.data);
+	i9.postValue.connect(iRoot.data);
     }
 }

The temporary code is just to make the Calculator semi-functional, until we put the real brains into it. Try it out like this:

      $ qmlviewer Calculator.qml

Note that we could extend the prototype by adding JavaScript application logic (like several of the Qt Quick examples). However, since the goal is to connect a C++ engine to a QML UI, this would be a waste of time.

Summary

You might be asking, “where’s the C++ code?” We’ll do that next time. Hopefully, you can already see that we’ve set up a well-defined interface that we can use to connect our calculator brains to.

The biggest things that I learned in this part of the exercise are:

  • When making a custom component, it is generally better to use ‘Item’ as the base element of your component.
  • Use the Component.onCompleted method do internal signal/slot connections and any other init code.
  • Signals in QML will also implicitly create a slot. The slot function has some special “magic” in the way it is declared and over-ridden.
  • The scope of variables, properties, and id’s is kind of sloppy. Judicious use of Hungarian notation and object dereferences can make the code more readable.

Source Files

Button.qml — The Button abstraction.

Calculator.00.qml — The first Calculator iteration (layout)

Calculator.01.qml — The second Calculator iteration (using Button)

Calculator.qml — The final Calculator

[1] See the file 00_scaffolding.qml

[2] See Getting Started Programming with QML and Qt Quick …which also contains links to examples.

[3] “thingamajig.trigger()” — what the heck is “thingamajig???”

[4] I could have done it with width/height, too.

[5] If you, like me, don’t like camelCasing, getUsedToIt.