Wiki

Meet Qt Jambi

by Gunnar Sletta

Meet Qt Jambi, a prototype technology that empowers Java developers with a high-performance cross-platform software development framework. Qt Jambi makes Qt's C++ libraries available to the Java community. Compared to other Java frameworks such as Swing and SWT, Qt Jambi has an intuitive and powerful API.

In addition, Qt Jambi provides the Java User Interface Compiler (JUIC), its own tailored version of Qt's user interface compiler. JUIC makes it possible for Java developers to take the advantage of Qt Designer, Trolltech's tool for designing and building graphical user interfaces from Qt components, by converting Qt Designer's user interface definition files to Java source files.

In this article we will take a closer look at Qt Jambi to see how the Qt framework is integrated with Java. We will do this by writing a small example application. The application is a game called Qtanoid, inspired by the arcade classic of a similar name, where the player scores points by using a paddle to bounce a ball against a brick wall in an attempt to demolish it brick by brick. We will also see how to use Qt Designer and JUIC to create the application's user interface.

Jambi-Game

The current Qt Jambi release is the third technology preview. It is built on Qt 4.2, providing access to new Qt features like the Graphics View framework which we will use when writing our game.

Building a User Interface with Qt Designer

The Qtanoid game's user interface is created using Qt Designer, but it is fully possible to write the entire user interface by hand in Java, just as we can with C++. The user interface contains three different elements, a label showing the score, a button to start the game, and a display where all the action happens.

The display is implemented by subclassing the QGraphicsView class. When using Qt Designer to create our user interface, this custom widget is unavailable to us. But it is still possible to include the widget in the user interface definition by "promoting" one of Qt's regular widgets as a placeholder for it on the form we create in Qt Designer.

So, when designing the user interface, we put a QGraphicsView widget onto the form, Then we open a context menu over it and click Promote to Custom Widget. In the dialog that opens, we specify the name of our subclass: QtanoidView in this case.

Jambi-Promotingcustomwidgets

When JUIC is run, it generates code for a QtanoidView object instead of the placeholder QGraphicsView widget. This is a very convenient way of putting custom widgets into user interfaces as long as you don't need to preview them, set up signal and slot connections, or edit your custom widget's properties. It also allows you to design a user interface that incorporates a component which has not yet been written.

Generating Source Code

Let's look at the Qtanoid user interface again. Using Qt Designer we have created the user interface and stored it as Qtanoid.ui. To get Java code from this .ui file we must run JUIC, typically from the command line like this:

        juic -cp .

The -cp option tells JUIC to recursively traverse the given directory, in this case the current directory, and generate source code for all the .ui files it finds. The generated classes are put into a Java package derived from the location of the .ui files. Basically the -cp option is a convenience option to update all .ui files in your project. It is also possible to integrate JUIC into build systems like ant, running the tool on a per-file basis. Likewise, the Qt Jambi Eclipse integration has JUIC integrated as a custom build tool which regenerates the class files whenever the .ui files change.

The output from JUIC tell us that we have generated a source file called Ui_Qtanoid.java that is located in the current directory. (For simplicity, we use the default package in this example.)

The generated component is used in a similar way to C++ code produced by uic: The generated component is a class with a setupUi() method that initializes the user interface. In the source file for our game (Qtanoid.java), we load the user interface like this:

    import com.trolltech.qt.gui.*;

Qt Jambi is organized after the standard Java naming convention of com.companyname.product.module, making QtGui accessible in com.trolltech.qt.gui. We add a * to import all the QtGui classes.

    public class Qtanoid 
        public static void main(String[] args) {
            QApplication.initialize(args);

In the main() method, we initialize the application by calling the QApplication.initialize() method, which corresponds to the C++ QApplication constructor. In Qt Jambi this method is static because it represents a common Java usage pattern.

            QWidget widget = new QWidget();
            Ui_Qtanoid ui = new Ui_Qtanoid();
            ui.setupUi(widget);

To create the main application widget and the user interface inside it, we instantiate QWidget and Ui_Qtanoid objects, and call the Qtanoid.setupUi() method, passing the widget as its argument.

The code so far will bring up the user interface, but to enable user interaction, we must also set up some signal and slot connections to get things happening.

We connect the start button's clicked() signal to the startGame() method in the QtanoidView class. The second connection is from the custom scoreChanged signal in the QtanoidView class to the score label, telling the label to update itself whenever the score changes.

            ui.startButton.clicked.connect(ui.graphicsView,
                                           "startGame()");
            ui.graphicsView.scoreChanged.connect(ui.score, 
                                             "setNum(int)");
            widget.show();
            QApplication.exec();
        }
    }

Finally, all that is left to do is to show the widget and call the QApplication.exec() method to run the event loop.

As we can see from the above code, a Qt Jambi signal is actually an object, and we make the connect() call using one of the signal's methods, passing the receiver and slot signature as arguments. This makes the pattern used to make connections slightly different to the corresponding pattern in C++.

Custom Widget Plugins
Readers experienced with Qt Designer are probably familiar with the custom widget plugin interface, which is an alternative way of making custom widgets available in Qt Designer. It is possible to use plugins to create "live" custom widgets in Qt Designer's forms &emdash; custom widgets can be previewed like any other Qt widget, and they support signal and slot connections as well as property editing.

In Qt Jambi, this feature is currently under development, and we aim to provide the same support for custom Qt Jambi widgets. In addition, the Qt Jambi team is working on a Qt Designer language plugin, making it possible to view various widget characteristics like properties and signals and slots using Java syntax rather than the C++ syntax used today.

Building the Game

So far we have looked at part of the toolchain, and seen how simple Java code can be using Qt Jambi. Now, let's dig a little bit deeper.

    public class QtanoidView extends QGraphicsView {

As we mentioned earlier, the QtanoidView class is derived from the QGraphicsView class. Making use of QGraphicsView saves us quite a bit of coding since we don't have to handle painting, user interaction, collision detection, and other game-related activities.

        public Signal1<Integer> scoreChanged = new Signal1<Integer>();

Our QGraphicsView subclass defines a scoreChanged signal using the Integer type to notify interested parties that the game score has changed. Qt Jambi uses a generics-based approach to signals: Our signal is an instance of the Signal1 class because it has only one argument (the score as an integer) &emdash; signals with more arguments are defined using Signal2, Signal3, and so on. This approach enables us to do type-checking at compile time for signal emissions.

Qt Jambi signal and slot connections also support Java's autoboxing feature, making it possible to connect signals of the Integer object type to slots taking the primitive type int. In fact, this is exactly what our connection in the main method does, connecting the scoreChanged signal to the setNum() slot in QLabel.

Here's the constructor for the QtanoidView class:

        public QtanoidView(QWidget parent) {
            super(parent);
 
            QLinearGradient gradient = new QLinearGradient(0, 1000, 0, 0);
            QBrush brush = new QBrush(gradient);
            setBackgroundBrush(brush);
 
            setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff);
            setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff);
 
            setFixedSize(sizeHint());
        }

The QtanoidView constructor follows the default Qt constructor pattern, accepting a parent argument that is passed on to the base class constructor. Here, we set some basic properties, such as the appearance of the widget's background and scrollbar policies. Qt Jambi makes use of the Java 1.5's enum features, so Qt.ScrollBarPolicy is an enum type and ScrollBarAlwaysOff is an enum value. Using this feature, Qt Jambi ensures that methods accepting enum arguments are verifiable at compile time.

In Qt Jambi, slots are just normal methods. Signal and slot connections are implemented completely in Java (based on the reflection API), which means that there is no need for a custom moc step or a Q_OBJECT macro. This makes Qt Jambi's signals and slots a lot easier to work with than the C++ equivalents. Qt Jambi also provides code that makes it possible to connect C++ signals to Java slots and vice versa.

In the main method we connected the start button to the startGame() slot. We define this slot here:

        public void startGame() {
            if (loop != null) {
                gameOver();
            }
 
            lastHitTime = System.currentTimeMillis();
 
            score = 0;
            scoreChanged.emit(0);
            setupGame();

The interesting part of the above code is the statement emitting the scoreChanged() signal. As you can see, emit() is also a method of the signal object, notifying all its registered slots. Note that, even though the scoreChanged signal expects an object of the Integer class as argument, you can pass it a primitive integer relying on Java's autoboxing feature to convert it to an Integer object containing the given value.

When starting a game, we first ensure that any running game is stopped, then we reset the score counter and last hit time. The delay between each hit is used to calculate the score, so we store the current time as a baseline. The setupGame() method creates the various game elements which are QGraphicsPixmapItem objects. We will cover these in detail later.

We want to run the game loop in a separate thread, calling back into the GUI thread for updates whenever required. The GameLoop class is a QObject subclass that implements the Runnable interface. This means that its code can be executed in a different thread.

            loop = new GameLoop();
 
            Thread thread = new Thread(loop);
            loop.moveToThread(thread);

To have proper event and threaded signals and slots handling for our GameLoop object, we move it to the newly-constructed thread. Note that this thread object is not a QThread object but a built-in Java Thread object. Qt Jambi merges Qt's QThread and Java threads to get the benefits from both.

One neat feature of Java threads is the daemon concept. Normally, any thread that is running will keep the virtual machine alive even though the main method has exited. Daemon threads will not do this, and since we want the application to exit when the user closes the game window, we make the game loop thread a daemon thread:

            thread.setDaemon(true);

The GameLoop class has one signal, nextFrame, which is emitted each time an update of the game state is required; i.e. when the ball moves, the player moves and whenever collisions may occur. We connect our game loop's signal to the three different actions that we must handle for each frame.

            loop.nextFrame.connect(this, "moveBall()");
            loop.nextFrame.connect(this, "movePlayer()");
            loop.nextFrame.connect(this, "checkCollisions()");
 
            thread.start();
        }

With the connections set up, we start the game loop's thread.

Since our GameLoop object now resides in the game loop thread and the QtanoidView object resides in the GUI thread, we must have queued connections between the nextFrame signal and the receiving slots. Like in C++, all Qt Jambi connections are auto-connected by default. This means that when a signal is emitted, Qt Jambi checks the thread affinity of the sender and receiver. If they differ, the method call is treated as a queued connection.

Object Management

In the startGame() method that we outlined above, we called the setupGame() method to construct the Graphics View related parts of our QtanoidView object. Let's use this method to take a look at how ownership and garbage collection are implemented in Qt Jambi:

        private void setupGame() {
            scene = new QGraphicsScene(this);
            setScene(scene);

QObjects are typically placed into parent-child relationships. By passing this as the graphics scene's parent we are saying that our QtanoidView object has ownership over the scene. How does this work together with Java's automatic garbage collection?

Whether or not an object is collected by Java's garbage collector depends on the type of the object and its typical usage pattern. As we already have mentioned, QObjects are typically placed into parent-child relationships. For that reason they are not collected by the Java garbage collector but rather deleted by their parents upon destruction. In the case of our application, this means the scene will automatically be deleted when the main application window is destroyed.

            QSize size = sizeHint();
            scene.setSceneRect(new QRectF(0, 0, size.width(), size.height()));

On the other hand, when setting the size of the scene, we are using a QRectF object. In C++, QRectF is often referred to as a value type, allocated on the stack and passed by value or const reference. In Qt Jambi, value types are treated as normal Java objects; i.e. they are garbage collected just like any other Java object.

We arrange the bricks that we are shooting the ball at in a series of rows in the scene. Each of the bricks is represented by a pixmap that we obtain using Qt Jambi's resource system &emdash; this is covered in detail in the online documentation &emdash; and we first need to obtain the dimensions of this pixmap so that we can place the bricks correctly:

            int bw = PIXMAP_BRICK.width();
            int bh = PIXMAP_BRICK.height();

We iterate through a specified set of rows and columns and, for each brick, we create a QGraphicsPixmapItem object and assign a pixmap to it. We then set the position and add the item to the scene.

            ...
            for (int y = 0; y < BRICK_ROWS; ++y) {
                for (int x = 0; x < BRICK_COLS; ++x) {
                    if (y < 2 || x == 0 || x == BRICK_COLS-1)
                        continue;
                    QGraphicsPixmapItem item = new QGraphicsPixmapItem(PIXMAP_BRICK);
                    item.setPos(x * bw, y * bh);
                    scene.addItem(item);
                    ...
                }
            }
            ...
        }

The ball and player are also QGraphicsPixmapItem objects, and are added to the scene using a similar approach.

Even though the QGraphicsPixmapItem class is usually used as an object, allocated on the heap and passed by pointer, it follows the same ownership rules as a value type by default; i.e., it will be garbage collected like any other Java object. When adding the items to the scene, we pass the ownership of the items to the scene object. This means that we need to disable garbage collection for the items since they will be deleted by the scene upon its destruction, due to the ownership relation.

Although it is possible to disable the garbage collection manually by calling the disableGarbageCollection() method that is present in all Qt Jambi based objects, Qt Jambi is smart enough to recognize methods that reassign the ownership, and will call this method automatically. As a programmer you normally do not have to worry about issues like these.

In addition to the object ownership and garbage collection mechanisms mentioned above, it is possible to explicitly delete Qt Jambi objects using the dispose() method for synchronous deletion or the disposeLater() method for asynchronous deletion.

Controlling the Action

Although the setupGame() method creates the various elements of the game, we can't have a playable game without some way for the player to move the paddle.

In our game, we have chosen to implement keyboard controls, so we must override the keyPressEvent() and keyReleaseEvent() methods inherited from QGraphicsView to let the player move the paddle using the left and right arrow keys:

        protected void keyPressEvent( QKeyEvent event) {
            if (event.key() == Qt.Key.Key_Left.value())
                leftPressed = true;
            if (event.key() == Qt.Key.Key_Right.value())
                rightPressed = true;
        }
        protected void keyReleaseEvent( QKeyEvent event) {
            if (event.key() == Qt.Key.Key_Left.value())
                leftPressed = false;
            if (event.key() == Qt.Key.Key_Right.value())
                rightPressed = false;
        }

In order to identify the arrow keys, we have to match the values of their corresponding enum values, Key_Left and Key_Right, against the integer value returned from QKeyEvent's key() method. Fortunately, by design, all Qt Jambi enums have a value() method that returns the integer value of the enum as it was declared in the C++ library. We use this to obtain integer values that we can use to make these comparisons.

Summary and Suggestions

In this article we have stepped through parts of a game written using Qt and Java to show how Qt Jambi can be used in practice. Although we used Qt Designer to create the game's user interface, using Qt Jambi's user interface compiler (JUIC) to generate the corresponding Java source code, we could have written the user interface code by hand instead.

We have seen how to use Qt Jambi's syntax for signals and slots, and taken a brief look at how Qt Jambi merges Qt's and Java's threading models to gain benefits from both. We also explored the approach Qt Jambi uses to handle object ownership with respect to Java's built-in garbage collector.

As it stands, the Qtanoid game is more of a simple demo than a game. A creative developer could make improvements in a number of areas:

  • Collision detection between the ball and the bricks could be improved. The current code only checks for each brick's base.
  • There is only one level in the game, with a fixed design consisting of a simple array of bricks. Different designs of bricks could be introduced, each with its own unique properties.
  • When the player misses the ball, the game simply ends. In a complete game, the player would get more than once chance to knock down the wall.

We hope that this article will inspire you to use Qt Jambi to write games and demos as well as more serious applications.

Qt Jambi Resources

In addition to integrating the complete Qt API with the benefits that Java provides, Qt Jambi also includes the Qt Jambi Generator, which generates Java APIs for existing C++ libraries. Although the generator makes the task of creating APIs straightforward, it is beyond the scope of this article.

See the online documentation for more information about the generator, and to explore Qt Jambi's Javadoc API documentation.

If you want more information about Qt Jambi, please visit the Trolltech website or join the discussions on the qt-jambi-interest mailing list.

The current preview of Qt Jambi is available to registered developers. Registration consists of entering some basic information in a simple online form.


Copyright © 2007 Trolltech Trademarks