-
Notifications
You must be signed in to change notification settings - Fork 43
Tutorial 1
At the end of this tutorial, you will know how to write a GTK application with a window, some widgets and how to draw a bitmap image.
Let's call our file my_first_gtk_app.f90
. Its two main parts will be:
- a module named
handlers
which will contain the functions that will handle the events and global variables like pointers toward widgets. Because writing a Graphical User Interface (GUI) needs events oriented programming: the interface will be composed of widgets and of callback functions that will be called when events occurs. To understand the GTK event-driven programming model, read the Main loop and Events documentation page. - The main program which will declare the GtkApplication.
Let's write the skeleton of our application:
module handlers
use iso_c_binding
use gtk, only:
use g, only:
implicit none
contains
end module handlers
program my_first_gtk_app
use handlers
implicit none
end program
The iso_c_binding
module, available since Fortran 2003, offers the constants for types interoperable between Fortran and C and functions to interoperate pointers. The gtk
module is the main module of gtk-fortran, defined in the src/gtk-auto.f90
and src/gtk.f90
files. The g
module is the GLib module (src/glib-auto.f90
file). The only
statement is highly recommended to lower the compilation time, because the gtk
and g
modules contain thousands of functions.
The recommended way to create a GTK application is to use the GtkApplication class (https://developer.gnome.org/gtk3/stable/GtkApplication.html), which will initialise the library and manage various operations to integrate the application in the system. Let's add in our main program the following lines :
type(c_ptr) :: app
integer(c_int) :: status
app = gtk_application_new("gtk-fortran.my_first_gtk_app"//c_null_char, &
& G_APPLICATION_FLAGS_NONE)
status = g_application_run(app, 0_c_int, c_null_ptr)
call g_object_unref(app)
The gtk_application_new()
function is returning a C pointer. We therefore declare our app
variable using the c_ptr
constant defined in the ISO_C_BINDING module. The first argument to pass is the application (unique) identifier: a string composed of alphanumerical characters containing at least one dot (see https://developer.gnome.org/gio/stable/GApplication.html#g-application-id-is-valid). But Fortran strings having a defined length, contrary to C strings terminated by a null character, we must append a c_null_char
each time we pass a Fortran string to GTK. The second argument, G_APPLICATION_FLAGS_NONE
, is a flag (see https://developer.gnome.org/gio/stable/GApplication.html for other values).
GTK is based on GLib and a GTK application is therefore a GLib application. We launch our application using the g_application_run()
function. Here we don't manage the command line arguments: the 0_c_int, c_null_ptr
arguments are the classical int argc, char **argv
used in C language. Calling that function means your program will enter the main loop of the GUI. That loop is idle most of the time, and waits for something to happen (an event). When quitting the application, the function will return the exit status in the status
variable, whose type must be interoperable with the C int
type.
When you read the GTK documentation, be conscious that GTK is using the GLib types instead of the C types: for example, gint
just stands for int
, gdouble
for double
, etc. See https://developer.gnome.org/glib/stable/glib-Basic-Types.html
The call g_object_unref(app)
line will free the memory. Note that C functions which return nothing (void
) are interfaced in Fortran by subroutines and therefore called by call
.
But don't forget to declare in the handlers
module GTK functions and constants you are using:
use gtk, only: gtk_application_new, G_APPLICATION_FLAGS_NONE
use g, only: g_application_run, g_object_unref
Note that in gtk-fortran all the enums, like G_APPLICATION_FLAGS_NONE, were placed in the gtk
module, whatever library they come from (so don't trust the prefix).
Let's verify that the code compiles:
$ gfortran my_first_gtk_app.f90 $(pkg-config --cflags --libs gtk-3-fortran)
Note that depending on your system, you may also need to export the PKG_CONFIG_PATH
environment variable with the pkgconfig path (see installation instructions).
You can now launch your application:
$ ./a.out
(a.out:16004): GLib-GIO-WARNING **: 16:27:17.982: Your application does not implement g_application_activate() and has no handlers connected to the 'activate' signal. It should do one of these.
Or if you are under Windows (MSYS2): ./a.exe
Nothing appears and you get a warning. It is normal because we have not yet defined any widget.
Most of the time, your GTK program will be based on a Window widget. And that window must be created when our GTK application is activated. Just before launching our application with status = g_application_run(app, 0_c_int, c_null_ptr)
, we need to add this line in the main program:
call g_signal_connect(app, "activate"//c_null_char, c_funloc(activate), c_null_ptr)
We connect our app
to a function named activate
that will be called by the main loop when the "activate" signal is emitted. It is a callback function. Don't forget to append the c_null_char
to the signal name. The c_funloc()
function of the ISO_C_BINDING module returns a C pointer toward the Fortran callback function. The c_null_ptr
means no particular data will be passed to the callback function.
Now, let's write an activate
function in our handlers
module, just after the contains
statement:
subroutine activate(app, gdata) bind(c)
type(c_ptr), value, intent(in) :: app, gdata
type(c_ptr) :: window
window = gtk_application_window_new(app)
call gtk_widget_show_all(window)
end subroutine activate
This subroutine activate()
will be called by the GTK main loop when you launch your application. You will define there all the widgets of your GUI. The input arguments are two C pointers toward the GTK application and the data defined in the g_signal_connect()
function (i.e. c_null_ptr
in our case). Note firstly the bind(c)
statement (this Fortran subroutine will be called by a C function in GTK) and secondly that the pointers are passed by value.
Our first widget in our app is an empty window: we have defined a C pointer toward that object and we have created it using gtk_application_window_new(app)
. Don't forget to call the gtk_widget_show_all()
function if you want your window to appear on screen! Note that in GTK 3, all widgets are hidden by default and that is why we use _show_all()
. In GTK 4, on the contrary, the widgets inside a window are all shown by default and the gtk_widget_show_all()
function was removed: you will use instead call gtk_widget_show(window)
.
Don't forget to add those function names in the use gtk, only:
statement of the module. Note that g_signal_connect
must be added also to the use gtk
although it is part of GLib (g module): for some reason, it is an exception in gtk-fortran and was placed in the gtk
module.
If you run you application, felicitations, you will see your first GTK window! ![](CHANGELOG file
You can change its title using:
call gtk_window_set_title(window, "Hello world!"//c_null_char)
**You now know you have to add the function name in the use gtk, only:
statement of the module. I won't repeat anymore that advice in the following steps of this tutorial. If you forget, the compiler will tell you! **
At this stage, your code should be similar to that file: my_first_gtk_app3.f90
To place your widgets in the window, you need a widget that will manage the layout, for example a GtkBox where the widgets will be arranged into column or row (code to add into the activate
subroutine):
type(c_ptr) :: box, my_button
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10_c_int)
call gtk_container_add(window, box)
Here, we have chosen to arrange them vertically and separated by 10 pixels (note the _c_int
suffix to be sure the integer will be interoperable with the C int
).
Our box is then added to the window. In GTK 4, gtk_container_add
was removed and should be replaced by gtk_window_set_child
.
Now, we define our button and add it to the box. We connect it to the my_button_clicked
subroutine that we want to be called when it is clicked:
my_button = gtk_button_new_with_label("Compute"//c_null_char)
call gtk_container_add(box, my_button)
call g_signal_connect(my_button, "clicked"//c_null_char, c_funloc(my_button_clicked))
In GTK 4, gtk_container_add
should be replaced by gtk_box_append
.
Let's add that callback function into the handlers
module:
subroutine my_button_clicked(widget, gdata) bind(c)
type(c_ptr), value, intent(in) :: widget, gdata
print *, "Button clicked!"
end subroutine
But how can you know the interface of that callback function? You can find it by looking in the GTK documentation for the "clicked" signal of a button:
https://developer.gnome.org/gtk3/stable/GtkButton.html#GtkButton-clicked
When you see a star *
in a prototype, don't bother with the type of the object. You just need to know it's a pointer and declare it as type(c_ptr)
in your Fortran code.
At this stage your code should be:
And your app should look like this:
As you can see, the window size was automatically adapted to the only widget inside. We could of course override that behaviour by using: https://developer.gnome.org/gtk3/stable/GtkWindow.html#gtk-window-set-default-size
The objective of the next steps is to study the chaotic behaviour of the Logistic Map: https://en.wikipedia.org/wiki/Logistic_map We add in our program the following module, with a function that will compute a great number of terms of that sequence, starting from x0 between 0 and 1 and with r in the [0, 4] range:
module math
use iso_c_binding, only: dp=>c_double
implicit none
contains
pure real(dp) function iterate(x0, r) result(x)
real(dp), intent(in) :: x0, r
integer :: i
x = x0
do i = 0, 20000
x = r * x * (1_dp - x)
end do
end function iterate
end module math
Note that we use a real type interoperable with the C double
type.
x0 will be chosen randomly between 0 and 1. We will study the effect of the r parameter. We will therefore add into the activate
subroutine a widget to enter the r value:
label_r = gtk_label_new("r parameter"//c_null_char)
call gtk_container_add(box, label_r)
r_spin_button = gtk_spin_button_new(gtk_adjustment_new(3._dp, 0._dp, 4._dp, &
& 0.1_dp, 0._dp, 0._dp), 0.0_dp, 15_c_int)
call gtk_container_add(box, r_spin_button)
In fact, two widgets: a label to show the name of the parameter and a GtkSpinButton as an entry field. See: https://developer.gnome.org/gtk3/stable/GtkSpinButton.html
The last argument is the number of decimals to display. The first argument is a GtkAdjustment object: the default value is 3.0, the min is 0.0 and the max 4.0, the step is 0.1.
We update the my_button_clicked
callback function to choose randomly x0, get r from the the GtkSpinButton and print the results in the terminal:
subroutine my_button_clicked(widget, gdata) bind(c)
type(c_ptr), value, intent(in) :: widget, gdata
real(dp) :: r, x0
call random_number(x0)
r = gtk_spin_button_get_value(r_spin_button)
print *, r, x0, iterate(x0, r)
end subroutine
The r_spin_button
pointer must therefore be declared as a global variable in the handlers
module:
type(c_ptr) r_spin_button
If x0=0 (improbable) the result of the iterations will of course be zero. In the other cases, depending on the r value, if you press several times the Compute button, you will see that the iterations can be attracted toward one value, two values, four values, etc. And when r>3.569946..., it becomes chaotic (except in a few values ranges).
At this stage your code should be: my_first_gtk_app5.f90 And your app should look like this:
We have used a print *
to print the results into the terminal. We could of course also have used a label and its gtk_label_set_text()
function or more elaborate solutions like a GtkTextView. But our real objective is to draw the final result of the iterations over the r value.
Below our GtkSpinButton, we define a GtkDrawingArea:
my_drawing_area = gtk_drawing_area_new()
pixwidth = 1000
pixheight = 600
call gtk_widget_set_size_request(my_drawing_area, pixwidth, pixheight)
call g_signal_connect (my_drawing_area, "draw"//c_null_char, c_funloc(draw))
call gtk_container_add(box, my_drawing_area)
The variables my_drawing_area
and pixwidth
and pixheight
will be declared at the top of the handlers
module, respectively with type(c_ptr)
and integer(c_int)
. We will later write the callback function for the draw
signal.
Following the previous code, we use the GdkPibux library:
- https://developer.gnome.org/gdk-pixbuf/stable/gdk-pixbuf-Image-Data-in-Memory.html
- https://developer.gnome.org/gdk-pixbuf/stable/gdk-pixbuf-The-GdkPixbuf-Structure.html
my_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8_c_int, &
& pixwidth, pixheight)
nch = gdk_pixbuf_get_n_channels(my_pixbuf)
rowstride = gdk_pixbuf_get_rowstride(my_pixbuf)
call c_f_pointer(gdk_pixbuf_get_pixels(my_pixbuf), pixel, &
& (/pixwidth*pixheight*nch/))
pixel = char(0)
A pixbuf is a representation of an image in the memory. The FALSE
value means there will be no transparency (no alpha channel). And 8
is the number of bits per colour.
The gdk_pixbuf_get_n_channels()
function should return 3 (Red, Green, Blue). The gdk_pixbuf_get_rowstride
will generally return pixwidth*nch, but it can be greater: it is the "number of bytes between the start of a row and the start of the next row".
my_pixbuf
, nch
and rowstride
will be declared at the top of the module. And also pixel
:
character(kind=c_char), dimension(:), pointer :: pixel
It is a pointer toward the C array containing the pixel data. That pointer is obtained by gdk_pixbuf_get_pixels
and transformed into a Fortran pointer with c_f_pointer
.
That array is initialized with zeros and the image will therefore be black (red=0, green=0, blue=0 for each pixel). It is a one dimensional array: the three first bytes are the intensities of the three colours of the first pixel (0,0) at the top left, the three following bytes are for the pixel (1,0), etc.
Each time the GtkDrawingArea will need to be redrawn (for example if another window was on top), the draw
signal will be emitted and the callback function called:
function draw(widget, my_cairo_context, gdata) result(ret) bind(c)
type(c_ptr), value, intent(in) :: widget, my_cairo_context, gdata
integer(c_int) :: ret
call gdk_cairo_set_source_pixbuf(my_cairo_context, my_pixbuf, 0d0, 0d0)
call cairo_paint(my_cairo_context)
ret = FALSE
end function draw
The drawing will be made via the Cairo library. Our pixbuf is placed at the top left (0,0) of what is called a Cairo context, which us then painted by cairo_paint
.
At this stage your code should be: my_first_gtk_app6.f90 And your app should look like this:
In GTK 4, the draw
signal has been removed. The GTK 4 version of those parts of the code is given in the Appendix at the bottom of this page.
Perhaps you have the feeling it was quite fastidious, but you now have a template to use every time you want to draw a bitmap image!
The algorithm to draw the bifurcation diagram of the logistic map, also called the Feigenbaum fractal, being short, we will put it directly into the my_button_clicked
callback function:
subroutine my_button_clicked(widget, gdata) bind(c)
type(c_ptr), value, intent(in) :: widget, gdata
real(dp) :: r, x0
real(dp) :: rmin, rmax
integer :: p, n, xp, yp, xpmax, ypmax
call random_seed()
rmin = gtk_spin_button_get_value(r_spin_button)
rmax = 4_dp
xpmax = pixwidth-1
ypmax = pixheight-1
pixel = char(0)
do xp = 0, xpmax
r = rmin + xp * (rmax - rmin) / xpmax
do n = 1, 100
call random_number(x0)
yp = ypmax - nint(iterate(x0, r) * ypmax)
p = 1 + xp*nch + yp*rowstride
pixel(p) = char(255)
pixel(p+1) = char(150)
pixel(p+2) = char(120)
end do
end do
call gtk_widget_queue_draw(my_drawing_area)
end subroutine my_button_clicked
The GtkSpinButton will now correspond to the minimal r value, the maximum begin 4. For each r value, we compute the iterations starting from 100 x0 random values.
The position of the (xp,yp) pixel in the one dimensional array is given by p = 1 + xp*nch + yp*rowstride
(in Fortran arrays indexing start at 1). The red, green, blue values are 255, 150, 120. Note also that the graphical coordinate system has the origin (0,0) at the top left and the ordinate axis is oriented downward.
The gtk_widget_queue_draw()
function tells the main loop that the area needs redrawing.
At this stage your code should be: my_first_gtk_app7.f90 And your app should look like this: Note that the computation takes several seconds and that the image appears only when it is finished.
Felicitations, you have learned a lot: you can know create your own GtkApplication in Fortran. You know how to add widgets, you have understood what is a callback function, and you can even draw a bitmap image in Fortran!
Of course, this is just a first step. We could improve some details:
- the GtkSpinButton and Button widths are identical to the width of the image. For the layout we have used the simple GtkBox container. But we could have used a more complex one like GtkGrid.
- Another problem is that the image content appears only at the end of the computation, when we return to the main loop of the GUI. One solution could be to manage directly that main loop (see the
mandelbrot_pixbuf.f90
example) to update the image during the computation. - We have not saved our image. See the
pixbuf_without_gui.f90
example: it is easy to save a pixbuf as a PNG file.
- replace
gtk_widget_show_all
bygtk_widget_show
- Replace
gtk_container_add
bygtk_window_set_child
andgtk_box_append
- Replace:
call g_signal_connect (my_drawing_area, "draw"//c_null_char, c_funloc(draw))
by
call gtk_drawing_area_set_draw_func(my_drawing_area, &
& c_funloc(my_draw_function), c_null_ptr, c_null_funptr)
- Replace:
function draw(widget, my_cairo_context, gdata) result(ret) bind(c)
type(c_ptr), value, intent(in) :: widget, my_cairo_context, gdata
integer(c_int) :: ret
call gdk_cairo_set_source_pixbuf(my_cairo_context, my_pixbuf, 0d0, 0d0)
call cairo_paint(my_cairo_context)
ret = FALSE
end function draw
by:
subroutine draw(widget, my_cairo_context, width, height, gdata) bind(c)
type(c_ptr), value, intent(in) :: widget, my_cairo_context, gdata
integer(c_int), value, intent(in) :: width, height
call gdk_cairo_set_source_pixbuf(my_cairo_context, my_pixbuf, 0d0, 0d0)
call cairo_paint(my_cairo_context)
end subroutine draw
- Installation
- My first gtk-fortran application
- Drawing an image in a PNG file (without GUI)
- A program also usable without GUI
- Using Glade3 and gtkf-sketcher (GTK 3)
- Using gtk-fortran as a fpm dependency
- Debugging with GtkInspector
- Learning from examples
- Video tutorials
- How to start my own project from a gtk-fortran example
- git basics
- CMake basics
- Alternatives to CMake
- How to migrate to GTK 4
- How to contribute to gtk-fortran
- How to hack the cfwrapper