Ada Programming/Libraries/GUI/GtkAda


GtkAda is the Ada binding for the popular open-source GTK+ libraries. Can be used on multiple platforms.

Ada. Time-tested, safe and secure.
Ada. Time-tested, safe and secure.

GtkAda Code Example

edit
with Gtk.Main, Gtk.Window;

procedure Simple_Application is
   Window : Gtk.Window.Gtk_Window;
begin
   Gtk.Main.Init;
   Gtk.Window.Gtk_New (Window);
   Gtk.Window.Show (Window);
   Gtk.Main.Main;
end Simple_Application;

Opening a File Chooser Dialog

edit

A file chooser dialog is a convenient tool that gives the user convenient access to the filesystem when opening or saving a file. Gtk provides a handy FileChooserDialog type that is fairly easy to setup and use.

As a C library, Gtk's library functions often have a variable number of parameters. Creation of a file chooser dialog typically involves passing the buttons you would want in the dialog's "action bar" as parameters to gtk_file_chooser_dialog_new().

As an Ada library, GtkAda's subprograms never have a variable number of parameters. The following excerpt shows how to create a working file chooser dialog that will save a file.

      -- the package must have already with'd Gtk.Dialog, Gtk.File_Chooser,
      -- and Gtk.File_Chooser_Dialog

      declare

         -- let's make our lives a little easier
         package Dialog renames Gtk.Dialog;
         use all type Dialog.Gtk_Response_Type;
         package File_Chooser renames Gtk.File_Chooser;
         package FCD renames Gtk.File_Chooser_Dialog;

         -- the dialog
         Filename_Dialog: FCD.Gtk_File_Chooser_Dialog;
         -- needed only to discard return value
         Discard: Gtk.Widget.Gtk_Widget;

      begin

         -- create chooser dialog
         FCD.Gtk_New
            (
             Dialog    => Filename_Dialog,
             Title     => "Save file: choose path",
             Parent    => null,
             Action    => File_Chooser.Action_Save
            );

         -- add save, cancel buttons
         -- 
         -- notice the different response id associated with each button;
         -- this makes it easy to handle user responses to the dialog,
         -- while the underscore that precedes the text allows for keyboard shortcuts
         -- (alt + character-that-follows-underscore)
         -- 
         -- Add_Button is an Ada function, so you have to accept the returned value,
         -- which is a Gtk_Widget. This is useful if you want to modify the result.
         Discard := Dialog.Add_Button
            (
             Dialog      => Dialog.Gtk_Dialog(Filename_Dialog),
             Text        => "_Cancel",
             Response_Id => Dialog.Gtk_Response_Cancel
            );
         Discard := Dialog.Add_Button
            (
             Dialog      => Dialog.Gtk_Dialog(Filename_Dialog),
             Text        => "_Save",
             Response_Id => Dialog.Gtk_Response_Accept
            );

         -- make sure user is OK with overwriting old file
         FCD.Set_Do_Overwrite_Confirmation(Filename_Dialog, True);

         -- set default filename;
         -- Gtk is smart enough to highlight everything before the extension (.txt)
         FCD.Set_Current_Name(Filename_Dialog, "new_file.txt");

         -- Run the dialog and react accordingly
         if FCD.Run(Filename_Dialog) = Dialog.Gtk_Response_Accept then
            declare Filename: UTF8_String := FCD.Get_Filename(Filename_Dialog);
            begin
               -- you have to define the Write_Data function yourself,
               -- since only you know how you want to write the data to disk
               Write_Data(Filename);
            end;
         -- in theory we could react to Gtk_Response_Cancel as well,
         -- but there doesn't seem to be a need here
         end if;

         -- destroy the dialog or it will stay visible and annoy you
         Gtk.Widget.Destroy(Gtk.Widget.Gtk_Widget(Filename_Dialog));

      end;

An interesting implementation detail is that most of the types you want to work with are access types for tagged types. Indeed, the types Gtk_Dialog and Gtk_File_Chooser_Dialog above are tagged types. The corresponding records will have nearly identical names: Gtk_Dialog_Record and Gtk_File_Chooser_Dialog_Record. Some other types are implemented as an "interface" called GType_Interface, and an example of this would be Gtk_File_Chooser; it has no corresponding type named Gtk_File_Chooser_Record.

We bring this up primarily to explain why the first parameters in the calls to Add_Button have the form Dialog => Dialog.Gtk_Dialog(Filename_Dialog); the formal parameter has type Gtk_Dialog, but the actual parameter has type Gtk_File_Chooser_Dialog. The latter conforms to the former, but Ada requires us to make this explicit.

Callbacks

edit

A key concept of GtkAda is that of the "callback": the programmer can arrange that, when an event occurs relative to some widget, the widget "calls back" to some function. We illustrate how one add a button to a window's "button bar" and set up callback functions that activate whenever the user clicks and releases the button, or whenever the user presses a shortcut key, also called a "mnemonic".

Callback signatures

edit

You typically need to define callbacks in a separate package, and each event's callback function must conform to a certain signature. The signatures that interest us are found in gtk-widget.ads.

Most events allow you to assign a callback in two different ways. We will consider the slightly more complicated option; it offers a slot parameter of type GObject. When you set up the callback, you can pass either the widget itself, another widget, or a custom GObject of your own making that contains information the callback needs to process the event. This latter type is probably the typical scenario, as most interface elements need to interact with other widgets and program data, and a slot is an easy way to make that work.

  • To handle the On_Button_Release_Event with a slot, a callback must have the following signature:
  type Cb_GObject_Gdk_Event_Button_Boolean is not null access function
    (Self  : access Glib.Object.GObject_Record'Class;
     Event : Gdk.Event.Gdk_Event_Button) return Boolean;
  • To handle the On_Mnemonic_Activate with a slot, a callback must have the following signature:
   type Cb_GObject_Boolean_Boolean is not null access function
     (Self : access Glib.Object.GObject_Record'Class;
      Arg1 : Boolean) return Boolean;

In each case, Self refers to the slot parameter given when we assign the callbacks.

Defining the callbacks

edit

In this case, we want the button to do the same thing, regardless of whether we activate it via button click or mnemonic. Since the callbacks' signatures differ, we can't use the same function to handle both. However, in this simple example we can just call one from the other. In a callbacks package body we define the following functions (with corresponding package specification):

function Handle_Button_Click
   (Self : access Glib.Object.GObject_Record'Class;
    Event: Gdk.Event.Gdk_Event_Button
   ) return Boolean
is
begin
   return Handle_Mnemonic(Self, False);
end Handle_Button_Click;

function Handle_Mnemonic
   (Self : access Glib.Object.GObject_Record'Class;
    Arg  : Boolean
   ) return Boolean
is
begin
   -- perform the needed activity with Self
   -- ...

   return False;
end Handle_Mnemonic;
  • You may be wondering what the point of Event and Arg are. In this particular application I may have no use for them, and can ignore them, but in some cases you might want to know if the user was holding the Control key when pressing the mouse button (included in the Event parameter).

(Note: As far as the author can tell, Gtk always passes False to Arg when the user invokes a button by the mnemonic. The GtkAda documentation provides no information on what Arg is supposed to communicate.)

  • You will notice that the callback functions return a Boolean value; its purpose is to indicate whether this callback has "completely handled" the event; if true, other handlers assigned to this widget and this event will not learn the event was handled. This is often the behavior you want, but in this example, returning False from the mnemonic allows Gtk to give visual feedback as if the button had been clicked-and-released. This will not happen if the callback returns True.

Assigning the callbacks

edit

Here we illustrate the creation of a button with a mnemonic in its label and the assignment of callbacks for both a mnemonic keypress and a press-and-release. This is not a full example; you will need to with and use the packages that contain the relevant types.

   -- declarations
   My_Button   : Gtk_Button;
   Button_Bar  : Gtk_Table;  -- horizontal box containing buttons
   Button_Data : GObject;    -- assign another widget, or a custom GObject
                             -- with information relevant for the desired behavior
   -- ...

begin

   -- ...
   -- create the button bar and the button and attach the button
   Button_Bar := Gtk_Table_New(1, 5, True);
   My_Button := Gtk_Button_New_With_Mnemonic("_Execute!");
   Button_Bar.Attach(Add_Button, 1, 2, 0, 1, Shrink, Shrink, 0, 0);

   My_Button.On_Button_Release_Event
      (Call  => Handle_Button_Click'Access,
       Slot  => Button_Data,
       After => False);
   My_Button.On_Mnemonic_Activate
      (Call  => Handle_Mnemonic'Access,
       Slot  => Button_Data,
       After => False);
   -- ...

   -- attach Button_Bar to the Window,
   -- or to some other UI element attached to the Window

   -- start the Gtk main loop
   Gtk.Main.Main;

end;

Reading

edit

TBC

Building GUI with Glade3

edit

As of 2012, there is the support of the interactive GTK builder Glade3. We describe a basic example of the different steps needed to build a "hello world" with Glade3.

Prerequisite

edit

You must have GTKAda installed and the environment variable GPR_PROJECT_PATH set to find the project file gtkada.gpr For Windows, GPR_PROJECT_PATH should look like:

C:\GNAT\2012\lib\gnat;C:\GtkAda\lib\gnat (if you used the default locations)

Creating the XML description file

edit

Glade3 generates interactively XML files that describes the UI. Our basic UI will contain a Main_Window with a label. We must take care of the termination by declaring a signal "destroy" in Main_Window to quit the app, otherwise the program will not end when we close the window.

 

glade3 output : simple.glade

edit
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="2.18"/>
<!-- interface-naming-policy project-wide -->
  <object class="GtkWindow" id="main_window">
    <property name="width_request">400</property>
    <property name="height_request">100</property>
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Glade 3 simple demo with Ada</property>
    <property name="resizable">False</property>
    <property name="window_position">center</property>
    <signal name="destroy" handler="Main_Quit" swapped="no"/>
    <child>
      <object class="GtkLabel" id="some_label">
        <property name="width_request">200</property>
        <property name="height_request">50</property>
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes">Hello from Glade 3 ;-)</property>
      </object>
    </child>
  </object>
</interface>

Ada code

edit

First, we declare the handler/callback that will be attached to the "destroy" signal. The callbacks must be in a package, otherwise you run into accessibility errors.

The Gobject event "destroy" is handled by "Main_Quit" in XML, and "Main_Quit" is implemented by the Ada procedure Simple_Callbacks.Quit.

The logical connection is :

signal : GTKObject.destroy => Handler name : "Main_Quit" => Ada callback : Simple_Callbacks.Quit

Callback spec : simple_callbacks.ads

edit
with Gtkada.Builder; use Gtkada.Builder;

package Simple_Callbacks is

   procedure Quit (Object : access Gtkada_Builder_Record'Class);

end Simple_Callbacks;

Callback body : simple_callbacks.adb

edit
with Gtk.Main;

package body Simple_Callbacks is

   procedure Quit (Object : access Gtkada_Builder_Record'Class) is
      pragma Unreferenced (Object);
   begin
      Gtk.Main.Main_Quit;
   end Quit;

end Simple_Callbacks;

Main program : simple_glade3.adb

edit

Read the XML description, register the termination handler, connect, start the Gtk.Main.Main loop, and that's it.

with Gtk;            use Gtk;
with Gtk.Main;       use Gtk.Main;
with Glib.Error;     use Glib.Error;
with Gtk.Widget;     use Gtk.Widget;
with Ada.Text_IO;
with Gtkada.Builder; use Gtkada.Builder;

-- the following package is user defined.
with Simple_Callbacks; use Simple_Callbacks;

procedure Simple_Glade3 is

   Builder : Gtkada_Builder;
   Error   : Glib.Error.GError;

begin
   Gtk.Main.Init;
   --     Step 1: create a Builder and add the XML data,
   Gtk_New (Builder);
   Error := Add_From_File (Builder, "simple.glade");
   if Error /= null then
      Ada.Text_IO.Put_Line ("Error : " & Get_Message (Error));
      Error_Free (Error);
      return;
   end if;
   --     Step 2: add calls to "Register_Handler" to associate your
   --     handlers with your callbacks.
   Register_Handler
     (Builder      => Builder,
      Handler_Name => "Main_Quit", -- from XML file <signal handler=..>
      Handler      => Simple_Callbacks.Quit'Access);

   -- Step 3: call Do_Connect. Once to connect all registered handlers
   Do_Connect (Builder);

   --  Find our main window, then display it and all of its children.
   Gtk.Widget.Show_All (Get_Widget (Builder, "main_window"));
   Gtk.Main.Main;

   -- Step 4: when the application terminates or all Windows described through
   --         your builder should be closed, call Unref to free memory
   --         associated with the Builder.
   Ada.Text_IO.Put_Line ("The demo is over");
   Unref (Builder);

end Simple_Glade3;

Project file : simple.gpr

edit
with "gtkada";

project Simple is

   type Gtkada_Kind_Type is
      ("static", "relocatable");
   Library_Type : Gtkada_Kind_Type := external ("LIBRARY_TYPE", "static");
   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Exec_Dir use ".";

   for Main use ("simple_glade3.adb");

   package Builder is
      for Default_Switches ("ada") use ("-s");
   end Builder;

   package Compiler is
            for Default_Switches ("ada") use ("-O2", "-gnat05");
   end Compiler;

   package Linker is
      -- for Windows production only ;; remove for Linux / Mac / Win debug
      for Default_Switches ("ada") use ("-mwindows");
   end Linker;

end Simple;

The final result

edit

 

More

edit

For RAD, gate3 is an Ada code sketcher : you build the User Interface with glade3.8, and gate3 generates an Ada prototype that is a valid Ada code.

Complete demo programs available from Sourceforge

  • Lorenz chaotic attractor: Drawing demo with GTK timer loop.
  • Julia set: GTK interface mixed with Ada tasking.

Library

edit
Project Info
https://github.com/adacore/gtkada/
Download
https://github.com/adacore/gtkada/releases


See also

edit

Wikibook

edit