Interfacing Raku to Gnome GTK+

Getting Started

A Simple Window

To get an idea of how the modules from the Gnome::Gtk3 package work, a simple example is shown where a window is opened. When the window manager button wm button is clicked, the application will stop and the GUI will disappear.

example window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use v6;

use Gnome::Gtk3::Main;
use Gnome::Gtk3::Window;

# Instantiate main module for UI control
my Gnome::Gtk3::Main $m .= new;

# A callback handler class to respond to the 'close application event'
class AppSignalHandlers {
  method exit-program ( ) { $m.gtk-main-quit; }
}

# Create a top level window and set a title
my Gnome::Gtk3::Window $top-window .= new
$top-window.set-title('Example');

# Initialize the callback handler class and register the method for the event
my AppSignalHandlers $ash .= new;
$top-window.register-signal( $ash, 'exit-program', 'destroy');

# Show everything and activate
$top-window.gtk-widget-show-all;

$m.gtk-main;

Lets explain some of the code displayed above. To start with, load the modules used in this program. These are Gnome::Gtk3::Main and Gnome::Gtk3::Window [on line 3 and 4]. They will load class definitions to control the main loop and to handle a plain window.

Next, we initialize a Gnome::Gtk3::Main object $m which will be used later on [7].

Then we will setup a class to handle signals. These signals are registered after all widgets are setup and laid out [10-12]. There is only one method defined in that class to stop the program. Our first GTK method is used here, gtk-main-quit() from the Gnome::Gtk3::Main class [11].

Now we can start creating the widgets. There is only one widget Gnome::Gtk3::Window and initialized as a window with its title set to ‘Example’ [15].

Initialize the handler class and register signals. We use the destroy signal to call the exit-program() method in the handler object $ash [18,19].

Then show the window and everything in it [22] and start the main loop [24].

To run the program, save the code in a file called simple-example.pl6 and type the following on the command line (in windows you can double click on the filename).

raku simple-example.pl6 <Enter>

What we have learned

So, what we have learned here are several specific things which will always come back in all user interface based programs.

  • Initialization of the libraries
  • Defining a handler class where all handler classes can reside. However, you are not restricted to one class and you do not have to have the class defined in your main program. You can split the handling in several ‘categories’ e.g. file dialog handling or tree view handling.
  • Creating a window and have it a title.
  • Add more widgets to the window (not shown here, it is a simple example you know).
  • Registration of your callback handlers to handle specific signals.
  • Starting and stopping of the main loop.

Simple Window with a Button

The next step is to have some action using a button. The button is placed in the window and clicking on the button will stop the program.

example window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use v6;

use Gnome::Gtk3::Main;
use Gnome::Gtk3::Window;
use Gnome::Gtk3::Button;

# Instantiate main module for UI control
my Gnome::Gtk3::Main $m .= new;

class AppSignalHandlers {

  method button-exit ( ) { $m.gtk-main-quit; }
  method exit-program ( ) { $m.gtk-main-quit; }
}

# Create a top level window and set a title
my Gnome::Gtk3::Window $top-window .= new;
$top-window.gtk-window-set-title('With Button');

# Create button
my Gnome::Gtk3::Button $button .= new(
  :label('Exit Our Very Lovely Program')
);
$top-window.gtk-container-add($button);

# Instantiate the event handler class and register signals
my AppSignalHandlers $ash .= new;
$button.register-signal( $ash, 'button-exit', 'clicked');
$top-window.register-signal( $ash, 'exit-program', 'destroy');

# Show everything and activate all
$top-window.gtk-widget-show-all;

$m.gtk-main;

You see that the layout of the program is more or less the same as in our previous example. We need to load an extra module Gnome::Gtk3::Button [5]. We also have added another method to the handler class to handle the button click [12].

We have used a method .set-title() to set the title of the window [18].

What is new is the creation of a button [21-23] and how it is added to the window [24]. Later on the signal handler .button-exit() is registered to handle the clicked event [28].

Now save the code in window-button.pl6 and run it. Pressing the button will stop the program.

Simple Window with two Buttons

Lets go on quickly and add another button with the .gtk-container-add() method. The relevant part is shown below.

...
my Gnome::Gtk3::Window $top-window .= new(:empty);
$top-window.set-title('With 2 Buttons');

# Create buttons
my Gnome::Gtk3::Button $button1 .= new(:label('Exit Our Very Lovely Program'));
$top-window.gtk-container-add($button1);
my Gnome::Gtk3::Button $button2 .= new(:label('Exit too with button two'));
$top-window.gtk-container-add($button2);

# Instantiate the event handler class and register signals
my AppSignalHandlers $ash .= new;
$button1.register-signal( $ash, 'button-exit', 'clicked');
$button2.register-signal( $ash, 'button-exit', 'clicked');
$top-window.register-signal( $ash, 'exit-program', 'destroy');
...

When run, we see that there is a problem. There is still one button and an error is shown on the commandline. This error comes directly from the GTK libs and will tell you sometimes what goes wrong. The error is;

(window-two-buttons.pl6:25360): Gtk-WARNING **: 20:31:04.187: Attempting
to add a widget with type GtkButton to a GtkWindow, but as a GtkBin
subclass a GtkWindow can only contain one widget at a time; it already
contains a widget of type GtkButton

A few things to note here;

  • The line 25360 can not be found in our code. It is perhaps a line number in the library or a memory address.
  • The objects in GTK+ are named a bit differently but it is easy to make the proper connection, e.g. GtkWindow is the native representation of a Gnome::Gtk3::Window class. This native object is also stored within that class.
  • We notice a new object, namely a GtkBin which is a parent class of GtkWindow.

The error tells us that the Window class is a subclass of Bin which is a container able to contain only one object. This is the reason that we only saw one button in the running program.

Ok. Need to do things differently then. Lets look at our next attempt which is also a bit more interesting too. The next example is taken from the GTK::Simple package so you can compare the two implementations.

example window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
use v6;

use Gnome::Gtk3::Main;
use Gnome::Gtk3::Window;
use Gnome::Gtk3::Grid;
use Gnome::Gtk3::Button;

# Instantiate main module for UI control
my Gnome::Gtk3::Main $m .= new;

# Class to handle signals
class AppSignalHandlers {

  # Handle 'Hello World' button click
  method first-button-click (
    :widget($b1), :other-button($b2)
  ) {
    $b1.gtk-widget-set-sensitive(False);
    $b2.gtk-widget-set-sensitive(True);
  }

  # Handle 'Goodbye' button click
  method second-button-click ( ) {
    $m.gtk-main-quit;
  }

  # Handle window managers 'close app' button
  method exit-program ( ) {
    $m.gtk-main-quit;
  }
}

# Create a top level window and set a title
my Gnome::Gtk3::Window $top-window .= new;
$top-window.gtk-window-set-title('Hello GTK!');
$top-window.gtk-container-set-border-width(20);

# Create a grid and add it to the window
my Gnome::Gtk3::Grid $grid .= new;
$top-window.gtk-container-add($grid);

# Create buttons and disable the second one
my Gnome::Gtk3::Button $button .= new(:label('Hello World'));
my Gnome::Gtk3::Button $second .= new(:label('Goodbye'));
$second.gtk-widget-set-sensitive(False);

# Add buttons to the grid
$grid.gtk-grid-attach( $button, 0, 0, 1, 1);
$grid.gtk-grid-attach( $second, 0, 1, 1, 1);

# Instantiate the event handler class and register signals
my AppSignalHandlers $ash .= new;
$button.register-signal(
  $ash, 'first-button-click', 'clicked',
  :other-button($second)
);
$second.register-signal(
  $ash, 'second-button-click', 'clicked'
);

$top-window.register-signal( $ash, 'exit-program', 'destroy');

# Show everything and activate all
$top-window.gtk-widget-show-all;

$m.gtk-main;

We will use a Grid to add the buttons to. Maybe you have heard of GtkVBox and GtkHBox. These are container classes where you can layout other widgets vertically or horizontally. They still exist in Gtk version 3 but are deprecated and therefore not supported by the Raku packages. However, the Grid class can do it all for you and easier.

The method .set-border-width() on the Window is used to get some space around our buttons [36]. Then an empty grid is created and added to the window [39,40]. The buttons are created [43,44] but the second button is disabled with .set-sensitive() [45]. This will be visible as a grayed-out button.

Add the buttons to the Grid with .gtk-grid-attach() [48,49]. The integers 0, 0, 1, 1 in the first call mean; first column, first row and take up 1 grid location in width and height. The second button is placed below the first one taking up the same space. The grid is molded around the buttons taking the minimum space needed.

The first button handler is .first-button-click(). We see that there is a named argument added (:other-button) to the registration call [53-56]. You are totally free to add any named argument except for :widget which is reserved. The arguments are provided to the handler as is. The :widget will hold the object on which the handler is registered.

The first handler .first-button-click() is called when the top button is clicked [15-20]. It uses the object $b1 provided by :widget to make that object insensitive [19]. This is the top button and will become grayed. The other button $b2 given by :other-button is made sensitive [20]. You will see that it ‘returns from its grayness’ so to speak.

The second handler .second-button-click() [23-25] is called when the bottom button is clicked and will call .gtk-main-quit() to stop the program.

What we have learned

  • A window can hold only one object while a grid can hold more objects. The grid can be added to the window and other widgets can be added to the grid.
  • We learned a few more methods to manipulate widgets e.g. .set-border-width() and .set-sensitive().
  • When registering signal handlers, we can add more information using named arguments but that the :widget argument is reserved.