Toolkit comparison

Introduction

At the beginning, I wanted to write a Klotski program. When looking on the net, I found Gnotski and decided to run a test : how long does it take to port a program from Gtk to Qt and how hard is it ? But eventually, I have found this funny and made a few ports to compute some stats about each toolkit.

To make your own idea, please download the source code of this study.


Gnotski

Gnotski is the original game, that you can find on the gnome CVS. The program is short and simple. It could be enhanced, optimized and changed but this is not the goal. The goal is to see, how much the toolkits resembles and how much they differ.

Here is a small code sample, that I will show you written in Gtk, Qt and PyQt. The code has nothing especially interesting, I just want to highlight how much Qt and Gtk have the same structure.

void gui_draw_pixmap(char *target, gint x, gint y){
  GdkRectangle area;
  int value;
  
  gdk_draw_pixmap(buffer, space->style->black_gc, tiles_pixmap,
		  get_piece_nr(target,x,y)*TILE_SIZE, 0, 
		  x*TILE_SIZE, y*TILE_SIZE, TILE_SIZE, TILE_SIZE);
  if(get_piece_id(target,x,y)=='*'){
    if(get_piece_id(orig_map,x,y)=='.')
      value = 20;
    else
      value = 22;
    gdk_draw_pixmap(buffer, space->style->black_gc, tiles_pixmap,
		    value*TILE_SIZE+10,10,
		    x*TILE_SIZE+10, y*TILE_SIZE+10,8,8);
  }
  area.x = x*TILE_SIZE; area.y = y*TILE_SIZE; 
  area.width = TILE_SIZE; area.height = TILE_SIZE;
  gtk_widget_draw (space, &area);
}


C-Klotski

It took me 3 hours to port Gnotski from Gtk/C to Qt/C++. This first port was basic : I have tried to maximise common code and structure. I have not really used C++, I have just wrapped C++ around C functions (hence the name C-Klotski). The port was damned easy. Gtk and Qt have the same widget structure and the same mechanism (signal and slots).

void Klotski::gui_draw_pixmap(char *target, int x, int y) 
{
	int value;
	bitBlt( buffer, 0, 0, tiles_pixmap, 
		  get_piece_nr(target,x,y)*TILE_SIZE, 0, 
		  TILE_SIZE, TILE_SIZE, Qt::CopyROP);
  
	if(get_piece_id(target,x,y)=='*'){
		if(get_piece_id(orig_map,x,y)=='.')
			value = 20;
		else
			value = 22;

	bitBlt( buffer, 8, 8, tiles_pixmap, value*TILE_SIZE+10,10,
	    8, 8, Qt::CopyROP);
	}

	QPainter p( space );
	p.drawPixmap( x*TILE_SIZE, y*TILE_SIZE, *buffer ); 
	p.end();
}


Cpp-Klotski

With this second port, I have tried to write the program more "the C++ way", while still maximising common code. I had to assign each function to a class. It was a bit harder than the basic C++ port because I had to restructure the program. But the restructuration was the only difficult task. Once it was done, the implementation was very short and still shares a lot of code with Gnotski.

The code snippet is exactly the same in this case.

void Board::gui_draw_pixmap(int x, int y) 
{
	int value;
	bitBlt( buffer, 0, 0, tiles_pixmap, 
		  map->piece_nr(x,y)*TILE_SIZE, 0, 
		  TILE_SIZE, TILE_SIZE, Qt::CopyROP);
  
	if(map->piece_id(x,y)=='*'){
		if(map->is_goal(x,y))
			value = 20;
		else
			value = 22;

		bitBlt( buffer, 8, 8, tiles_pixmap, value*TILE_SIZE+10,10,
		    8, 8, Qt::CopyROP);
	}

	QPainter p( this );
	p.drawPixmap( x*TILE_SIZE, y*TILE_SIZE, *buffer ); 
	p.end();
}


PyQt-Klotski

The third port was a port to PyQt. I have learned Python recently and find it to be a great language. The PyQt binding looks cool and has a very active maintainer, so I gave it a shot. I started from the Cpp-Klotski. The porting was trivial. 90% of the work was just changing C++ syntax to python syntax (adding 'self.' everywhere, getting rid of ';', ...) and that could be automated by a clever script. The port took me 2 hours, but if I am pretty sure that if I had started directly from Gnotski, it would have taken less than 4 hours.

def gui_draw_pixmap(self,x, y) :
	bitBlt( self.buffer, 0, 0, self.tiles_pixmap, 
		  self.map.piece_nr(x,y)*TILE_SIZE, 0, 
		  TILE_SIZE, TILE_SIZE, Qt.CopyROP)
  
	if self.map.piece_id(x,y)=='*' :
		if self.map.is_goal(x,y) :
			value = 20
		else:
			value = 22

		bitBlt( self.buffer, 8, 8, self.tiles_pixmap, value*TILE_SIZE+10,10,
		    8, 8, Qt.CopyROP)

	p = QPainter( self )
	p.drawPixmap( x*TILE_SIZE, y*TILE_SIZE, self.buffer ); 
	p.end()



Score system

Gnotski uses a few Gnome functions to perform some score managment. There is nothing in Qt or Kde to manage score yet, so I couldn't port that part. To keep my statistics fair, I have removed the lines concerning score handling from my computing. Here are the only lines referring to score :

83:void game_score();
90:void score_cb(GtkWidget *, gpointer);
453:  GNOMEUIINFO_MENU_SCORES_ITEM (score_cb, NULL),
482:  gnome_score_init(APPNAME);
571:        game_score();
617:void score_cb(GtkWidget *widget, gpointer data){
618:  gnome_scores_display (_(APPNAME_LONG), APPNAME, current_level, 0);
619:}
620:
621:void game_score(){
622:  gint pos;
623:  pos = gnome_score_log(moves,current_level,FALSE);
624:  gnome_scores_display(_(APPNAME_LONG), APPNAME, current_level, pos);  
625:}

It accounts for this stat:
Lines Characters
14 435



Size comparison

I made my stats in the stat directory of each program. Gnotski mixes in the same structure level definitions and menu entries. I couldn't reuse this structure directly in Qt because it was too much Gnome specific. So I have used a almost the same kind of structure and have integrated the menu code separately. This is why there are some special stats for menu+level stuff. I have also isolated the declarations in a separate file. And finally, the "pure code" part is the Total with declarations and level+menu stuff substracted. For Gnotski, I have also removed the score stuff.

Here are the results

Files Lines Characters
Gnotski Declarations Gnotski-declaration.h 91 2454
Level+Menu gnotski-levels.h 381 8978
Pure code 568 13829
Total pieces.h
gnotski-without-score.c
1040 25266

Files Lines Characters
C-Klotski Declarations klotski.h 126 3019
Level+Menu klotski-levels-menu.h 327 6528
Pure code 505 10453
Total pieces.h
klotski.h
levels.h
klotski.cpp
958 19990

Files Lines Characters
Cpp-Klotski Declarations klotski.h 157 3136
Level+Menu levels-menu.h 319 6502
Pure code 492 9394
Total pieces.h
klotski.h
levels.h
klotski.cpp
968 19032

Files Lines Characters
PyQt-Klotski Declarations 0 0
Level+Menu levels-menu.py 315 6270
Pure code 391 9357
Total levels.py
klotski.py
pieces.py
706 15633



Conclusions

First of all, code size is not everything. To have a good idea of the differences between each toolkit and langages, please look at the source code by yourself. The program is short and simple so you shouldn't have hard time understanding it. get_piece_nr() is the only tricky function that deduce which pixmap should be drawn. You can cleanly ignore it, this is not very relevant to our comparison.

Secondly, this program is quite short and simple. So, the conclusions we can draw are not definitive statements about which toolkit is the best, but first impressions on how they compare.

Menu+Level

Menu and level stuff are irrelevants because I have used a different structure than Gnotski. Here is however an interpretation : My structure is more efficient than the Gnotski one: you notice Gnotski uses 380 lines and 8980 characters to define all levels and menu while the Cpp-Klotski examples uses around 320 lines and 6500 characters. The PyQt-Klotski uses 315 lines and 6270 characters for the same thing being the most efficient.

Declarations

C needs only a few declarations, which accounts for 91 lines. C++ need to declare all classes and methods, thus 126 lines for C-Klotski and 157 lines for Cpp-klotksi. Python doesn't use declarations.

The difference between C-Klotski and Cpp-Klotski is not very surprising. In C-klotksi, all objects access eachother freely and mix together. This is not a clean object-oriented program. With Cpp-Klotski, I have defined clean objects with a clean methods to access each object (as much as I could). This means that objects don't access eachother freely, so it is more bug-proof, but it also means that you probably need more methods to make the objects communicate. This is only my interpretation though.

Pure code

The results are clear:

C-Klotski is more efficient than Gnotski in terms of lines and characters. My feeling is that it is due to the toolkit being more efficient. You can code the same thing in C++/Qt and C/Gtk but it will take less lines and characters to do it with C++/Qt.

I'm a bit surprised that there is so much difference between C-Klotski and Cpp-Klotski. My interpretation is that when you have cleaner code, you can do things in a more simpler way.

PyQt-Klotski clearly rocks. 100 lines less than C-Klotski or Cpp-Klotski. But it uses almost the same number of characters as Cpp-Klotski. I would account that for the need to put 'self.' everywhere to access class member, which is a bit heavy.

Total

In the total, Gnotski is the biggest program with 1040 lines and 25000 characters. This is due to the difference of the languages. C++ and Qt both reduce the length of the code you have to type while C and Gtk/Gdk expands it because they are making object-oriented stuff in a non-object oriented language:

Cpp-Klotski and C-Klotski are almost equivalent with 960 lines and 19000 or 19900 characters. PyQt-Klotski establish its superiority with 700 lines and 15600 characters.



Funny stuff

Let's compute a few other statistics:

Getting rid of gtk_, gdk_ and _ in Gnotski

toolkits-comparison > cat gnotski/*.h gnotski/*.c | wc
   1054    3011   25696
toolkits-comparison > cat gnotski/*.h gnotski/*.c | sed -e 's/gtk_//g' -e 's/gdk_//g' -e 's/_//g' | wc
   1054    3011   24645

Only 1000 character less. I'm disappointed, I would have expected more than that. It looks like it will take more than just improve the function names to bring a Gtk program to the size of a Qt program

Getting rid of self in PyQt-Klotski

toolkits-comparison > cat pyqt-klotski/*.py | wc
    706    2156   15633                                                                                         
toolkits-comparison > cat pyqt-klotski/*.py | sed -e 's/self//g' | wc
    706    2151   14737

Only 1000 character less again. I'm disappointed, I would have expected more than that. But wait, that's 1/15 of the program, which is still a good deal of it. I'll add an alias to 'self' in my editor!


Other sources

Klotski is not the only source proving such statistics.

KVim

I am porting gvim to Kde. Bascially, I just copy/paste the code from Gtk and turns it into Kde/Qt stuff. It is almost always shorter and simpler. A relevant example:
Gtk dialog stuff Kde dialog stuff
#ifdef GUI_DIALOG
 
typedef struct _ButtonData {
    int        *status;
    int        index;
    GtkWidget  *dialog;
} ButtonData;
 
typedef struct _CancelData {
    int       *status;
    GtkWidget *dialog;
} CancelData;
 
/* ARGSUSED */
static void
dlg_button_clicked(GtkWidget * widget, ButtonData *data)
{
    *(data->status) = data->index + 1;
    gtk_widget_destroy(data->dialog);
}
 
/*
 * This makes the Escape key equivalent to the cancel button.
 */
 
/*ARGSUSED*/
static int
dlg_key_press_event(GtkWidget * widget, GdkEventKey * event, CancelData *data)
{
    if (event->keyval != GDK_Escape)
        return FALSE;
 
    /* The result value of 0 from a dialog is signaling cancelation. */
    *(data->status) = 0;
    gtk_widget_destroy(data->dialog);

    return TRUE;
}


/* ARGSUSED */
int
gui_mch_dialog(int type,                /* type of dialog */
               char_u * title,          /* title of dialog */
               char_u * message,        /* message text */
               char_u * buttons,        /* names of buttons */
               int def_but)             /* default button */
{
    char_u              *names;
    char_u              *p;
    int                 i;
    int                 butcount;
    int                 dialog_status = -1;
    int                 vertical;

    GtkWidget           *dialog;
    GtkWidget           *frame;
    GtkWidget           *vbox;
    GtkWidget           *table;
    GtkWidget           *pixmap;
    GtkWidget           *dialogmessage;
    GtkWidget           *action_area;
    GtkWidget           *sub_area;
    GtkWidget           *separator;
    GtkAccelGroup       *accel_group;

    GdkPixmap           *icon = NULL;
    GdkBitmap           *mask = NULL;
    char                **icon_data = NULL;

    GtkWidget           **button;
    ButtonData          *data;
    CancelData          cancel_data;

    if (title == NULL)
        title = (char_u *) "Vim dialog...";

    if ((type < 0) || (type > VIM_LAST_TYPE))
        type = VIM_GENERIC;

    /* Check 'v' flag in 'guioptions': vertical button placement. */
    vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL);

    /* if our pointer is currently hidden, then we should show it. */
    gui_mch_mousehide(FALSE);

    dialog = gtk_window_new(GTK_WINDOW_DIALOG);
    gtk_window_set_title(GTK_WINDOW(dialog), (const gchar *)title);
    gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
#ifdef GTK_HAVE_FEATURES_1_1_4
    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gui.mainwin));
#endif
    gtk_widget_realize(dialog);
    gdk_window_set_decorations(dialog->window, GDK_DECOR_BORDER);
    gdk_window_set_functions(dialog->window, GDK_FUNC_MOVE);

    cancel_data.status = &dialog_status;
    cancel_data.dialog = dialog;
    gtk_signal_connect_after(GTK_OBJECT(dialog), "key_press_event",
                    GTK_SIGNAL_FUNC(dlg_key_press_event),
                    (gpointer) &cancel_data);

    gtk_grab_add(dialog);

    /* this makes it look beter on Motif style window managers */
    frame = gtk_frame_new(NULL);
    gtk_container_add(GTK_CONTAINER(dialog), frame);
    gtk_widget_show(frame);

    vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(frame), vbox);
    gtk_widget_show(vbox);

    table = gtk_table_new(1, 3, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(table), 4);
    gtk_table_set_col_spacings(GTK_TABLE(table), 8);
    gtk_container_border_width(GTK_CONTAINER(table), 4);
    gtk_box_pack_start(GTK_BOX(vbox), table, 4, 4, 0);
    gtk_widget_show(table);

    /* Add pixmap */
    switch (type) {
    case VIM_GENERIC:
        icon_data = generic_xpm;
        break;
    case VIM_ERROR:
        icon_data = error_xpm;
        break;
    case VIM_WARNING:
        icon_data = alert_xpm;
        break;
    case VIM_INFO:
        icon_data = info_xpm;
        break;
    case VIM_QUESTION:
        icon_data = quest_xpm;
        break;
    default:
        icon_data = generic_xpm;
    };
    icon = gdk_pixmap_colormap_create_from_xpm_d(NULL,
                                     gtk_widget_get_colormap(dialog),
                                     &mask, NULL, icon_data);
    if (icon) {
        pixmap = gtk_pixmap_new(icon, mask);
        /* gtk_misc_set_alignment(GTK_MISC(pixmap), 0.5, 0.5); */
        gtk_table_attach_defaults(GTK_TABLE(table), pixmap, 0, 1, 0, 1);
        gtk_widget_show(pixmap);
    }

    /* Add label */
    dialogmessage = gtk_label_new((const gchar *)message);
    gtk_table_attach_defaults(GTK_TABLE(table), dialogmessage, 1, 2, 0, 1);
    gtk_widget_show(dialogmessage);

    action_area = gtk_hbox_new(FALSE, 0);
    gtk_container_border_width(GTK_CONTAINER(action_area), 4);
    gtk_box_pack_end(GTK_BOX(vbox), action_area, FALSE, TRUE, 0);
    gtk_widget_show(action_area);

    /* Add a [vh]box in the hbox to center the buttons in the dialog. */
    if (vertical)
        sub_area = gtk_vbox_new(FALSE, 0);
    else
        sub_area = gtk_hbox_new(FALSE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(sub_area), 0);
    gtk_box_pack_start(GTK_BOX(action_area), sub_area, TRUE, FALSE, 0);
    gtk_widget_show(sub_area);

    /*
     * Create the buttons.
     */

    /*
     * Translate the Vim accelerator character into an underscore for GTK+.
     * Double underscores to keep them in the label.
     */
    /* count the number of underscores */
    i = 1;
    for (p = buttons; *p; ++p)
        if (*p == '_')
            ++i;

    /* make a copy of "buttons" with the translated characters */
    names = alloc(STRLEN(buttons) + i);
    if (names == NULL)
        return -1;

    p = names;
    for (i = 0; buttons[i]; ++i)
    {
        if (buttons[i] == DLG_HOTKEY_CHAR)
            *p++ = '_';
        else
        {
            if (buttons[i] == '_')
                *p++ = '_';
            *p++ = buttons[i];
        }
    }
    *p = NUL;

    /* Count the number of buttons and allocate button[] and data[]. */
    butcount = 1;
    for (p = names; *p; ++p)
        if (*p == DLG_BUTTON_SEP)
            ++butcount;
    button = (GtkWidget **)alloc((unsigned)(butcount * sizeof(GtkWidget *)));
    data = (ButtonData *)alloc((unsigned)(butcount * sizeof(ButtonData)));
    if (button == NULL || data == NULL)
    {
        vim_free(names);
        vim_free(button);
        vim_free(data);
        return -1;
    }

    /* Attach the new accelerator group to the window. */
    accel_group = gtk_accel_group_new();
    gtk_accel_group_attach(accel_group, GTK_OBJECT(dialog));

    p = names;
    for (butcount = 0; *p; ++butcount) {
        char_u          *next;
        GtkWidget       *label;
        guint           accel_key;

        /* Chunk out this single button. */
        for (next = p; *next; ++next) {
            if (*next == DLG_BUTTON_SEP) {
                *next++ = NUL;
                break;
            }
        }

        button[butcount] = gtk_button_new();
        GTK_WIDGET_SET_FLAGS(button[butcount], GTK_CAN_DEFAULT);

        label = gtk_accel_label_new("");
        gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), dialog);

        accel_key = gtk_label_parse_uline(GTK_LABEL(label), (const gchar *)p);
# ifdef GTK_USE_ACCEL
        /* Don't add accelator if 'winaltkeys' is "no". */
        if (accel_key != GDK_VoidSymbol) {
            gtk_widget_add_accelerator(button[butcount],
                    "clicked",
                    accel_group,
                    accel_key, 0,
                    0);
        }
# endif

        gtk_container_add(GTK_CONTAINER(button[butcount]), label);
        gtk_widget_show_all(button[butcount]);

        data[butcount].status = &dialog_status;
        data[butcount].index = butcount;
        data[butcount].dialog = dialog;
        gtk_signal_connect(GTK_OBJECT(button[butcount]),
                           (const char *)"clicked",
                           GTK_SIGNAL_FUNC(dlg_button_clicked),
                           (gpointer) &data[butcount]);

        gtk_box_pack_start(GTK_BOX(sub_area), button[butcount],
                           TRUE, FALSE, 0);
        p = next;
    }

    vim_free(names);

    --butcount;
    --def_but;      /* 1 is first button */
    if (def_but < 0)
        def_but = 0;
    if (def_but > butcount)
        def_but = butcount;

    gtk_widget_grab_focus(button[def_but]);
    gtk_widget_grab_default(button[def_but]);

    separator = gtk_hseparator_new();
    gtk_box_pack_end(GTK_BOX(vbox), separator, FALSE, TRUE, 0);
    gtk_widget_show(separator);

    dialog_status = -1;
    gtk_widget_show(dialog);

    /* loop here until the dialog goes away */
    while (dialog_status == -1 && GTK_WIDGET_VISIBLE(dialog))
        gtk_main_iteration_do(TRUE);

    if (dialog_status < 0)
        dialog_status = 0;

    /* let the garbage collector know that we don t need it anylonger */
    gtk_accel_group_unref(accel_group);

    vim_free(button);
    vim_free(data);

    return dialog_status;
}


#endif  /* GUI_DIALOG */
#ifdef GUI_DIALOG

/* ARGSUSED */
int
gui_mch_dialog(int type,                /* type of dialog */
               char_u * title,          /* title of dialog */
               char_u * message,        /* message text */
               char_u * buttons,        /* names of buttons */
               int def_but)             /* default button */
{
        //dbf("dialog : %d - %s - %s - '%s' - %d", type, title, message,
        //buttons, def_but );
        VimDialog vd(type, title, message, buttons, def_but);
        int ret = vd.exec();
        //dbf("return : %d", ret);
        return ret;
}


#endif  /* GUI_DIALOG */


#include 
#include 
#include 

class VimDialog : public QDialog
{

public:
        VimDialog (int type,            /* type of dialog */
               unsigned char * title,           /* title of dialog */
               unsigned char * message, /* message text */
               unsigned char * buttons, /* names of buttons */
               int def_but);            /* default button */

protected:
        void reject() { done(0); }
        void accept() { done( result() ); }
private:
        QSignalMapper   mapper;
        QPushButton     *buttonArray[20];
        int             butNb;
};

/*
 *   Vim Dialog
 *
 * Returns:
 *  0: Cancel
 *  1- : nb of the pressed button
 */

VimDialog::VimDialog (int type,         /* type of dialog */
               char_u * title,          /* title of dialog */
               char_u * message,        /* message text */
               char_u * buttons,        /* names of buttons */
               int def_but)             /* default button */
        :QDialog(vmw, "vim generic dialog", true), // true is for "modal"
        mapper(this, "dialog signal mapper")
{

        /*
         *  Create buttons
         */
        char_u * mybuttons = vim_strsave( buttons );
        if (mybuttons == NULL)
                return;
        char_u  *p = mybuttons;
        char_u  *q = p + strlen((char*)mybuttons);
        butNb   = 0;

        // example
        // VimDialog::VimDialog (this=0x7fffecb8, type=2, title=0x19008cc "VIM
        // - ATTENTION",
        // message=0x1998a70 "Swap file \".gui_kde.cc.swp\" already exists!",
        // buttons=0x19008f8 "&Open Read-Only\n&Edit anyway\n&Recover\n&Quit",
        // def_but=1)

        QStringList buttonText;
        while (1) {
                while(q>=p && *q!= DLG_BUTTON_SEP) q--; // find next one
                //dbf("adding button : \"%s\"\n", q+1);
                buttonText.prepend( QString( (char *) (q+1) ) );
                butNb++;
                if (qsetAccel( s.at(s.find('&')+1).latin1()
);
                }
        }

        // if mouse pointer is hiddin, show it
        gui_mch_mousehide( false );

        /*
         * Create Icon
         */
        char ** icon_data;
        switch (type) {
        case VIM_GENERIC:
                icon_data = generic_xpm;
        break;
        case VIM_ERROR:
                icon_data = error_xpm;
        break;
        case VIM_WARNING:
                icon_data = alert_xpm;
        break;
        case VIM_INFO:
                icon_data = info_xpm;
        break;
        case VIM_QUESTION:
                icon_data = quest_xpm;
        break;
        default:
                icon_data = generic_xpm;
        };

        QLabel * icon = new QLabel( this );
        icon->setPixmap( QPixmap( (const char **) icon_data ) );
        icon->setFixedSize( icon->sizeHint() );

        QLabel * text = new QLabel( (char *) message, this );
        text->setAlignment( AlignHCenter | AlignVCenter | ExpandTabs );

        /*
         *  Layout
         */

        QVBoxLayout * vly = new QVBoxLayout( this, 5, 5 );
        QHBoxLayout * hly1 = new QHBoxLayout( vly, 5);
        hly1->addWidget( icon );
        hly1->addWidget( text );

        QHBoxLayout * hly2 = new QHBoxLayout( vly, 15);
        for( int i=0; iaddWidget( buttonArray[i] );
                if (i == def_but-1) {
                        buttonArray[i]->setDefault( true );
                        buttonArray[i]->setAutoDefault( true );
                        setResult( i+1 );
                }
                connect(buttonArray[i], SIGNAL(clicked()), &mapper,
SLOT(map()));
                mapper.setMapping(buttonArray[i], i+1);
        }
        connect( &mapper, SIGNAL(mapped(int)), this, SLOT(done(int)));

        setCaption((const char *) title);

        vly->activate();
}


VeePee: Python Support for GNOME and KDE Applications

VeePee, at its current stage of development, is a set of components aimed at GNOME and KDE application developers to enable them to easily add scripting capabilities to their applications.

VeePee wraps equivalent code for Gnome and Kde. In the FAQ, the author answers :

Isn't supporting multiple GUI toolkits going to be difficult to maintain?
In reality GUI toolkits all provide a similar set of features, they just do it in different ways. In the current version of VeePee 74% of the code is GUI independent, 15% is GNOME specific and 11% is KDE specific.

Again, Gnome code is 33% bigger than the equivalent Kde code.


Real conclusion

This statistics shouldn't make us lose the main conclusion from this analysis:

It is trivial to port a program from Gtk to Qt


You just have to wrap Cpp functions and classes around C functions and adapt Gtk code to the equivalent Qt code. This is an easy process and I have done it several times, for example for kvim.


Personal interpretation

A toolkit is a set of widgets that handles asynchronous activation events. So this is in essence an object-oriented thing. Gtk has choosen to handle that in C. I think Qt made the good choice, an object oriented language like C++ is definitely more suited to handle this kind of things.

One benefit of Qt here is that you have shorter syntax. If you consider only the number of typed characters, Gtk seems to require 4/3 more characters than Qt/C++. This can mean a fairly big amount of time.

Then we come to PyQt. Python is very good language, at the same time very simple and powerful. Switching from Qt/C++ to PyQt is very simple, everything have the same name. It could even be done by a python script. And you can reuse all the copious and very good Qt documentation and that is a very strong point.


Personal advices

If you are starting a new application please consider the following advices:

If you want to do plain C and C only, then use Gtk. It was made for you. But is Gtk really C ? This is C with constructors, destructors, objects, dynamic casting, inherintance, etc. This is C with everything to handle ojbects. Perhaps a real object-oriented language would make your program easier to write and debug, don't you think ?

If you accept to do C++, I strongly suggest Qt. There is excellent and copious documentation, your program will be shorter. C++ is the native language of Qt, which means there are no delays for new versions or wrapping problems. Guillaume Laurent, who used to be co-maintainer of Gtk-- (one C++ binding for Gtk) has switched to Qt/Kde with happiness. And, last but not least, Qt is more advanced than Gtk. Qt has had for more than one years features that were introduced lately in Gtk or that you will only find in the upcoming reelase of Gtk 2.0 : Unicode, Canvas, XML, threads, ...

Now, if your application doesn't need heavy time critical calculations, I suggest PyQt even more than Qt. PyQt integrates with Qt as if it was its native language. And Python is such a great language! You will gain in code size, in readability, in compilation time (there is none) and in efficiency. Because the language is natively very powerful, you can use more complex structures without hassle. And don't forget that PyQt can be used directly on Windows and Linux.


Feedback

Did you like this study ? Or did you dislike it ? Did I make some mistake ? Then please write me back at phil at freehackers dot org.

I recognised I prefer Qt to Gtk but I have tried to stay as neutral as possible during this study, except for the conclusions marked as "Personal".

Did you like the game ? Then check the real klotski I have written. It is a fresh start, uses the QCanvas, has more features and shares almost no code with these klotski.

It would be really cool to have this same program ported to other toolkits or language, to have a more interesting study. I'd really like to see a PyGtk version and a GtkAda version for example.


Last modification : $Date: 2005/07/02 16:23:24 $ - $Author: philippe $