GTK+ code rewriter

This page describes a small tool for migrating libraries and applications to GTK+ 3 as easily as possible. This is work in progress, and the changes that can be done so far include: The plan is to add support for all structure fields so that your code more or less automatically be made ready for GTK+ 3.

Using the tool

The rewriter works as a drop-in replacement for gcc. This means that as long as you are using gcc to build your code in any way, it's easy to use the tool:
 make clean
 make CC=ccc-gtk
The above builds the code using gcc as usual but also produces one patch file per source file with the changes for that file. Review the patches, apply them, and hopefully you're done. Note that the added accessors for GTK+ 2.13.x are not set in stone before 2.14 is released so don't commit the generated patches just yet.

Building

Get LLVM as described here. For the clang part, instead of getting it from svn, get the git repo here (make sure the cloned repo ends up in the llvm/tools/ directory):
 cd tools
 git clone git://git.imendio.com/projects/clang.git
 cd clang
 git checkout --track -b gtk origin/gtk
 make
 make install
(The part in the clang build instructions about patching clang.cpp should not be needed, at least not on Ubuntu Hardy.) Now you should be able to run make as described above, with "CC=ccc-gtk". Make sure to make clean first, since otherwise no files will be processed.

Helping out

If you want to help out, please try the tool out and see if there are any problems with running it on your code. There are still struct field accesses that are not covered, see the file gtk.rewrites in the git repo for the list of the ones which are handled. This list can easily be added to. Note that some fields don't have accessors yet in GTK+, which is also a good place to help out.

Another idea that could be implemented is to provide a compatibility header that does something along the lines of:

#if !GTK_CHECK_VERSION(2,16,0)
#define gtk_foo_get_bar(x) x->bar
... more accessors added in 2.16 ...
#endif
#if !GTK_CHECK_VERSION(2,14,0)
#define gtk_widget_get_window(x) x->window
#define gtk_widget_get_style(x) x->style
...  more accessors added in 2.14 ...
#endif

This would make it possible to clean up the code without requiring the absolutely latest GTK+ release (the header should be shipped with the app and included in some common header).

If you want to become a super hero, you can also help out on the actual tool itself, or clang. For instance, there are things missing in clang like exact source location information for nested macros which is needed to support rewriting code like the casting macros common in any GObject code.

Let me (richard a t imendio.com) know if you want to help out or if there are issues, and I'll try to assist.

Example output

Two examples of diffs created by the tool for the IM application Gossip. The first one shows rewritten includes and simple struct field accesses:
--- ephy-spinner.c	2008-07-16 22:52:06.000000000 +0200
+++ ephy-spinner.c-rewritten	2008-08-13 11:07:27.000000000 +0200
@@ -34,9 +34,7 @@
 #define STOP_PROFILER(name)
 
 #include <gdk-pixbuf/gdk-pixbuf.h>
-#include <gtk/gtkicontheme.h>
-#include <gtk/gtkiconfactory.h>
-#include <gtk/gtksettings.h>
+#include <gtk/gtk.h>
 
 /* Spinner cache implementation */
 
@@ -655,11 +653,11 @@ ephy_spinner_expose (GtkWidget *widget,
 	height = gdk_pixbuf_get_height (pixbuf);
 
 	/* Compute the offsets for the image centered on our allocation */
-	x_offset = (widget->allocation.width - width) / 2;
-	y_offset = (widget->allocation.height - height) / 2;
+	x_offset = (gtk_widget_get_allocation (widget).width - width) / 2;
+	y_offset = (gtk_widget_get_allocation (widget).height - height) / 2;
 
-	pix_area.x = x_offset + widget->allocation.x;
-	pix_area.y = y_offset + widget->allocation.y;
+	pix_area.x = x_offset + gtk_widget_get_allocation (widget).x;
+	pix_area.y = y_offset + gtk_widget_get_allocation (widget).y;
 	pix_area.width = width;
 	pix_area.height = height;
 
@@ -668,10 +666,10 @@ ephy_spinner_expose (GtkWidget *widget,
 		return FALSE;
 	}
 
-	gc = gdk_gc_new (widget->window);
-	gdk_draw_pixbuf (widget->window, gc, pixbuf,
-			 dest.x - x_offset - widget->allocation.x,
-			 dest.y - y_offset - widget->allocation.y,
+	gc = gdk_gc_new (gtk_widget_get_window (widget));
+	gdk_draw_pixbuf (gtk_widget_get_window (widget), gc, pixbuf,
+			 dest.x - x_offset - gtk_widget_get_allocation (widget).x,
+			 dest.y - y_offset - gtk_widget_get_allocation (widget).y,
 			 dest.x, dest.y,
 			 dest.width, dest.height,
 			 GDK_RGB_DITHER_MAX, 0, 0);


The second example shows code snippets that could not be handled automatically, with a comment inserted explaining why:
--- gossip-chat.c	2008-05-14 18:32:24.000000000 +0200
+++ gossip-chat.c-rewritten	2008-08-13 18:41:54.000000000 +0200
@@ -373,6 +373,11 @@ chat_input_text_buffer_changed_cb (GtkTe
 
 		gtk_widget_size_request (chat->input_text_view, &req);
 
+		/* REWRITE: Use an accessor function instead of direct access.
+		   Also change the code no to take the address of the return value
+		   since that does not work accessors that return non-pointer types.
+		   gtk_widget_get_allocation (GTK_WIDGET (chat->view))
+		 */
 		allocation = >K_WIDGET (chat->view)->allocation;
 
 		priv->default_window_height = window_height;
@@ -497,11 +502,11 @@ chat_text_view_size_allocate_cb (GtkWidg
 	}
 
 	sw = gtk_widget_get_parent (widget);
-	if (sw->allocation.height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
+	if (gtk_widget_get_allocation (sw).height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
 		GtkWidget *vscroll;
 
 		priv->vscroll_visible = TRUE;
-		gtk_widget_set_size_request (sw, sw->allocation.width, MAX_INPUT_HEIGHT);
+		gtk_widget_set_size_request (sw, gtk_widget_get_allocation (sw).width, MAX_INPUT_HEIGHT);
 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
 						GTK_POLICY_NEVER,
 						GTK_POLICY_AUTOMATIC);
@@ -519,6 +524,11 @@ chat_text_view_size_allocate_cb (GtkWidg
 	diff = priv->last_input_height - allocation->height;
 	priv->last_input_height = allocation->height;
 
+	/* REWRITE: Use an accessor function instead of direct access.
+	   Also change the code no to take the address of the return value
+	   since that does not work accessors that return non-pointer types.
+	   gtk_widget_get_allocation (GTK_WIDGET (chat->view))
+	 */
 	view_allocation = >K_WIDGET (chat->view)->allocation;
 
 	dialog = gossip_chat_window_get_dialog (priv->window);