The first thing to do of course, is download the VEGTK source and install it. You can always get the latest version from www.object-tools.com http://www.object-tools.com/. The second thing to do is download GTK sources.You can always get the latest version from ftp.gtk.org in /pub/gtk. You can also view other sources of GTK information on http://www.gtk.org/ http://www.gtk.org/. GTK uses GNU autoconf for configuration. Once untar'd, type ./configure --help to see a list of options. Please look at GTK documentation about the installation procedure for GTK.
To install VEGTK please look at ReadMe and Install files for your compiler.
To begin our introduction to VEGTK, we'll start with the simplest program possible. This program will create a 200x200 pixel window and has no way of exiting except to be killed using the shell.
-- example-start base base.e
class BASE
inherit
-- All constants we need are defined in the class below.
-- eg gtk_window_toplevel etc.
GTK_CONSTANTS
-- This class contains common features like vegtk_init and so
VEGTK_MAIN
creation
make
feature -- Creation
make is
local
window : GTK_WINDOW;
do
vegtk_init
!!window.make(Gtk_window_toplevel)
window.show;
gtk_main
end
end -- class BASE
-- example-end
Compiling the the above program depends on Eiffel compiler you use. In case of VisualEiffel you can do it using:
vec -a:Make_It.esd
*.esd files are provided with examples.
Root class of the application that uses VEGTK must inherit from GTK_CONSTANTS and VEGTK_MAIN which declare procedures, constants etc. That will be used in your VEGTK application.
The line:
vegtk_init;
calls the procedure vegtk_init; which will be called in all VEGTK applications. This sets up a few things for us such as the default visual and color map and then proceeds to call gdk_init(). This function initializes the library for use, sets up default signal handlers, and checks the arguments passed to your application on the command line, looking for one of the following:
--gtk-module
--g-fatal-warnings
--gtk-debug
--gtk-no-debug
--gdk-debug
--gdk-no-debug
--display
--sync
--no-xshm
--name
--class
It removes these from the argument list, leaving anything it does not recognize for your application to parse or ignore. This creates a set of standard arguments accepted by all GTK applications. To get the changed argument list use the following feature:
vegtk_args : ARRAY[STRING]
Again, this array won't contain any argumens recognized and used by
gtk and gdk.
The next two lines of code create and display a window.
!!window.make(Gtk_window_toplevel)
window.show;
The Gtk_window_toplevel argument specifies that we want the window to undergo window manager decoration and placement. Rather than create a window of 0x0 size, a window without children is set to 200x200 by default so you can still manipulate it.
The show procedure lets GTK know that we are done setting the attributes of this widget, and that it can display it.
The last line enters the GTK main processing loop.
gtk_main;
gtk_main; is another call you will see in every VEGTK application. When control reaches this point, GTK will sleep waiting for X events (such as button or key presses), timeouts, or file IO notifications to occur. In our simple example however, events are ignored.
Now for a program with a widget (a button). It's the classic hello world a la GTK.
-- example-start helloworld helloworld.e
class HELLOWORLD
inherit
-- All constants we need are defined in the class below.
-- eg gtk_window_toplevel etc.
GTK_CONSTANTS
-- This class contains common features like vegtk_init and so
VEGTK_MAIN
-- If we need to handle callbacks we must inherit from the below
-- class
VEGTK_CALLBACK_HANDLER
creation
make
feature -- Callback handlers
hello (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
-- This is a callback function. The data arguments are ignored
-- in this example. More on callbacks below.
do
print("Hello World%N");
end
delete_event (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
-- If you return False in the "delete_event" signal handler,
-- GTK will emit the "destroy" signal. Returning True means
-- you don't want the window to be destroyed.
-- This is useful for popping up 'are you sure you want to quit?'
-- type dialogs.
do
print ("delete event occurred%N");
-- Change True to False and the main window will be destroyed with
-- a "delete_event".
cb_data.set_return_value_boolean(True);
end
destroy (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
do
gtk_main_quit;
end
close (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
local
window : GTK_WINDOW;
do
window ?= data;
if window /= Void then
window.destroy;
end
end
feature -- Creation
make is
local
window : GTK_WINDOW;
button : GTK_BUTTON;
do
-- This is called in all GTK applications. Arguments are parsed
-- from the command line and are returned to the application.
vegtk_init;
!!window.make(Gtk_window_toplevel)
-- When the window is given the "delete_event" signal (this is given
-- by the window manager, usually by the 'close' option, or on the
-- titlebar), we ask it to call the delete_event function
-- as defined above.
signal_connect (window, "delete_event",$delete_event);
-- Here we connect the "destroy" event to a signal handler.
-- This event occurs when we call gtk_widget_destroy on the window,
-- or if we return 'FALSE' in the "delete_event" callback.
signal_connect (window, "destroy", $destroy);
-- Sets the border width of the window.
window.set_border_width (10);
-- Creates a new button with the label "Hello World".
!!button.make_with_label ("Hello World");
-- When the button receives the "clicked" signal, it will call the
-- procedure hello. The hello procedure is defined above.
signal_connect (button, "clicked",$hello);
-- This will cause the window to be destroyed by calling
-- window.destroy from procedure close when "clicked".
-- Again, the destroy signal could come from there,
-- or the window manager. Also note using data in this call
-- and in the procedure close .
signal_connect_with_data (button, "clicked",$close,window);
-- This packs the button into the window (a gtk container).
window.add (button);
-- The final step is to display this newly created widget.
button.show;
-- and the window
window.show;
-- All GTK applications must have a gtk_main. Control ends here
-- and waits for an event to occur (like a key press or
-- mouse event).
gtk_main
print ("End%N");
end
end -- class HELLOWORLD
-- example-end
Before we look in detail at helloworld, we'll discuss signals and callbacks. GTK is an event driven toolkit, which means it will sleep in gtk_main until an event occurs and control is passed to the appropriate function.
This passing of control is done using the idea of "signals". When an event occurs, such as the press of a mouse button, the appropriate signal will be "emitted" by the widget that was pressed. This is how GTK does most of its useful work. There are a set of signals that all widgets inherit, such as "destroy", and there are signals that are widget specific, such as "toggled" on a toggle button.
To make a button perform an action, we set up a signal handler to catch these signals and call the appropriate function. This is done by using a procedure such as:
signal_connect(widget : GTK_OBJECT; sig_name :STRING ;func_addr : POINTER)
or
signal_connect_with_data(widget : GTK_OBJECT; sig_name:STRING ;func_addr : POINTER;
data : ANY) is
Where the first argument is the widget which will be emitting the signal, and the second, the name of the signal you wish to catch. The third is the addres of the procedure you wish to be called when it is caught and the fourth, the data you wish to have passed to this procedure.
The procedure specified in the third argument is called a "callback procedure", and should generally be of the form:
callback_proc (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
callback_proc (data : ANY) is
callback_proc is
Where the first argument will be a data given as the last argument to the signal_connect_with_data procedure as shown above, and the second, an object that represent other arguments passed to callback procedure.
Because of the fact that Eiffel is an object oriented language using the callbacks is different comparing the C library GTK. The most significant differencies are:
All you need to know about VEGTK_CALLBACK_HANDLER is
All you need to know about VEGTK_CALLBACK_DATA
There are a number of a bit special procedures thow.
You need them if your callback procedure need to return value to the caller. Semantics of these functions are obviouse.
If you need some more information please look at sources of VEGTK_CALLBACK_HANDLER and VEGTK_CALLBACK_DATA.
In addition to the signal mechanism described above, there are a set of events that reflect the X event mechanism. Callbacks may also be attached to these events. These events are:
In order to connect a callback function to one of these events, you
use the procedure signal_connect, as described above, using one of the
above event names as the name
parameter.
Inside callback procedure you can inspect which event occured by using get_event function from VEGTK_CALLBACK_DATA. It will return an object of class GDK_EVENT.
At the C level GdkEvent is a C union
structure whose type will
depend upon which of the above events has occurred. In order for us to
tell which event has been issued each of the possible alternatives has
a type
parameter which reflects the event being issued. The other
components of the event structure will depend upon the type of the
event. Possible values for the type are:
GDK_NOTHING
GDK_DELETE
GDK_DESTROY
GDK_EXPOSE
GDK_MOTION_NOTIFY
GDK_BUTTON_PRESS
GDK_2BUTTON_PRESS
GDK_3BUTTON_PRESS
GDK_BUTTON_RELEASE
GDK_KEY_PRESS
GDK_KEY_RELEASE
GDK_ENTER_NOTIFY
GDK_LEAVE_NOTIFY
GDK_FOCUS_CHANGE
GDK_CONFIGURE
GDK_MAP
GDK_UNMAP
GDK_PROPERTY_NOTIFY
GDK_SELECTION_CLEAR
GDK_SELECTION_REQUEST
GDK_SELECTION_NOTIFY
GDK_PROXIMITY_IN
GDK_PROXIMITY_OUT
GDK_DRAG_BEGIN
GDK_DRAG_REQUEST
GDK_DROP_ENTER
GDK_DROP_LEAVE
GDK_DROP_DATA_AVAIL
GDK_CLIENT_EVENT
GDK_VISIBILITY_NOTIFY
GDK_NO_EXPOSE
GDK_OTHER_EVENT /* Deprecated, use filters instead */
In Eiffel variant there is a class GDK_EVENT with feature type and a number of derived classes
GDK_EVENT_BUTTON
GDK_EVENT_CLIENT
GDK_EVENT_CONFIGURE
GDK_EVENT_CROSSING
GDK_EVENT_DND
GDK_EVENT_EXPOSE
GDK_EVENT_FACTORY
GDK_EVENT_FOCUS
GDK_EVENT_KEY
GDK_EVENT_MOTION
GDK_EVENT_NO_EXPOSE
GDK_EVENT_PROPERTY
GDK_EVENT_PROXIMITY
GDK_EVENT_SELECTION
GDK_EVENT_VISIBILITY
Every derived class contains features specific to the type of event. Please look at the sources for the details.
So, to connect a callback function to one of these events we would use something like:
signal_connect( button, "button_press_event",$button_press_callback);
This assumes that button
is a GTK_BUTTON widget. Now,
when the mouse is over the button and a mouse button is pressed, the
procedure button_press_callback
will be called.
For details on the GDK_EVENT data types, see the appendix entitled GDK Event Types.
Frankly speaking, using an addres of the callback procedure looks not very good at OO point of view. Of course using just pointer is dangerous and it's a potential source of the errors. But we don't see enough good alternative for this aprouch. IMHO absence of the procedures as the first class objects is a serious problem of the Eiffel the Language. We hope to see in the nearest feature some improvements of the language, which would allow as to use more elegant mechanism.
For thouse who concerned about problems described in previous section we realized another callback mechanizm. The basic idea is:
There is an example of using this aprouch (in the directory examples/callbacks). It's very simple, so you probably won't have any problems understanding it.
Now that we know the theory behind this, lets clarify by walking through the example helloworld program.
Here is the callback function that will be called when the button is "clicked". We ignore both the widget and the callback data in this example, but it is not hard to do things with them. The next example will use the data argument to tell us which button was pressed.
hello (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
-- This is a callback function. The data arguments are ignored
-- in this example. More on callbacks below.
do
print("Hello World%N");
end
The next callback is a bit special. The "delete_event" occurs when the window manager sends this event to the application. We have a choice here as to what to do about these events. We can ignore them, make some sort of response, or simply quit the application.
The value you return in this callback lets GTK know what action to take. By returning True, we let it know that we don't want to have the "destroy" signal emitted, keeping our application running. By returning False, we ask that "destroy" is emitted, which in turn will call our "destroy" signal handler. Please note the mechanism of returning value from callback procedure.
delete_event (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
-- If you return False in the "delete_event" signal handler,
-- GTK will emit the "destroy" signal. Returning True means
-- you don't want the window to be destroyed.
-- This is useful for popping up 'are you sure you want to quit?'
-- type dialogs.
do
print ("delete event occurred%N");
-- Change True to False and the main window will be destroyed with
-- a "delete_event".
cb_data.set_return_value_boolean(True);
end
Here is another callback function which causes the program to quit by calling gtk_main_quit. This procedure tells GTK that it is to exit from gtk_main when control is returned to it.
destroy (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
do
gtk_main_quit;
end
This is another callback procedure which causes the program to quit but indirectly by calling window.destroy, which in turn causes call of the above callback procedure.
close (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
local
window : GTK_WINDOW;
do
window ?= data;
if window /= Void then
window.destroy();
end
end
feature -- Creation
make is
This next part, declares a variables, which represent owr widgets. These are used below to create a window and a button.
local
window : GTK_WINDOW;
button : GTK_BUTTON;
Here is our vegtk_init again. As before, this initializes the toolkit, and parses the arguments found on the command line. Any argument it recognizes from the command line, it removes from the list; To acces this modified array of command line arguments use feature vegtk_args.
do
vegtk_init;
Create a new window. This is fairly straight forward. It sets up a new window, but it is not displayed until we call window.show near the end of our program.
!!window.make(gtk_window_toplevel)
Here is an example of connecting a signal handler to an object, in this case, the window. Here, the "destroy" signal is caught. This is emitted when we use the window manager to kill the window (and we return False in the "delete_event" handler), or when we use the window.destroy(). By setting this up, we handle both cases with a single call. Here, it just calls the destroy() procedure defined above which quits GTK for us.
signal_connect (window, "destroy", $destroy);
This next function is used to set an attribute of a container object. This just sets the window so it has a blank area along the inside of it 10 pixels wide where no widgets will go. There are other similar functions which we will look at in the section on Setting Widget Attributes
window.set_border_width (10);
This call creates a new button. It will have the label "Hello World" on it when displayed.
!!button.make_with_label ("Hello World");
Here, we take this button, and make it do something useful. We attach a signal handler to it so when it emits the "clicked" signal, our hello procedure is called. Obviously, the "clicked" signal is emitted when we click the button with our mouse pointer.
signal_connect (button, "clicked",$hello);
We are also going to use this button to exit our program. When the button is "clicked", same as above,it calls the first hello() callback procedure, and then this one in the order they are set up. You may have as many callback functions as you need, and all will be executed in the order you connected them.
signal_connect_with_data (button, "clicked",$close,window);
This is a packing call, which will be explained in depth later on. But it is fairly easy to understand. It simply tells GTK that the button is to be placed in the window where it will be displayed. Note that a GTK container (which window is) can only contain one widget. There are other widgets, that are described later, which are designed to layout multiple widgets in various ways.
window.add (button);
Now we have everything set up the way we want it to be. With all the signal handlers in place, and the button placed in the window where it should be, we ask GTK to "show" the widgets on the screen. The window widget is shown last so the whole window will pop up at once rather than seeing the window pop up, and then the button form inside of it. Although with such a simple example, you'd never notice.
button.show;
window.show;
And of course, we call gtk_main which waits for events to come from the X server and will call on the widgets to emit signals when these events come.
gtk_main
And the final return. Control returns here after gtk_quit is called.
print ("End%N");
Now, when we click the mouse button on a GTK button, the widget emits a "clicked" signal. In order for us to use this information, our program sets up a signal handler to catch that signal, which dispatches the function of our choice. In our example, when the button we created is "clicked", the hello() procedure is called, and then the next handler for this signal is called. This calls the destroy procedure of the window. This causes the window to emit the "destroy" signal, which is caught, and calls our destroy callback procedure, which simply exits GTK.
Another course of events, is to use the window manager to kill the window. This will cause the "delete_event" to be emitted. This will call our "delete_event" handler. If we return True here, the window will be left as is and nothing will happen. Returning False will cause GTK to emit the "destroy" signal which of course, calls the "destroy" callback, exiting GTK.
Note that these signals are not the same as the Unix system signals, and are not implemented using them, although the terminology is almost identical.