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.

Advertisements

[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?

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.

QtQuick: Why bother with QML?

December 21, 2011

This post is an introduction to the next several posts. If you’re bored with QtQuick introductions (since there are many)… then there’s probably nothing to see here.

Composite (and its ancestor Hydrogen) has always had a Qt UI. And when it comes to GUI, Qt is the toolkit that I prefer to use. Why Because I really don’t like UI development. I like to work on models, business logic, complex calculations, etc. I really get bored with sizing widgets and trying to figure out how to convince the font system to give you the real, actual, true bounding rectangle (and not the one that’s better for typesetting).

And then there’s the code. The options have always been something like:

  • Use a C API like GTK+ and write gobs of code and manually
    type all the gtk_widget_* namespaces for every class method.
  • Use a C++ API wripper like gtkmm (which never really gets
    rid of the smell of the C API).
  • Write gobs of Qt code to express your UI. Which means that
    small changes (like moving a widget from here to there) often
    require a lot of code changes.
  • Use a UI designer to create XML files that get processed into
    code. (I.e. Qt Designer or Glade.) While it’s easy, these
    files often have compatibility issues even across minor
    releases. They also are not very diff friendly (which is
    important to a backend-loving code monkey like me).
  • Write your UI in a scripting language like Tcl/Tk or Python
    (e.g. the PySide bindings). Here you trade-off flexibility
    for simplicity a lot of the time. (Think: custom widgets.)

These options all have one thing in common: they suck.

Along comes QtQuick, with the idea of expressing your UI in a scripting language called QML. QML is:

  • Like javascript and CSS: so it looks like code, and diff’s like code.
  • Declarative/Interpretive: so small changes just require that I refresh the QML file in question… not recompile (or even restart) my whole application.
  • Designer-friendly: if your project has an artist, they probably aren’t very good at C++. QML isn’t too difficult to parse… which makes them more productive.
  • Developer-friendly: as a C++ lover I’m not crazy about the syntax of QML… but I love that I can throw up a UI pretty quick. While the UI’s that I design look like butt… I don’t feel like I have invested a lot of code into something that needs to change (and change a lot) later in the project.
  • Architecture-friendly: It more or less enforces that you use a model/view architecture in your application. When the UI was code, it’s very tempting to pragmatically break the rules.
  • Prototype-friendly: You can add some logic into the QML via JavaScript, which means you can prototype the UI’s behavior without having to invest any C++ code.
  • Promising: The Qt framework is reorganizing itself around QML. It’s pretty fast now, but it promises to be blazing fast in Qt 5. (Even faster than using native widgets or QGraphicsView… and that’s an impressive accomplishment.)

However, it does come with a few drawbacks:

  • It’s oriented to embedded devices that have a fixed-size, full-screen “app” paradigm. Therefore, you’ll find that lots of sizes and relationships are set in fixed-size pixels. It’s not quite as nice as QLayout in C++. This means it’s still a little… primative in a desktop context.
  • The scripting language is kind of… strange. It is very weakly typed (e.g. inspecting the code you can’t be sure if a symbol is a property or a method or a signal or an element or a reference to a C++ class). This is really, really confusing.
  • The language uses a sort of “inner class” idiom like in Java.
  • It’s interpreted, so typos likemy_symbol/mySymbol will not be detected until run-time. (In fact, this is a drawback of Qt signals/slots as a whole.)
  • Concepts like QAction and QMenu are missing, and will probably never be added. They’ve been branded as obsolete, old thinking with respect to UI design. While I don’t necessarily disagree… the loss of them takes some getting used to.
  • It’s new, and the documentation is still developing. For example, none of the examples that I could find show any sort of in-depth C++/QML interaction. Most of the examples use JavaScript to implement the application logic.

All in all, I think that the good out-weighs the bad.

Since I don’t know how to use QML effectively, the following few blogs will share my journey and what I’ve learned. I’ll do so by writing a very simple calculator application.

That’s also the caveat: I’m writing this as I go… so I probably just said something dumb. 🙂