gtkcooker

Note that I doubt I will ever make a new release of this. It was a good idea except I don't think it's the right thing for what I need. Anyway, if you want to give it a try... (not to mention I use it heavily while at the same time thinking about a better approach).

Introduction

gtkcooker is an abstract GUI language that compiles to C/GTK+.

It is released in the public domain (freedom has no license).

The target audience is hackers who waste too much time handling the complexity of GTK+ (how do I create a GtkEntry? how do I pack stuff using this weird gtk_box_pack_start? how to make a menu? to handle events?). Sure GTK+ is nice because it has a lot of features. But one wastes too much time working with it.

Some people invented glade to draw the GUI instead of writing code. Good. Except I want to write code, not draw. And it's bloated (XML-based). So no glade for me.

There are other stuff around but nothing fits my needs, so here is gtkcooker.

It surely lacks a lot of features for you (mostly lists/trees I guess) but hey, maybe someone will find it useful, so here it is.

And contributions are welcome to improve it.

Download

August, 31st, 2012: gtkcooker 0.3 2012/08/31.
A little bug fix.

August, 4th, 2011: gtkcooker 0.3 2011/08/04.
Adding radio and check menu items.

April, 7th, 2008: gtkcooker 0.3 2008/04/07.
This was the release for Savannah (free software hosting service from the GNU project). The file is back here. I won't change anything in it. Nobody will ever use it, it's plain useless to bother. (I should change URLs in it.)

Examples

Here come a few examples for you to take a quick look at the language.

hello.k

The famous "hello world".

widget hello
{
  window win;
  label hello("Hello world!");
  win > hello;
  win.delete = gtk_main_quit();
}

ccode {

int main(void)
{
  hello h;

  gtk_init(0, NULL);

  init_hello(&h);
  gtk_widget_show_all(h.win);
  gtk_main();

  return 0;
}

}

You create a GUI element (a widget) using the widget keyword. Inside it you define various GUI elements (here a window and a label), you pack them inside each others (here win > hello) and you handle events (here you quit GTK+ when the user deletes the window).

You can put C code in the file using the ccode keyword.

gtkcooker creates structures out of widgets, here we have hello. You have to call init_hello to create the GUI and then you pass the hand to GTK+.

Running gtkcooker hello.k will produce two files: hello.h and hello.c.

Here is hello.h:

/* generated by gtkcooker (http://savannah.nongnu.org/gtkcooker) */

#ifndef _HELLO_H_
#define _HELLO_H_

#include <gtk/gtk.h>

typedef struct hello {
  GtkWidget *win;
  unsigned long win_delete;
  GtkWidget *hello;
} hello;

int init_hello(hello *this);

#endif /* _HELLO_H_ */

And here is hello.c:

/* generated by gtkcooker (http://savannah.nongnu.org/gtkcooker) */

#include "hello.h"




int main(void)
{
  hello h;

  gtk_init(0, NULL);

  init_hello(&h);
  gtk_widget_show_all(h.win);
  gtk_main();

  return 0;
}


static gboolean _1(GtkWidget *_widget, GdkEvent *_event, hello *this)
{
{gtk_main_quit();}
  return TRUE;
}

/* returns -1 on error, 0 if everything is fine */
int init_hello(hello *this)
{
  GtkWidget *_0;
  GtkWidget *_2;

  this->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  {
    GdkGeometry _geo; _geo.min_width = _geo.min_height = 1;
    gtk_window_set_geometry_hints(GTK_WINDOW(this-gt;win),
        this-gt;win, &_geo, GDK_HINT_MIN_SIZE);
  }
  _0 = gtk_vbox_new(FALSE, 0);
  this->hello = gtk_label_new(0);
  gtk_label_set_markup_with_mnemonic(GTK_LABEL(this->hello), "Hello world!");
  _2 = gtk_hbox_new(FALSE, 0);

  gtk_container_add(GTK_CONTAINER(this->win), _0);
  gtk_box_pack_start(GTK_BOX(_0), _2, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(_2), this->hello, FALSE, FALSE, 0);


  this->win_delete = g_signal_connect(this->win, "delete-event", G_CALLBACK(_1), this);

  return 0;
}

You compile hello.c like that:

gcc -Wall -o hello `pkg-config gtk+-2.0 --cflags --libs` hello.c

And you obtain the following stuff (under the wonderful SedWM window manager):

[image: screenshot of hello.k in action]

Sure, not very sexy, but fast to write.

hello_you.k

Here now comes hello_you.k.

widget hello(char *name)
{
  window win;
  label hello("Hello ");
  label you(name);
  win > hello, you;
  win.delete = gtk_main_quit();

  button quit("_OK");
  quit.clicked = gtk_main_quit();

  win > separator;
  win > quit;
}

ccode {

int main(int n, char **v)
{
  hello h;

  if (n < 2) return 1;

  gtk_init(0, NULL);

  init_hello(&h, v[1]);
  gtk_widget_show_all(h.win);
  gtk_main();

  return 0;
}

}

You then run (bash shell):

./hello_you my\ dear\ friend\ from\ Paris\!

Leading to:

[image: hello_you in action]

It demonstrates how to pass arguments to the widget's creation. Here we pass a string that will be used to create a label.

You also see how to pack several elements at the same time (win > hello, you).

Finally you see a new keyword (separator) used to create, well, separators.

hello_you_pretty.k

The previous examples are not very pretty (color, spacing).

You can specify these properties.

For example, let's take hello_you.k and make it more pretty.

widget hello(char *name)
{
  window win;

  label hello("Hello ");
  label you(name);

  hello.halign = right;
  you.halign = left;
  hello.grow = fill;
  you.grow = fill;

  win.background = "lightblue";

  box blue_box;
  blue_box.grow = fill;
  blue_box >(border:20, grow:fill) hello, you;

  blue_box.foreground = "darkblue";

  win >(grow:fill) blue_box;
  win.delete = gtk_main_quit();

  button quit("_OK");
  quit.width = 50;
  quit.clicked = gtk_main_quit();

  box space;
  space.grow = fill;

  win > separator;
  win >(border:5) space, quit;
}

ccode {

int main(int n, char **v)
{
  hello h;

  if (n < 2) return 1;

  gtk_init(0, NULL);

  init_hello(&h, v[1]);
  gtk_widget_show_all(h.win);
  gtk_main();

  return 0;
}

}

Leading to:

[image: hello_you_pretty in action]

Several stuff here.

First you see that to specify properties of GUI elements you use the same syntax than for specifying events (win.background = "lightblue").

You also see that packing has properties like GUI elements do (blue_box >(border:20, grow:fill) hello, you).

grow is to handle resizing of the window. By default when you resize a window the new space is not used. You may choose which elements in the GUI get it and how they get it. (Well, if you don't know about that in GTK+ you certainly won't get the point.)

You see that grow is used at several places (as a property of elements and as a property of a packing). I am currently not very satisfied with that. It may change in the future. (I spend a lot of time trying to figure out how to setup this grow stuff for the resizing to work as I wish.)

You may specify colors, foreground and background. (Note that setting the background of a box does not work.)

I think properties speak for themselves. No need to describe them all.

See also the trick to force the button to go to the right. You put an empty box before it and you do a grow = fill on the box so that it takes all remaining space. The button is then flushed to the right.

This trick is not very pretty. One would like to have anonymous items to create the box without naming it and add it directly in the packing line. Something like:

win >(border:5) box(grow:fill), quit;

Or maybe even simpler methods...

Well, maybe one day...

hello_title.k

This example illustrates how to call C code in the init routine. Since not all GTK+ is available in gtkcooker we need a way to directly access the missing stuff. The init construct does the trick. You may put one at the beginning of the init routine and one at the end.

In the example below we set the title of the window.

widget hello
{
  window win;
  label hello("Hello world! How do you do?");
  win > hello;
  win.delete = gtk_main_quit();

  init {
    gtk_window_set_title(GTK_WINDOW(this->win), "gtkcooker example");
  }
}

ccode {

int main(void)
{
  hello h;

  gtk_init(0, NULL);

  init_hello(&h);
  gtk_widget_show_all(h.win);
  gtk_main();

  return 0;
}

}

Leading to:

[image: hello_title in action]

You see the title of the window in the title bar.

To access GUI elements you use this-> followed by the name of the GUI element. There it is this->win.

hello_mvc.k

A more complex example. There the GUI shows some data from the application and lets the user modify the data. It's some kind of model/view/controller stuff (even if I hate to call that this way, it reminds me some bad time in private companies).

#cinclude <string.h>

hcode {

typedef struct {
  char *value;
} data;

}

widget gui(data *d)
{
  {
    data *saved_d;
  }
  init {
    this->saved_d = d;
  }

  window win;
  label current("Value: ");
  label value("");

  label t_label("New value: ");
  text t;

  box box_left;
  box box_right;

  box_left > current;
  box_left > t_label;

  box_right > value;
  box_right > t;

  win > box_left, box_right;

  t.activate = gtk_label_set_text(GTK_LABEL(this->value),
                 gtk_entry_get_text(GTK_ENTRY(this->t)));

  button quit("_OK");

  win > separator;
  win > quit;

  quit.clicked = {
    /* we skip error's handling. In real code you *must* take care
     * of possible failures from strdup
     */
    /* you *must* free the old memory as well, there is a leak here */
    this->saved_d->value = strdup(gtk_label_get_text(GTK_LABEL(this->value)));
    gtk_main_quit();
  };

  init {
    gtk_label_set_text(GTK_LABEL(this->value), d->value);
  }
}

ccode {

int main(void)
{
  data my_data;
  gui my_gui;

  my_data.value = "Alice";

  gtk_init(0, NULL);

  init_gui(&my_gui, &my_data);

  gtk_widget_show_all(my_gui.win);
  gtk_main();

  printf("Value after GUI interaction: %s\n", my_data.value);
  return 0;
}

}

When you run it you see:

[image: hello_vmc first screen]

If you type "Eve" in the text entry, you should see:

[image: hello_vmc second screen]

Pressing the key Enter leads to:

[image: hello_vmc third screen]

The value has been updated. We see "Eve" instead of "Alice."

When you quit the program you see the new value printed in the shell's window.

Several new constructs appear. #cinclude leads to an #include statement in the produced C file. hcode is like ccode except that the code is put in the produced H file.

You see that you can have local variables in widgets. (Here it is saved_d.) They are put in the GUI's structure in the H file. You access them like GUI elements, using the this-> stuff. With them you easily connect your GUI with external data.

You also see a trick to align GUI elements. Instead of packing like that:

  win > current, value;
  win > t_label, t;

We do:

  box box_left;
  box box_right;

  box_left > current;
  box_left > t_label;

  box_right > value;
  box_right > t;

  win > box_left, box_right;

So that value and t are vertically aligned.

Yeah, I agree. It is very ugly. You don't quickly understand that all this is done to align GUI elements. Definitely some more abstract way to do that is required. (If you have ideas, share!) (It has to be easy to implement.)

draw.k

Another example, to show you how some variables are made available for events' handlers.

widget draw_widget {
  window win;
  draw d;

  d.width = 320;
  d.height = 200;
  d.background = "white";

  win > d;

  label l("You clicked at position: ");
  label m("(none)");

  win > l, m;

  d.pointer = {
    char s[64];

    if (type != GDK_BUTTON_PRESS) return TRUE;

    sprintf(s, "%dx%d", x, y);
    gtk_label_set_text(GTK_LABEL(this->m), s);
  };

  button quit("_OK");
  win > quit;

  quit.clicked = gtk_main_quit();
  win.delete = gtk_main_quit();
}

ccode {

int main(void)
{
  draw_widget d;

  gtk_init(0, NULL);

  init_draw_widget(&d);
  gtk_widget_show_all(d.win);

  gtk_main();

  return 0;
}

}

Leading to (after you click in the white zone):

[image: draw.k in action]

Here you see that in d.pointer we use the variables type, x and y. gtkcooker automatically generate these variables for you to use them in the event's handler. Read the produced draw.c to look at what really happens.

menu.k

As a last example, here is a basic menu's example.

widget hello
{
  window win;

  menu m("_File", "_Help");
  menu m_file("_Open...", separator, "_Quit");
  menu m_file_open("_File", "_Image");
  menu m_help("_About", "_Tutorial");
  m > m_file, m_help;
  m_file > m_file_open;

  m_file_open[0].activate = printf("you want to open a file\n");
  m_file_open[1].activate = printf("you want to open an image\n");

  m_file[2].activate = gtk_main_quit();

  m_help[0].activate = printf("you want to know about the program\n");
  m_help[1].activate = printf("you want to see the tutorial\n");

  win > m;

  label hello("Hello world!");
  win > hello;
  win.delete = gtk_main_quit();
}

ccode {

int main(void)
{
  hello h;

  gtk_init(0, NULL);

  init_hello(&h);
  gtk_widget_show_all(h.win);
  gtk_main();

  return 0;
}

}

Only one keyword (menu) to create menus and submenus. You simply pack submenus in menus just like you pack other GUI elements. Events in menus need a special treatment (the [n] construct). And you see the separator keyword to have, well, separators between menus' items.

Simple, no?

More complex examples

Check gcomposer which is a real-life example using gtkcooker for its GUI. Check the gui/ subdirectory.

Troubles

In case of troubles, don't hesitate to contact the authors.

Related software

There are a lot of projects around there to ease the life of GUI programmers. Some are based on XML (like glade), so they simply are ruled out (too bloated). Some look similar to gtkcooker. I especially think about IUP (the LED version, see this example) and JavaFX.

gtkcooker looks a lot like IUP (with LED). Sure, we don't have nested constructs. (By the way it should not be very hard to add, maybe one day...) So why not use IUP instead of writing this big hack? Well IUP (with LED) is simply too far away from my needs and does not integrate well in my current coding's practice.

JavaFX also has nested constructs. It also has dynamic behavior (when you change some data the GUI automatically updates itself). Is it a useful feature? Well, why not. Anyway JavaFX is for, well, java. And java is not what I use. So JavaFX is ruled out for me.

PHP-GTK has also some nice features, like passing custom parameters to signal handlers. In gtkcooker we don't directly pass arguments to signal handlers, but the handler's code can access all data from the widget using the this-> construct. (OK, this is more some kind of object oriented stuff.) If I may, I would give an advice to PHP-GTK people. Simplify the GUI creation. You don't need to use all these ugly pack_start and the like. You just rewrote GTK+ in PHP, this is not very pretty. Take a look at IUP (or, well, gtkcooker) and see what kind of abstraction you can get. I am sure some similar stuff may be done in PHP.

Some inspiration comes from HTML/DOM/CSS. (Sure, we are far away from that, but the simplicity of it is a source of inspiration.)

Missing stuff, future work

Of course a lot of GTK+ is missing. But hey... time and laziness you know...

It would be nice to have "anonymous items" to remove the need to create names when one wants a box to pack stuff for example. In such a case one does not care about the box, so one would like to pack directly without having to think about a name.

Another problem is the huge number of keywords. You cannot use a keyword to name an element. It's annoying sometimes. (Doing window box; is an error, box is a keyword. And we have many keywords.)

We don't have dynamic behaviour like JavaFX. (But is it useful?)

No debugging. When gtkcooker fails to compile your code it may be hard to know why and what to change. And the produced code may be hard to debug as well.

I think colors are not well handled (we only parse, we don't allocate, it might be bad, especially with low-color screens, like 8bpp ones that have indexed colormaps).

There also might be a problem with callbacks when you destroy a widget. Should we unconnect the signal? may a signal handler be triggered after the deletion of a widget? If yes we are in trouble...

Anyway, I use gtkcooker. It is simple. I write my GUIs fast. It works well. It is not bloated. If something is missing I can add it. So...

Maybe other hackers out there might like it. Or be inspired by it. Or not...


Contact: sed@free.fr

Created: Mon, 07 Apr 2008 12:40:05 +0200
Last update: Fri, 31 Aug 2012 18:23:46 +0200