2010-01-18

Qt as GTK, again

Another set of night hours spent hacking on making the GTK API wrap Qt. I focused on adding functionality and not on cleaning up, so no code this time either. I just want to show the progress.

Wrapping Qt in GTK might seem like an impossible task, but the fact is that both toolkits, in turn, wrap the same APIs themselves. So, how does the progress look this far?

Upgraded Hello World (link)

The first example, upgraded hello world, works like a charm and does what is expected. There is only one change that has to be made to the example, and the need for it will go away when I've started cleaning the code.

Changes made to the example:
  • Replaced the gtk/gtk.h headerfile with my substitute.
Table Packing Example (link)

The next example, table packing, simply demonstrates the grid layout abilities of GTK. There seems to be a couple of pixels between the rows for some reason (I will investigate at a later point), but apart from that, everything works. The changes made are the same as for the first example.

Changes made to the example:
  • Replaced the gtk/gtk.h header with my substitute.
Manual Menu Example (link)

Robin Burchell (w00t) has attempted something similar in the gqt project (gitorious link). We will discuss how we can join forces ASAP (I've been sort of unavailable this weekend). The gqt project has clean code - look great. It employs a different approach than I when it comes to mapping signals/slots/events and for handling the construction of menus.

Robin mentioned that one of the major hurdles when mapping the APIs is that GTK uses a widget based structure for menus, while Qt uses QActions for menu items. To have a swing at this, I decided to attack the manual menu example next. It took some changes to the main approach of the wrapping effort, but now I think I have it sorted out.

Changes made to the example:
  • Replaces the gtk/gtk.h header file with my substitute.
  • The popup is always shown at (100, 100) as the GdkEvent structure cannot be cast to a GdkEventButton structure.
Manual Menu Example

Another benefit of doing the manual menu example was that I had to have a better look at the events mechanism. As it seems, the "event" signal hooks into all events (like a Qt event filter). This means that there has to be a general event filter, as well as specific event filters (for when connecting to a specific event such as "delete_event"). This kind of breaks my idea of splitting out the code for creating a specific GdkEvent for each type of event in a separate class, so I need to think a bit about that (shouldn't really be a major issue - I just don't want code duplication).

Back to the menu widget versus action approach. As it seems, the QAction and QWidget classes' common base is the QObject class. Thus all pointers to GtkWidget, GtkWindow, etc. needs to be pointers to QObject. That way, the menu object can be made to match quite nicely. The downside is that the code already contained quite a lot of casts. Now it has even more. For instance, a trivial function such as gtk_container_set_border_width needs casting:

void gtk_container_set_border_width(QObject *o_w, int spacing)
{
QT_WIDGET_ASSERT(w)
w->setContentsMargins(spacing,spacing,spacing,spacing);
}

The QT_WIDGET_ASSERT macro creates a QWidget pointer named w, by casting o_w using qobject_cast and then asserts that the pointer isn't null. I'm not sure of the performance penalty of this, but I guess there is one. However, to compensate, all of GTK's GTK_WINDOW, GTK_BOX, etc. are instead defined as simple pass-through macros. This means that performance wise it is a race between GTK_CHECK_CAST and qobject_cast.

I did consider putting the qobject_cast calls in the GTK_xxx macros, but that would not work. For instance, gtk_menu_shell_append can place a QAction in either a QMenu, or in a QMenuBar. As the function is given a QObject pointer, it can now determine (by casting) if the container is a QMenu or QMenuBar and act accordingly.

So, where to next? I belive in fixing the known bugs before continuing. Especially when working on a project such as this, which I suspect will backfire anytime soon (it has been too easy this far). This means that the next step is to do something about the GdkEvent structures to get the popup of the manual menu example to work properly (now it appears at a static location). After that? Cleaning and merging with gqt...

2010-01-15

GTK+ made Qt

Disclaimer! First of all - this is not an attack on any toolkit, nor critique. Rather, it is a hacker sitting down and trying out an idea.

It has been a couple of years since I first tried out this idea (some seven, I believe). This time, licensing permits it and I believe that the timing is right. So, without further ado, let me introduce GTK+ made Qt.

Qt and GTK+, and all other toolkits out there, provide more or less the same functionality (take that with a grain of salt, but the core is true). So, my idea was to wrap Qt in a GTK+ API layer. Just to see if it could be done. Attacking the problem at this level might be considered stupid - but I like to get results fairly quickly, and this way I can work with GTK+ code directly, instead of writing a ton of underlaying code just to get something to compile.

There are tons and tons of corner cases and not so corner-ish cases that can make this break. The implementation is miles and miles from perfect and the code I've been testing it on is rather trivial. What I've done is that I've taken one of the examples of the GTK+ 2.0 Tutorial and made it do what it is supposed to do (sort of). Examples of what I've ignored in the process:
  • Event handling functions get NULL instead of a GdkEvent pointer.
  • Packing flags (e.g. homogenus and spacing when calling gtk_hbox_new) ignored - I simply use QVBoxLayout, QHBoxLayout and QGridLayout where they fit best.
  • Only a minimalist part of the API has been implemented (the process is build, add missing symbols, repeat)
  • Most GtkXxx classes are defined to a QWidget. Objects are casted up and down all over the place to work around this.
However, the problem isn't really matching the APIs to each other and getting every pixel right. At least, that is not the current problem. Instead, matching Qt's C++ style OO to GTK+'s is the problem. What are the differences? I hear you ask.

To create a trivial dialog in Qt, you inherit the QDialog class and then create the children in the constructor of your new class. In GTK+, you generally create a function for creating the dialog in question. In that function, all widgets (including the dialog) are created and put in a proper hierarchy. The resulting hierarchy of widgets is (95%) identical, and all the custom code to make it tick is there - it is just placed differently in the source.

Another difference that has a large impact is the fact that Qt event handles are virtual methods of QObjects and slots are QObject methods. In GTK+, they are plain C functions that are passed as function pointer to g_signal_connect calls.

So, the starting point is this example. I've placed that code in helloworld.cpp (we need it to build as a C++ source file, I'm sure that GCC can be convinced of that by other means, e.g. by using g++ directly, but just changing the extension does it for me).

The only change made to the source code itself is that the include of gtk/gtk.h has been replaced by the inclusion of gtk-made-qt.h (no, the name is not critique, it is just my strange humor). The header file is the container of the rest of this experiment.

The GTK+ Hello World example running in QtCreator on Windows Vista

First of all, I've made some rather rude typedefs and defines with regard to GLib (this just shows how much that needs to be redone to do this properly):

#define G_OBJECT(obj) ((QObject*)(obj))
#define G_CALLBACK(func) ((void*)(func))

typedef void* gpointer;
typedef bool gboolean;
typedef char gchar;

#define g_print qDebug

...

There is more of this of course, and some of it is spread out across multiple header files. Next follows the same horrific crime, but this time made to some of the GTK+ widgets:

#define GtkWidget QWidget

void gtk_widget_show(QWidget *w) { w->show(); }

#define GTK_WINDOW_TOPLEVEL (0)
QWidget *gtk_window_new(int) { return new QWidget(); }
void gtk_window_set_title(GtkWindow *w, const char *t) { w->setWindowTitle(QString(t)); }

...

#define GTK_BOX(obj) (obj->layout())
void gtk_box_pack_start(QLayout *l, QWidget *w, bool expand, bool fill, int padding) { l->addWidget(w); }

As you can see, straight forward, brute force, get the job done hacks. All this shows how close the different APIs match. This is trivial code (albeit unsafe) and adds almost no extra conversions or checks.

The really interesting part is the implementation of the g_signal_connect function. Here, bridges for both events and signals/slots are dynamically setup. So, here it is in all its glory:


void g_signal_connect(QObject *src, const char *cstrEventName, void *f, void *data)
{
QString eventName = QString(cstrEventName);

if (eventName.endsWith("_event"))
{ // This is an event, f is an eventFuncPtr
QObject *o = QGtkEventFilter::createFilter(eventName, eventFuncPtr(f), data);
if(o)
src->installEventFilter(o);
else
qWarning("Failed to match GTK event '%s' to a Qt event filter.", cstrEventName);
}
else
{ // This is a callback, f is an callbackFuncPtr
QObject *o = new QGtkCallbackBridge(src, callbackFuncPtr(f), data);

const char *signalName = 0;
if(eventName == "clicked")
signalName = SIGNAL(clicked());

if (signalName)
QObject::connect(src, signalName, o, SLOT(trigger()));
else
qWarning("Failed to match GTK signal '%s' to a Qt signal.", cstrEventName);
}
}

To my surprise, that is all that it takes. The QGtkCallbackBridge is a trivial QObject that calls the given function pointer with the QObject::sender() and the given data pointer when signalled. The QGtkEventFilter is not much more complicated. It simply filters out the event in question and triggers the given function pointer in much the same way. I've created a factory for the event filters, as I want them to re-create something looking like the original GdkEvent structure. There is actually one event filter class for each type of event, just so that this code can be added.

Well, I've not packaged the code yet. My goal is to try at least one more example first (perhaps two, given time). In the mean time, let's see if this can be made useful (a large enough portion of the GTK+ API needs to be implemented for that) - and if the idea is interesting at all (from a political standpoint, etc). As it stands now, this might help porting GTK+ applications to Qt only platforms, or it could be used as a migration kit (not that I encourage people to migrate from GTK+ to Qt - it is a choice that every developer has to make - use whatever feels comfortable to you).