Meet Qt Jambi

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 20 | Документация


by Gunnar Sletta

Meet Qt Jambi, a prototype technology that empowers Java developerswith a high-performance cross-platform software developmentframework. Qt Jambi makes Qt's C++ libraries available to the Javacommunity. Compared to other Java frameworks such as Swing and SWT, QtJambi 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 userinterfaces from Qt components, by converting Qt Designer's user interfacedefinition files to Java source files.

In this article we will take a closer look at Qt Jambi to see how theQt framework is integrated with Java. We will do this by writing asmall example application. The application is a game called Qtanoid,inspired by the arcade classic of a similar name, where the playerscores points by using a paddle to bounce a ball against a brick wallin 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.

center

The current Qt Jambi release is the third technology preview. It isbuilt on Qt 4.2, providing access to new Qt features like the GraphicsView 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, butit is fully possible to write the entire user interface by hand in Java,just as we can with C++. The user interface contains three differentelements, a label showing the score, a button to start the game, and adisplay where all the action happens.

The display is implemented by subclassing the QGraphicsViewclass. When using Qt Designer to create our user interface, thiscustom widget is unavailable to us. But it is still possible toinclude the widget in the user interface definition by "promoting" oneof Qt's regular widgets as a placeholder for it on the form we createin Qt Designer.

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

center

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

[править] Generating Source Code

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

        juic -cp .

The -cp option tells JUIC to recursively traverse the givendirectory, in this case the current directory, and generate sourcecode for all the .ui files it finds. The generated classes are putinto a Java package derived from the location of the .uifiles. Basically the -cp option is a convenience option to updateall .ui files in your project. It is also possible to integrateJUIC into build systems like ant, running the tool on a per-filebasis. Likewise, the Qt Jambi Eclipse integration has JUICintegrated as a custom build tool which regenerates the class fileswhenever the .ui files change.

The output from JUIC tell us that we have generated a source filecalled 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 producedby uic: The generated component is a class with a setupUi()method that initializes the user interface. In the source file for ourgame (Qtanoid.java), we load the user interface like this:

    import com.trolltech.qt.gui.*;

Qt Jambi is organized after the standard Java naming convention ofcom.companyname.product.module, making QtGui accessible incom.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 itrepresents 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 theQtanoid.setupUi() method, passing the widget as its argument.

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

We connect the start button's clicked() signal to thestartGame() method in the QtanoidView class. Thesecond connection is from the custom scoreChanged signal in theQtanoidView class to the score label, telling the label toupdate 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 eventloop.

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

<tbody>
Custom Widget Plugins
Readers experienced with Qt Designerare probably familiar with the custom widget plugin interface, which is analternative way of making custom widgets available in Qt Designer. It is possible to useplugins to create "live" custom widgets in Qt Designer's forms &emdash; customwidgets can be previewed like any other Qt widget, and they supportsignal and slot connections as well as property editing.

In Qt Jambi, this feature is currently under development, and we aimto provide the same support for custom Qt Jambi widgets. In addition,the Qt Jambi team is working on a Qt Designer language plugin, makingit possible to view various widget characteristics like properties andsignals and slots using Java syntax rather than the C++ syntax usedtoday.</tbody>

[править] Building the Game

So far we have looked at part of the toolchain, and seen how simpleJava 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 quitea 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 ascoreChanged signal using the Integer type to notifyinterested parties that the game scorehas changed. Qt Jambi uses a generics-based approach to signals:Our signal is an instance of the Signal1 class because it has onlyone argument (the score as an integer) &emdash; signals with more argumentsare defined using Signal2, Signal3, and so on. This approachenables us to do type-checking at compile time for signal emissions.

Qt Jambi signal and slot connectionsalso support Java's autoboxing feature, making it possible to connectsignals of the Integer object type to slots taking the primitivetype int. In fact, this is exactly what our connection in the mainmethod does, connecting the scoreChanged signal to thesetNum() 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 constructorpattern, accepting a parent argument that is passed on to the baseclass constructor. Here, we set some basic properties, such as theappearance of the widget's background and scrollbar policies.Qt Jambi makes use of the Java 1.5's enum features, soQt.ScrollBarPolicy is an enum type and ScrollBarAlwaysOffis an enum value. Using this feature, Qt Jambi ensures that methodsaccepting enum arguments are verifiable at compile time.

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

In the main method we connected the start button to thestartGame() 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 thescoreChanged() signal. As you can see, emit() is alsoa method of the signal object, notifying all its registered slots.Note that, even though the scoreChanged signal expects an objectof the Integer class as argument, you can pass it a primitiveinteger relying on Java's autoboxing feature to convert it to anInteger 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 betweeneach hit is used to calculate the score, so we store the current timeas a baseline. The setupGame() method creates the various gameelements which are QGraphicsPixmapItemobjects. We will cover these in detail later.

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

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

To have proper event and threaded signals and slots handling for ourGameLoop object, we move it to the newly-constructed thread. Notethat this thread object is not a QThread object but a built-inJava Thread object. Qt Jambi merges Qt's QThread and Java threads to get the benefitsfrom both.

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

            thread.setDaemon(true);

The GameLoop class has one signal, nextFrame, which isemitted each time an update of the game state is required; i.e. when theball moves, the player moves and whenever collisions may occur. We connectour game loop's signal to the three different actions that we musthandle 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 inthe game loop thread and the QtanoidView object resides in the GUIthread, we must have queued connections between the nextFramesignal and the receiving slots. Like in C++, all Qt Jambi connectionsare auto-connected by default. This means that when a signal isemitted, Qt Jambi checks the thread affinity of the sender andreceiver. If they differ, the method call is treated as aqueued connection.

[править] Object Management

In the startGame() method that we outlined above, we calledthe setupGame() method to construct the Graphics View relatedparts of our QtanoidView object. Let's use this method to take alook at how ownership and garbage collection are implemented in QtJambi:

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

QObjects are typically placed into parent-childrelationships. By passing this as the graphics scene's parent weare 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 collectordepends on the type of the object and its typical usage pattern. As wealready have mentioned, QObjects are typically placed intoparent-child relationships. For that reason they are not collected bythe Java garbage collector but rather deleted by their parents upondestruction. In the case of our application, this means the scene willautomatically 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 valuetype, allocated on the stack and passed by value or const reference.In Qt Jambi, value types are treated as normal Java objects; i.e. theyare garbage collected just like any other Java object.

We arrange the bricks that we are shooting the ball at in a series ofrows in the scene. Each of the bricks is represented by a pixmap thatwe obtain using Qt Jambi's resource system &emdash; this is covered indetail in the online documentation &emdash; and we first need to obtain thedimensions 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. Wethen 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 areadded to the scene using a similar approach.

Even though the QGraphicsPixmapItemclass is usually used as an object, allocated on the heap and passed bypointer, 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 addingthe items to the scene, we pass the ownership of the items to the sceneobject. This means that we need to disable garbage collection for theitems since they will be deleted by the scene upon its destruction, dueto the ownership relation.

Although it is possible to disable the garbage collection manually bycalling the disableGarbageCollection() method that is present inall Qt Jambi based objects, Qt Jambi is smart enough to recognizemethods that reassign the ownership, and will call this methodautomatically. As a programmer you normally do not have to worryabout issues like these.

In addition to the object ownership and garbage collection mechanismsmentioned above, it is possible to explicitly delete Qt Jambi objectsusing the dispose() method for synchronous deletion or thedisposeLater() method for asynchronous deletion.

[править] Controlling the Action

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

In our game, we have chosen to implement keyboard controls, so we mustoverride the keyPressEvent() and keyReleaseEvent() methodsinherited from QGraphicsView to let theplayer 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 oftheir 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 declaredin the C++ library. We use this to obtain integer values that we canuse to make these comparisons.

[править] Summary and Suggestions

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

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

As it stands, the Qtanoid game is more of a simple demo than agame. 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 designconsisting of a simple array of bricks. Different designs of brickscould be introduced, each with its own unique properties.
  • When the player misses the ball, the game simply ends. In a completegame, 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 writegames and demos as well as more serious applications.

[править] Qt Jambi Resources

In addition to integrating the complete Qt API with the benefits thatJava provides, Qt Jambi also includes the Qt Jambi Generator, whichgenerates Java APIs for existing C++ libraries. Although the generatormakes the task of creating APIs straightforward, it is beyond thescope of this article.

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

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

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


Copyright © 2007 Trolltech Trademarks