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).
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.
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.)
Here come a few examples for you to take a quick look at the language.
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):
Sure, not very sexy, but fast to write.
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:
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.
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:
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...
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:
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
.
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:
If you type "Eve" in the text entry, you should see:
Pressing the key Enter
leads to:
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.)
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):
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.
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?
Check gcomposer which
is a real-life example using gtkcooker
for its GUI. Check
the gui/
subdirectory.
In case of troubles, don't hesitate to contact the authors.
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.)
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