Useing Qt to write Equinox-OSGi-UI-Applications


I saw I didn’t blogged since about 2 months. So I thought I’ll start a series of blog entries showing off new things and paths I’m exploring.

I’ll start with a Technical Topic because it’s a really exciting thing I guess not only for me but also for the whole Equinox-OSGi/Java-Community.

Since some time Qt is released under LGPL and since some weeks now their Java-Binding named Qt-Jambi is released too under LGPL. I’ve been playing with Qt-Jambi (because my UFaceKit project has a Qt-Port) before but now that the code is under LGPL it’s getting more interesting to the wider Java-audience and naturally also people who use Equinox-OSGi for their applications.

A simple QtJambi-Application

Before digging into the details what I’ve done let’s look at a simply QtJambi-Application if we are not using Equinox-OSGi.

package at.bestsolution.qt;

import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QGridLayout;
import com.trolltech.qt.gui.QLabel;
import com.trolltech.qt.gui.QLineEdit;
import com.trolltech.qt.gui.QMainWindow;
import com.trolltech.qt.gui.QWidget;

public class HelloWorld extends QMainWindow {
  public HelloWorld() {
    setWindowTitle("Hello World!");
    QWidget composite = new QWidget();
    QGridLayout layout = new QGridLayout();
    composite.setLayout(layout);

    QLabel label = new QLabel();
    label.setText("Label");
    layout.addWidget(label,0,0);

    QLineEdit text = new QLineEdit();
    layout.addWidget(text,0,1);

    setCentralWidget(composite);
  }

  public static void main(String[] args) {
    QApplication.initialize(new String[0]);

    HelloWorld world = new HelloWorld();
    world.show();
    QApplication.exec();
  }
}

This looks not much different to a SWT-Application besides the fact that one doesn’t has to pass a parent when creating a widget and instead of running the event loop one simply calls QApplication.exec().

QtJambi and Equinox-OSGi

Couldn’t be hard you think when you’ve used other UI-Toolkits (SWT,Swing) in your Equinox-OSGi-Applications already but the problem is that Swing is not problematic because it is part of the JRE and SWT is shipped as an (in fact multiple) Equinox-OSGi-Bundle/Fragment.

What we need to do is to Equinox-OSGify the bundles coming from Qt but this task is more complex then it looks on the first sight because using the simple converter provided by PDE is not providing us a solution because QtJambi-Code expects to load the libraries in very special way which means we need to patch their Java-Code to make it aware of Equinox-OSGi.

The really cool thing is that patching and maintaining the patch is easier than one might think because they provide their sources through a git-repo one could simply clone and maintain the patched sources. So maintaining the patch is easier than it is for example to maintain a patch for the eclipse-platform because of git.

The tough thing is to get the environment setup in a way than one can produce .jars from the sources because one

  • Has to compile the Qt-Sources
  • To generate the Java-Binding-Classes to the Qt-Sources (extracted from the C++-Header-Files)

which is a bit time consuming and not documented very well at the moment. Though this is doable for a medium skilled Java-Dev I think one should be able to checkout the complete project with native and generated Java-Code and doesn’t have to compile all the stuff.

After having managed to setup a build environment I patched the libary loading classes and recreated the .jar-packages. QtJambi is split in 2 .jars:

  • qtjambi.jar: Hold platform independent Java-Classes
  • qtjambi-${os}.jar: Holding native libraries for the platform and the JNI-Glue

So the setup is similar to SWT but in SWT also the Java-Code is part of the native fragment because it differs from platform to platform and the host bundle is simply an empty bundle. In contrast to that in Qt the Host-Bundle is holding all Java-Classes and in the native fragments one has the native-libs and JNI-Glue.

So what this all means for you? Not too much because I did 2 things as part of UFaceKit-Target-Setup:

  • Packaged my changes to the Java-Code and provide it for download
  • Added ant-tasks who fetch the native libs from Qt-Software and repackage them


One could also use these ant-tasks when not using UFaceKit (I’m using it for my RCP-Development-Setup).

The Equinox-OSGi-Support is not fully finished and I’ll maybe rework it a bit in future when understanding the code better but for now it sufficient to go on and file a CQ to make use of Qt in UFaceKit. Let’s see what’s coming out from this now that Qt is LGPL.

Simple Qt-Jambi and Equinox-OSGi-Application

Let’s create an Equinox-Application which uses Qt as UI-Toolkit now. The easiest thing is to use the PDE-Wizard to create a “Headless Hello RCP” and add a MainWindow.java.

package at.bestsolution.qt;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QGridLayout;
import com.trolltech.qt.gui.QLabel;
import com.trolltech.qt.gui.QLineEdit;
import com.trolltech.qt.gui.QMainWindow;
import com.trolltech.qt.gui.QPixmap;
import com.trolltech.qt.gui.QPushButton;
import com.trolltech.qt.gui.QWidget;

public class MainWindow extends QMainWindow {
  public MainWindow() {
    QWidget widget = new QWidget();
    widget.setObjectName("main_window");

    QGridLayout layout = new QGridLayout();
    layout.setMargin(0);
    widget.setLayout(layout);  

    addHeader(layout,"Tom Schindl","at/bestsolution/qt/bookmarks.png");

    QWidget content = new QWidget();
    QGridLayout contentLayout = new QGridLayout();
    content.setLayout(contentLayout);

    addLine(0, contentLayout, "Firstname");
    addLine(1, contentLayout, "Lastname");
    addLine(2, contentLayout, "Age");

    QPushButton button = new QPushButton();
    button.setObjectName("submit");
    button.setText("Submit");
    contentLayout.addWidget(button,3,1);

    layout.addWidget(content);

    setCentralWidget(widget);
  }

  private void addHeader(QGridLayout layout, String labelText, String icon) {
    QLabel header = new QLabel();
    layout.addWidget(header);
    header.setObjectName("header");
    QGridLayout headerLayout = new QGridLayout();
    headerLayout.setMargin(0);
    header.setLayout(headerLayout);
 
    QLabel headerIcon = new QLabel();  
    headerIcon.setObjectName("header_icon");
    headerIcon.setPixmap(loadImage(icon));

    headerLayout.addWidget(headerIcon);

    QLabel headerText = new QLabel();
    headerLayout.addWidget(headerText,0,1);
    headerLayout.setColumnStretch(1, 100);
    headerText.setObjectName("header_text");
    headerText.setText(labelText);
  }

  private void addLine(int line, QGridLayout contentLayout, String labelText) {
    QLabel label = new QLabel();
    label.setText(labelText);
    label.setObjectName("label");
    contentLayout.addWidget(label);

    QLineEdit text = new QLineEdit();
    text.setObjectName("text");
    contentLayout.addWidget(text,line,1);
  }

  private QPixmap loadImage(String path) {
    try {
      InputStream in = getClass().getClassLoader().getResourceAsStream(path);
      ByteArrayOutputStream out = new ByteArrayOutputStream();
 
      int l;
      byte[] buffer = new byte[1024];

      while ((l = in.read(buffer)) != -1) {
        out.write(buffer, 0, l);
      }

      QPixmap pic = new QPixmap();
      pic.loadFromData(out.toByteArray());
      return pic;
    } catch (Exception e) {
      e.printStackTrace();
    }

    return null;
  }
}

and modify the generated application class like this:

package at.bestsolution.qt;

import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;

import com.trolltech.qt.gui.QApplication;

public class Application implements IApplication {
  public Object start(IApplicationContext context) throws Exception {
    QApplication.initialize(new String[0]);
 
    MainWindow window = new MainWindow();
    window.show();

    QApplication.exec();

    return IApplication.EXIT_OK;
  }

   public void stop() {}
 }

Well as you see I’m not a good designer and the application looks well not really nice though it looks native on my OS-X though this is only faked by Qt because they are drawing everything on the screen as far as I understood it.

One could think that this fact is a draw back of Qt but IMHO it’s the other way round because with this strategy they can support things SWT can’t support easily – Completely restyle your application using a declarative language and well they use CSS like e.g. E4 does too.

The first thing to do is to add a method to load a stylesheet to Application.java:

private String loadStyles(String cssPath) {
  InputStream in = getClass().getClassLoader().getResourceAsStream(cssPath);
  BufferedReader r = new BufferedReader(new InputStreamReader(in));
  StringBuilder s = new StringBuilder();
  String line;

  try {
    while( (line = r.readLine()) != null ) {
      s.append(line);
    }
  } catch (IOException e) {
    e.printStackTrace();
  }
 
  return s.toString();
}

and set the style sheet on the main window:

public Object start(IApplicationContext context) throws Exception {
  QApplication.initialize(new String[0]);
 
  MainWindow window = new MainWindow();
  window.setStyleSheet(loadStyles("at/bestsolution/qt/style.css"));
  window.show();

  QApplication.exec();

  return IApplication.EXIT_OK;
}

and we need to define some styles:

resulting in this application:


which we all agree looks better than:

As you see this is also not my design but then one you get when using the Eclipse-Forms-API with the difference that in Eclipse one has to learn a new API to deal with besides SWT whereas in Qt the UI-Code is still Qt and styled by a declarative syntax and if you ask me the Forms-API is going to replace in space of Eclipse in E4 through SWT + CSS but this is only my personal opinion.

So should we all now move to Qt-Jambi to write UI-Applications in Java like we did years ago when we abandoned Swing and started using SWT?

Let’s look at some potentially problematic areas:

  • Qt and QtJambi misses an application framework like eclipse RCP provides one for SWT-Application developers
  • Qt and QtJambi misses Databinding support like Eclipse-Databinding provides one for SWT, JavaBeans and EMF
  • Nokia removed all resources from QtJambi development and wants to build a community to work on it

For at least 2 of the above there are solutions already today:

  • E4’s core application platform is UI-Toolkit agonstic so though E4 is not released until 1.5 years it would give people the possibility to use Qt as their UI-Toolkit of choice which supports many many things starting from animations, multimedia integration, …
  • UFaceKit provides JFace-Viewer like and Eclipse-Databinding support for QtJambi if the CQ I’m going to file is approved

Still the killer problem is the lacking support from Nokia on QtJambi and it’s unclear if a community could be build around it who not even maintains but also adds new features.

I think this is a bitty because with getting a real application framework with E4, it’s themeing, multimedia and animation support I think QtJambi could get a real possibility to write cross-platform RCP-Applications in Java without the sometimes really hurting lowest common denominator problem we have in SWT.

So what should one do? Though QtJambi looks like a real solution for writing nice looking RCP-Applications the uncertainness caused by Nokia by cutting resources makes it unusable for most companies.

For form developers I could point you once more to UFaceKit which supports both SWT and Qt and your form application code is not affected by changing the underlying technology but one can still rely on native stuff where needed (e.g. using Qt animation/multimedia support).

For me as one of E4 committers and UFaceKit-Project lead it means:

  • I’d try to keep the application runtime widget agonstic if possible (well we are on a good track here)
  • I’ll file a CQ to let UFaceKit make use of QtJambi and provide first class JFace-Viewer and Eclipse-Databinding support for QtJambi
  • 1. updated my wrong capitalization of Qt
    2. please note that I’m using Equinox specific stuff to make this work so it is maybe not runnable on other OSGi implementations but I’m happy to incooperate feedback and suggestions into my git-clone to support other OSGi implementations

Advertisements
This entry was posted in UFaceKit. Bookmark the permalink.

10 Responses to Useing Qt to write Equinox-OSGi-UI-Applications

  1. Anonymous says:

    Besides it being “Qt” and not “QT” (QuickTime) the most important / sad part is that Jambi is officially dead.

    Nevertheless I really love Qt and would love so see it revived within Eclipse.

    Or even rewrite Eclipse in Qt? ;D

  2. Tom says:

    From my short excursion to Qt I have two say it is an impressive technology-stack they are providing.

    I don’t think it is a good idea to put this impressive API behind a common denominator API like SWT because Qt itself is already platform independent. So this means one has to have a 1:1 mapping which QtJambi provides.

    Moving their QtJambi codebase to Eclipse would have the same problem that you need to find a company investing the time and money into it besides being impossible because Nokia has chosen LGPL as the OpenSource License and one is not allowed to host LGPL code at eclipse-infrastructure nor publish it but as mentionned in my post E4 makes it at least technically possible write a big UI-Application with any ToolKit you want and many parts of Eclipse have 0 dependency on SWT.

  3. SDiZ says:

    I believe the Qt loop must be running in the “main” thread to fully functional in all OS.

    OSGi (in general) does not provide this facility. What you are doing is Equinox-specific.

  4. Tom says:

    Yes but this is true for SWT-Applications too. The only thing I did on OS-X is to pass the -XstartOnFirstThread switch.

    I don’t know if this is something specific to Equinox but the title should have reflected this by stating “Useing Qt to write Equinox-OSGi-UI-Applications”.

    For now I simply adjusted the SWT packageing for use with QT but I’m open to look at other frameworks too. I’m also using other Equinox specific stuff like Buddy-Classloading to work around other problems QT-Jambi code has.

    I updated the post to clearly state that this is Equinox specific but I’m happy to get feedback what needs to be changed so that it runs on other OSGi implementations too.

  5. Anonymous says:

    It would be fantastic being able to run Eclipse using Qt. It’s really annoying having the ugly gnome file picker appear, because it’s so ugly and bad compared to the KDE/Qt one. Eclipse using Qt or a Qt version of SWT would be fantastic.

  6. Anonymous says:

    > which we all agree looks better than:

    We all don't agree.

  7. Anonymous says:

    But at least it looks MUCH better than ugly GTK.

  8. william says:

    Hi! Your blog is simply super. you have create a differentiate. Thanks for the sharing this website. it is very useful professional knowledge. Great idea you know about company background.
    Increasing your web traffic and page views Add, add your website in http://www.itsolusenz.com

  9. Darren says:

    Thank you for this tutorial.
    I am currently trying to create osgi bundles out of Qt.

    • tomeclipsedev says:

      No need to do this your own because I already did the work for you. My UFaceKit-Setup script creates them for you. Simply take a look at Setup which is a simply ant-script creating OSGi compatible Qt-bundles for you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s