GTK+ Extensions Made Easy Thanks to Vala

Writing proper GTK+ Widgets is difficult and time consuming. One of the big problems is the intricacies of the Gnome Object Model; the amount of boiler plate code that needs to be written to write a new class. Even writing a class that simply inherits from an existing class and adds some new state or behavior in tedious.

The problem is the syntax - the Gnome Object Model is a library for the C programming language that supplies a complete object model with inheritance, interfaces, reflection (for introspection) and so on. Where you might expect to see language features in an Object Orient language, you instead find naming conventions, macros and library function calls. Mastering the Gnome Object Model is no mean feat, and since C language bindings are preferred (possibly even expected, new Gnome libraries really need to be written in C. Or so that was the case until Vala came along.

Vala is a new language with syntax similar to C#. Vala does not compile directly to machine code, nor does it run on a virtual machine like .NET and Java. Rather, it translated into portable C code that is then compiled to machine code. The beauty of this is that the developer gets the benefit of writing in a higher level language than C which has appropriate language features while still being able to produce and distribute standard C language bindings for the Gnome Platform.

In this blog entry I demonstrate how to write a proper GTK+ Widget in Vala and compare it to what is required to write the same widget in C.

I keep going on about a proper widget. Let me clarify what that means. Consider the gtkmm library - the C++ binding to the GTK+ library. It is simple enough to write new gtkmm Widgets as shown throughout the gtkmm tutorials. Take this example - a new widget that is a toplevel window with a button in it. When the button is pressed, a message is printed on standard out:

  1. // In the header file:
  2.  
  3. class HelloWorld : public Gtk::Window
  4. {
  5. public:
  6. HelloWorld();
  7. virtual ~HelloWorld();
  8.  
  9. protected:
  10. //Signal handlers:
  11. virtual void on_button_clicked();
  12.  
  13. //Member widgets:
  14. Gtk::Button m_button;
  15. };
  16.  
  17.  
  18. // In the code file:
  19.  
  20. HelloWorld::HelloWorld()
  21. : m_button("Hello World") // creates a new button with label "Hello World".
  22. {
  23. // Sets the border width of the window.
  24. set_border_width(10);
  25.  
  26. // When the button receives the "clicked" signal, it will call the
  27. // on_button_clicked() method defined below.
  28. m_button.signal_clicked().connect(sigc::mem_fun(*this,
  29. &HelloWorld::on_button_clicked));
  30.  
  31. // This packs the button into the Window (a container).
  32. add(m_button);
  33.  
  34. // The final step is to display this newly created widget...
  35. m_button.show();
  36. }
  37.  
  38. HelloWorld::~HelloWorld()
  39. {
  40. }
  41.  
  42. void HelloWorld::on_button_clicked()
  43. {
  44. std::cout << "Hello World" << std::endl;
  45. }

The "HelloWorld" class above is simple to write and can be reused within any gtkmm application. It can not, however, be used by a GTK+ application written in C. Nor can it be included in any language bindings generated by using the introspection features of the GObject library - simply, the HelloWorld C++ class is not a GObject and is therefore a second class citizen in the Gnome world. It is not a proper GTK+ widget.

Let's implement the same class in Vala:

  1. using Gtk;
  2.  
  3. public class HelloWorld : Gtk.Window
  4. {
  5. construct
  6. {
  7. stdout.printf( "Here\n" );
  8. set_border_width( 10 );
  9.  
  10. m_button = new Gtk.Button();
  11. m_button.clicked += on_button_clicked;
  12.  
  13. add( m_button );
  14. m_button.show();
  15. }
  16.  
  17. private void on_button_clicked()
  18. {
  19. stdout.printf( "Hello World\n" );
  20. }
  21.  
  22. Gtk.Button m_button;
  23. }

The above Vala code is identical in effect to the C++ GTKMM code, yet it is simpler to read, more terse and intuitive. But beyond the syntactic sugar, the HelloWorld class defined in Vala is a real GObject class! That is, it is a first class citizen in the Gnome world!

Now hold your breath and have a look at the C code that Vala generates. This code can be distributed as the C bindings for the new GTK+ class:

In the header file:

  1. #include <glib.h>
  2. #include <glib-object.h>
  3. #include <gtk/gtk.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6.  
  7. G_BEGIN_DECLS
  8.  
  9. #define TYPE_HELLO_WORLD (hello_world_get_type ())
  10. #define HELLO_WORLD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_HELLO_WORLD, HelloWorld))
  11. #define HELLO_WORLD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_HELLO_WORLD, HelloWorldClass))
  12. #define IS_HELLO_WORLD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_HELLO_WORLD))
  13. #define IS_HELLO_WORLD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_HELLO_WORLD))
  14. #define HELLO_WORLD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_HELLO_WORLD, HelloWorldClass))
  15.  
  16. typedef struct _HelloWorld HelloWorld;
  17. typedef struct _HelloWorldClass HelloWorldClass;
  18. typedef struct _HelloWorldPrivate HelloWorldPrivate;
  19.  
  20. struct _HelloWorld {
  21. GtkWindow parent_instance;
  22. HelloWorldPrivate * priv;
  23. };
  24.  
  25. struct _HelloWorldClass {
  26. GtkWindowClass parent_class;
  27. };
  28.  
  29. HelloWorld* hello_world_construct (GType object_type);
  30. HelloWorld* hello_world_new (void);
  31. GType hello_world_get_type (void);

In the code file:

  1. struct _HelloWorldPrivate {
  2. GtkButton* m_button;
  3. };
  4.  
  5. #define HELLO_WORLD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_HELLO_WORLD, HelloWorldPrivate))
  6. enum {
  7. HELLO_WORLD_DUMMY_PROPERTY
  8. };
  9. static void hello_world_on_button_clicked (HelloWorld* self);
  10. static void _hello_world_on_button_clicked_gtk_button_clicked (GtkButton* _sender, gpointer self);
  11. static GObject * hello_world_constructor (GType type, guint n_construct_properties,
  12. GObjectConstructParam * construct_properties);
  13. static gpointer hello_world_parent_class = NULL;
  14. static void hello_world_finalize (GObject* obj);
  15.  
  16. static void hello_world_on_button_clicked (HelloWorld* self) {
  17. g_return_if_fail (self != NULL);
  18. fprintf (stdout, "Hello World\n");
  19. }
  20.  
  21.  
  22. HelloWorld* hello_world_construct (GType object_type) {
  23. HelloWorld * self;
  24. self = g_object_newv (object_type, 0, NULL);
  25. return self;
  26. }
  27.  
  28.  
  29. HelloWorld* hello_world_new (void) {
  30. return hello_world_construct (TYPE_HELLO_WORLD);
  31. }
  32.  
  33.  
  34. static void _hello_world_on_button_clicked_gtk_button_clicked (GtkButton* _sender, gpointer self) {
  35. hello_world_on_button_clicked (self);
  36. }
  37.  
  38.  
  39. static GObject * hello_world_constructor (GType type, guint n_construct_properties,
  40. GObjectConstructParam * construct_properties) {
  41. GObject * obj;
  42. HelloWorldClass * klass;
  43. GObjectClass * parent_class;
  44. HelloWorld * self;
  45. klass = HELLO_WORLD_CLASS (g_type_class_peek (TYPE_HELLO_WORLD));
  46. parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
  47. obj = parent_class->constructor (type, n_construct_properties, construct_properties);
  48. self = HELLO_WORLD (obj);
  49. {
  50. GtkButton* _tmp0;
  51. fprintf (stdout, "Here\n");
  52. gtk_container_set_border_width ((GtkContainer*) self, (guint) 10);
  53. _tmp0 = NULL;
  54. self->priv->m_button = (_tmp0 = g_object_ref_sink ((GtkButton*) gtk_button_new ()),
  55. (self->priv->m_button == NULL) ? NULL : (self->priv->m_button =
  56. (g_object_unref (self->priv->m_button), NULL)), _tmp0);
  57. g_signal_connect_object (self->priv->m_button, "clicked",
  58. (GCallback) _hello_world_on_button_clicked_gtk_button_clicked, self, 0);
  59. gtk_container_add ((GtkContainer*) self, (GtkWidget*) self->priv->m_button);
  60. gtk_widget_show ((GtkWidget*) self->priv->m_button);
  61. }
  62. return obj;
  63. }
  64.  
  65.  
  66. static void hello_world_class_init (HelloWorldClass * klass) {
  67. hello_world_parent_class = g_type_class_peek_parent (klass);
  68. g_type_class_add_private (klass, sizeof (HelloWorldPrivate));
  69. G_OBJECT_CLASS (klass)->constructor = hello_world_constructor;
  70. G_OBJECT_CLASS (klass)->finalize = hello_world_finalize;
  71. }
  72.  
  73.  
  74. static void hello_world_instance_init (HelloWorld * self) {
  75. self->priv = HELLO_WORLD_GET_PRIVATE (self);
  76. }
  77.  
  78.  
  79. static void hello_world_finalize (GObject* obj) {
  80. HelloWorld * self;
  81. self = HELLO_WORLD (obj);
  82. (self->priv->m_button == NULL) ? NULL : (self->priv->m_button =
  83. (g_object_unref (self->priv->m_button), NULL));
  84. G_OBJECT_CLASS (hello_world_parent_class)->finalize (obj);
  85. }
  86.  
  87.  
  88. GType hello_world_get_type (void) {
  89. static GType hello_world_type_id = 0;
  90. if (hello_world_type_id == 0) {
  91. static const GTypeInfo g_define_type_info = { sizeof (HelloWorldClass), (GBaseInitFunc) NULL,
  92. (GBaseFinalizeFunc) NULL, (GClassInitFunc) hello_world_class_init,
  93. (GClassFinalizeFunc) NULL, NULL, sizeof (HelloWorld),
  94. 0, (GInstanceInitFunc) hello_world_instance_init, NULL };
  95. hello_world_type_id = g_type_register_static (GTK_TYPE_WINDOW, "HelloWorld",
  96. &g_define_type_info, 0);
  97. }
  98. return hello_world_type_id;
  99. }

Hands up who want's to write the C version by hand.... no one. Hands up if you'd be happy writing the Vala version... eveyone.

Now that we can write simple extensions to GTK+, a world of possibilities for GTK+ extension writers is opened up. Best of all, new components could be made available in Glade and can be created from XML by the GtkBuilder. As an example, we can create an instance of our HelloWorld widget with the following GtkBuilder XML:

  1. <?xml version="1.0"?>
  2. <interface>
  3. <object class="HelloWorld" id="Root" />
  4. </interface>

And then load it with the following GTK+ code (written in C):

  1. GtkBuilder *builder;
  2. GtkWidget *w;
  3.  
  4. builder = gtk_builder_new ();
  5. gtk_builder_add_from_file (builder, "HelloWorld.xml", NULL);
  6.  
  7. w = GTK_WIDGET (gtk_builder_get_object (builder, "Root"));
  8. gtk_widget_show (w);
  9.  
  10. gtk_main ();
  11.  
  12. // we need to destroy top-level windows created by the builder
  13. gtk_widget_destroy( w );

Vala is taking the Gnome platform in a positive direction. It is a language that will be familiar to many with a C++, C# or Java background. It promotes reuse of Gnome components across a number of language bindings, leveraging the power of the Gnome object model and its introspection features.