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.
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).
23 Comments:
Bloody cool!!!
Hi there Johan.
I've only skim read this, so excuse if I'm misunderstanding - but I think you might be able to reuse some of the work I've done on an idea quite similar to this in the past.
My aim was to reimplement Gtk API as wrappers around Qt, and I did make a fair amount of progress, but some obstacles (Qt's QAction+QMenu vs Gtk's more widget-oriented menu) and a seeming lack of how it could be applied made me halt work on it.
If you're curious, take a look over at http://gitorious.org/gqt
> we need it to build as a C++ source file, I'm sure that GCC can be convinced of that by other means
-x c++
(But this means a lot of GTK+ sources won't compile as is, they'll need to be modified to be C++-safe.)
oh man, I love this.
fan-tas-tic. You got me with gtk-made-qt :-)
I really like this idea. Some people stick with gtk, because they're familiar with it, not because they consider it's better.
Regards!
@w00t, that sounds very much like what I've been trying to do. I'll look at it asap.
yeah, ok, i get the idea.
but where would this be useful?
writing qt or gtk apps is a whole other approach, let alone the interface design, you can't just write it with another Toolkit, that is only strange ;)
@Anonymous: that was one of my questions. Perhaps it can be use to move an application from GTK+ to Qt, or to get a GTK+ application running on a Qt-only platform.
As for writing GTK+ and Qt applications being different - yes that is the challenge. But underneath it is all too similar.
Finally, does GTK+ have design rules, isn't that Gnome.
@Johan:
Great! If you want to collaborate, and you've a clear idea of where to head, I'm open to ideas.
I do think that this could be an interesting project from a number of perspectives.
Hit me up on IRC (assuming you use it); I'm on freenode as w00t.
The most interesting part: can this be compiled as a library to replace gtk2 libs with calls to Qt in the future? That would be very cool!
put your stuff on github, let hack this into something awesome
@ChALkeR - I think that would be hard. I never think that we'll really get to the point where any non-trivial GTK+ application needs some manual patching to get it to work...
@Anonymous - it just need some polishing and a plan, then we can hack as a group.
Please do not use GitHub!
Use Gitorious (kde-developers) or KDESVN...
There should be a central place.
You should parse the C-source-code (KDevelop's C++ parser or whatever), some #defines are certainly not enough.
But it's very cool. I wouldn't be able to do that, because I do not know enough about Gtk+ and you need skills in both toolkits.
The Userjnqui
Wow this really seems absolutely fabolous!
I can't wait to get KIMP, play around with Knome/eKsFCE/LeKsDE, FirefoKs or Kromium.
This could get so cool!
Well, there is the gtkqt-engine. Works quite well already, for example I can use Chrome/Gimp/FireFox and more without any problems (or minor problems) and it really does fit better into KDE. It draws with native Qt widgets I think.
Turned into a runtime replacement for Gtk+, this would rock hard. I'm not very satisfied with gtk-qt-engine because while it uses Qt for widget drawing, you still get ugly Gtk dialogs and icons, and you have to double click as opposed to KDE where single click is the default.
If you think about it, Gtk+ made Qt would improve the situation for everyone.
This is a good idea I agree that something has to been done with all api out there that do the same thing it. I believe (even thou I don't know if this is true) it is a primary cause of slow down when loading apps because of load and unload libs that should other wise just stay load but don't because they are not being shared.
I Originally had an Idea to do it the other way around gtk+ wrapper for qt. Since to me it seems smarter to wrap a c to c++ then c++ to c it seem you trying to do this backwards. I think we are better if we try doing the same-thing that xdg-utils is doing with Portland project. Create an api similar to the (what i guess to be failed) dapi and the Common Desktop Infrastructure.
http://www.smg.co.jp/opensource
Well, my choice of wrapping Qt in GTK+ instead of the other way around is based on more than C vs C++.
Johan Thelin said...
Well, my choice of wrapping Qt in GTK+ instead of the other way around is based on more than C vs C++.
Well now you got me wondering.....:)
What is it base on?
First, I do not wish to start a flame war here - I just want to give you my personal view on things.
I tend to prefer Qt and feel that Qt has advantages in that it is more portable and easier to port (fewer dependencies). Thus, I see a point in enabling GTK+ applications to be moved from GTK to Qt in a smooth fashion.
I.e. the direction of the wrapping is based on my preference of toolkit.
Johan Thelin said...
First, I do not wish to start a flame war here - I just want to give you my personal view on things.
Sorry about it sounding a little rude i just what to know what was up i don't what to talk about which one is better gtk or qt that is a matter of choice and a good way of starting a war:)
Thankyou you have answered it perfectly
Post a Comment
<< Home