Thursday, March 20, 2014

Getting Started with GtkAda

There is not much resources on how to program with GtkAda. GtkAda User’s Guide provides some information but not enough to start from scratch. I tried to follow the Getting Started with GTK+ but writing it with GtkAda.

I will assume you have gnat gpl 2013 and GtkAda installed. I use the svn version of GtkAda (3.8.3) in stead of the GtkAda 2013 GPL 3.4.2.

Basics

We'll start with the simplest program possible. This program will create an empty window.
The source files are under the following directory structure:
example-top-dir
  |-- obj
  |-- src
  `-- hello.gpr
src/hello.adb:
with Gtk.Main;
with Gtk.Window;      use Gtk.Window;

with Main_quit;

procedure Hello is
   Win   : Gtk_Window;
begin
   --  Initialize GtkAda.
   Gtk.Main.Init;

   -- create a top level window
   Gtk_New (Win);
   Win.Set_Title ("Window");

   -- connect the "destroy" signal
   Win.On_Destroy (main_quit'Access);

   --  Show the window
   Win.Show_All;

   --  Start the Gtk+ main loop
   Gtk.Main.Main;
end Hello
src/main_quit.adb:
with Gtk.Widget;      use Gtk.Widget;
with Gtk.Main;

procedure main_quit (Self : access Gtk_Widget_Record'Class) is
begin
   Gtk.Main.Main_Quit;
end main_quit;
The project file hello.gpr:
with "gtkada";

project Hello is

   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Main use ("hello.adb");

   --  Enable Ada 2005.
   package Compiler is
      for Default_Switches ("ada") use ("-gnat05");
   end Compiler;

end Hello;
You then could compile the program as
gprbuild -P hello

All GtkAda application will, of course, with the Gtk.Main and Gtk.Window packages. We'll need to add a call back to quit the program, so with the Main_quit procedure.

In the main procedure Hello, we declared a Win variable as of type Gtk_Window. Since we have use Gtk.Window, we does not need to give the full path as Gtk.Window.Gtk_Window.

The following line will call Gtk.Main.Init, which is the initialization function for GtkAda; it calls the underline gtk_init() function and will setup GtkAda, the type system, the the connection to the windowing environment, etc. The Gtk.Main.Init take care of passing command line arguments counter and string array to the C gtk_init() function; this allows GTK+ to parse specific command line arguments that control the behavior of GTK+ itself. The parsed arguments will be removed from the array, leaving the unrecognized ones for your application to parse.

For more information on which command line arguments GtkAda recognizes, please refer to the Running GTK+ Applications section.

A call to Gtk_New (Win) will create a new Gtk_Window and store it inside the Win variable. Gtk_New has Gtk.Enums.WindowToplevel as its default The_Type argument, so we don't need to specify it with the call. Which means the the Gtk_Window will be managed by the windowing system: it will have a frame, a title bar and window controls, depending on the platform.

In order to terminate the application when the Win is destroyed we connect the "destroy" signal to the Main_quit function by calling Win.On_Destroy (main_quit'Access);

This function will terminate the GtkAda main loop started by calling Gtk.Main.Main later. The "destroy" signal is emitted when a widget is destroyed, either by explicitly calling Gtk.Widget.Destroy or when the widget is unparented. Top-level Gtk_Windows are also destroyed when the Close window control button is clicked.

Gtk_Window are hidden by default. By calling Win.Show_All, we are asking GtkAda to set the visibility attribute so that it can be displayed. Al this work is done after the main loop has been started.

The last line of interest is the call to Gtk.Main.Main. This function will start the GtkAda main loop and will block the control flow of Hello until Gtk.Main.Main_Quit is called.

While the program is running, GtkAda is receiving events. These are typically input events caused by the user interacting with your program, but also things like messages from the window manager or other applications. GtkAda processes these and as a result, signals may by emitted on your widgets. Connecting handlers for these signals is how you normally make your program do somthing in response to user input.

In the main_quit procedure, Gtk.Main.Main_Quit is called to quit the program.

The following example is slightly more complex, and tries to showcase some of the capabilities of GtkAda.

In the long tradition of programming languages and libraries, it is called Hello, World.

Using the same project file of the first example, here are the sources:

src/hello.adb:
with Gtk.Main;
with Gtk.Window;      use Gtk.Window;
with Gtk.Button;      use Gtk.Button;
with Gtkada.Handlers; use Gtkada.Handlers;

with hello_cb; use hello_cb;

procedure Hello is
   Win    : Gtk_Window;
   Button : Gtk_Button;
begin
   --  Initialize GtkAda.
   Gtk.Main.Init;

   -- create a top level window
   Gtk_New (Win);
   Win.Set_Title ("Window");

   -- When the window emits the "delete-event" signal (which is emitted
   -- by GTK+ in response to an event coming from the window manager,
   -- usually as a result of clicking the "close" window control), we
   -- ask it to call the on_delete_event() function as defined above.
   Win.On_Delete_Event (main_del'Access);

   -- connect the "destroy" signal
   Win.On_Destroy (main_quit'Access);

   -- set the border width of the window
   Win.Set_Border_Width (10);

   -- create a button with label
   Gtk_New (Button, "Hello World");

   -- connect the click signal
   Button.On_Clicked (button_clicked'Access);

   -- connect the "clicked" signal of the button to destroy function,
   -- it will destroy the Win when button is clicked.
   Widget_Callback.Object_Connect
     (Button,
      "clicked",
      Widget_Callback.To_Marshaller (button_quit'Access),
      Win);

   -- This packs the button into the window. A Gtk_Window inherits from Gtk_Bin
   -- which is a special container that can only have one child.
   Win.Add (Button);

   --  Show all the window
   Win.Show_All;

   -- All GTK applications must have a Gtk.Main.Main. Control ends here
   -- and waits for an event to occur (like a key press or a mouse event),
   -- until Gtk.Main.Main_Quit is called.
   Gtk.Main.Main;
end Hello;
src/hello_cb.ads:
with Gtk.Widget;  use Gtk.Widget;
with Gtk.Button;  use Gtk.Button;
with Glib.Object;

with Gdk.Event;

package hello_cb is
   function main_del
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event)
      return  Boolean;
   procedure main_quit (Self : access Gtk_Widget_Record'Class);
   procedure button_clicked (Self : access Gtk_Button_Record'Class);
   procedure button_quit (Self : access Gtk_Widget_Record'Class);
end hello_cb;
src/hello_cb.adb:
with Ada.Text_IO; use Ada.Text_IO;
with Gtk.Main;

package body hello_cb 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.
   function main_del
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event)
      return  Boolean
   is
   begin
      Put_Line ("Delete event encounter.");
      return True;
   end main_del;

   procedure main_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Gtk.Main.Main_Quit;
   end main_quit;

   procedure button_clicked (Self : access Gtk_Button_Record'Class) is
   begin
      Put_Line ("Hello clicked");
   end button_clicked;

   procedure button_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Put_Line ("buttion_quit is called");
      Destroy (Self);
   end button_quit;

end hello_cb;
You compile the program with the same command:
gprbuild -P hello

This time, we defined a hello_cb package to host all the call back functions.

In GtkAda, there is no need for g_signal_connect_swapped(), we use the Widget_Callback.Object_Connect method.

P.S. All source code from these series of Getting Started with GtkAda are now at GitHub.

2 comments:

Unknown said...

Many thanks.
Although we are in 2020, and I am using raspbian (pyOS) with the gnat (08 it says) and gtkada coming from the distribution itself, and that on this tiny raspberrypi 4, your two examples ran directly, without me struggling. Which is kind of unexpected (considering I am a beginner in Ada, somewhat in raspberry, but not at all in Linux or CS).
Thanks again for your help.

Unknown said...

Many thanks.
Although we are in 2020, and I am using raspbian (pyOS) with the gnat (08 it says) and gtkada coming from the distribution itself, and that on this tiny raspberrypi 4, your two examples ran directly, without me struggling. Which is kind of unexpected (considering I am a beginner in Ada, somewhat in raspberry, but not at all in Linux or CS).
Thanks again for your help.