Unary adding operator and Access type

Hello!

While browsing code using GtkAda library I came across the use of unary addition operator in addition to Access-type.

Window.On_Delete_Event (+Delete_Event'Access);

I managed to find out (with the help of ChatGPT…) that this is a “trick” to treat the pointer given in this way as global (important for lifetime).
Where can I read more about this? Maybe I searched in the wrong place, but I couldn’t find a section on this in ARM.

You probably mean this

with Gtk.Label;    use Gtk.Label;
with Gtk.Window;   use Gtk.Window;
with Gtk.Widget;   use Gtk.Widget;
with Gtk.Table;    use Gtk.Table;

with Ada.Unchecked_Conversion;
with Gdk.Event;
with Gtk.Main;

procedure Hello is
   Window : Gtk_Window;
   Grid   : Gtk_Table;
   Label  : Gtk_Label;

   type Event_Callback is not null access function
        (  Self  : access Gtk_Widget_Record'Class;
           Event : Gdk.Event.Gdk_Event
        )  return Boolean;
   function "+" is
      new Ada.Unchecked_Conversion
          (  Event_Callback,
             Cb_Gtk_Widget_Gdk_Event_Boolean
          );
   type Destroy_Callback is access procedure
        (  Widget : access Gtk_Widget_Record'Class
        );
   function "+" is
      new Ada.Unchecked_Conversion
          (  Destroy_Callback,
             Cb_Gtk_Widget_Void
          );

   function Delete_Event_Handler
            (  Widget : access Gtk_Widget_Record'Class;
               Event  : Gdk.Event.Gdk_Event
            )  return Boolean is
   begin
      return False;
   end Delete_Event_Handler;

   procedure Destroy_Handler
             (  Widget : access Gtk_Widget_Record'Class
             )  is
   begin
      Gtk.Main.Main_Quit;
   end Destroy_Handler;
begin
   Gtk.Main.Init;
   Gtk.Window.Gtk_New (Window);
   Window.Set_Title ("Hello World");
   Window.On_Delete_Event (+Delete_Event_Handler'Access);
   Window.On_Destroy (+Destroy_Handler'Access);

   Gtk_New (Grid, 1, 1, False);
   Window.Add (Grid);
   Gtk_New (Label);
   Label.Set_Text ("Hello World!");
   Grid.Attach (Label, 0, 1, 0, 1);
   Label.Show;

   Grid.Show;
   Window.Show;

   Gtk.Main.Main;
end Hello;

hello-world in GtkAda. In this code Delete_Event_Handler is a function local to the main procedure Hello. When access to it is taken it is deeper than the access type expected by On_Delete_Event (Cb_Gtk_Widget_Gdk_Event_Boolean). The compiler will reject:

Window.On_Destroy (Destroy_Handler'Access);

on this ground. Now the trick is to convert a local access to the required global one ignoring potential error since we know for sure that Destroy_Handler will never be called outside Hello:

   type Event_Callback is not null access function
        (  Self  : access Gtk_Widget_Record'Class;
           Event : Gdk.Event.Gdk_Event
        )  return Boolean;
   function "+" is
      new Ada.Unchecked_Conversion
          (  Event_Callback,
             Cb_Gtk_Widget_Gdk_Event_Boolean
          );

Now Delete_Event_Handler’Access’Access is of Event_Calback type. And +Delete_Event_Handler’Access’Access is the required Cb_Gtk_Widget_Gdk_Event_Boolean.

There is nothing special about choosing “+” as the conversion name. It could be any name. “+” is a short one.

Note that all this is highly deprecated and has no place in production code where you would declare Delete_Event_Handler in a library level package.

In this short example it is used to keep all in a single source file.

3 Likes

Many thanks!

In the meantime I found the definitions :slight_smile: :

 --
   -- Circumvention of accessibility checks
   --
   type Local_Widget_Callback is
     access procedure (Widget : access Gtk_Widget_Record'Class);
   function "+" is new
     Ada.Unchecked_Conversion
       (Local_Widget_Callback,
        Cb_Gtk_Toggle_Button_Void);
   function "+" is new
     Ada.Unchecked_Conversion (Local_Widget_Callback, Cb_Gtk_Button_Void);
   type Local_Delete_Callback is
     access function
       (Widget : access Gtk_Widget_Record'Class; Event : Gdk_Event)
        return Boolean;
   function "+" is new
     Ada.Unchecked_Conversion
       (Local_Delete_Callback,
        Cb_Gtk_Widget_Gdk_Event_Boolean);

Where can I find relatively up-to-date examples of using GtkAda? I’m particularly interested in Signal Handling and User Data management.
Unfortunately, I’m new to Gtk, as I mainly use Qt.

In any program using GtkAda. You must be more specific because handling depends on whether

  • There is a predefined primitive operation in the corresponding GtkAda object, like On_Delete_Event.
  • If the predefined primitive useful. Many are not, like On_Button_Click. So you would rather use Connect.
  • If the handler parameter profile codified in GtkAda. In a few cases you need to extract parameters manually from the untyped handler form.
  • If the signal is masked out by default, like keyboard events. You need to unmask them in order to catch.

As for User_Data it is practically always the widget that connects to the signal. BTW always derive a custom widget type from predefined containers. It is easy to do and you can fill the extension type with necessary data.

So, learning by doing is the best way to go forward. Start programming and you will find out. You can also safely ignore GtkAda documentation and read the Gtk one. There is a direct mapping from Gtk to GtkAda.

Thanks for the help!

Yes, I know that the best way to learn is through practice. However, the AdaCore GtkAda manual is completely confusing to me regarding Signal handling. I think this is because I don’t know Gtk well enough.
Also, there is the fact that several example programs are based on the Glade & Builder method, but I don’t want to use it for the time being. (As far as I know, Glade is deprecated). And there is also the Gtk2 - Gtk3 difference too.
I will try with the official Gtk documentation, but since I am also learning Ada, it won’t be easy! :slight_smile:
Although, I can actually write a C program in any programming language… :laughing:

Right, Glade is wasting time. If you want to use Gtk seriously forget Glade exists.

GtkAda is Gtk3

GtkAda documentation is mostly generated content = useless. You need to understand the GtkAda design principle. GObject is a poor man’s OO implementation on top of C. GtkAda has an Ada tagged type or interface for each “class” Gtk creates using GObject. Signal handling is also made typed in GtkAda so that most signal handlers have an Ada access to subprogram type with all arguments properly typed. GtkAda parses the actual signal arguments for you when it calls the handler.

GtkAda brings proper typing on top of untyped Gtk. It is a great improvement in terms of use and safety, but you need to know Gtk still. Once you navigated the topic in the Gtk documentation it is quite easy to track it down to GtkAda by scanning the GtkAda packages. You will find that GtkAda code is far easier to write and understand than Gtk itself.

Note also that Gtk is single threaded. If you want to use it in connection with Ada tasks you need to marshal to the main thread. There is a library to do that.

Sorry, but I need some help because I simply can’t understand the use of the Gtk.Handlers package.
What I want to do is to handle the On_Click event of several buttons with a single handler and be able to write out which button was pressed. All this using user_data. Since I couldn’t find any other docs, I’m trying to interpret the relevant part of the official GtkAda documentation.
I understand in general terms what the documentation wants to convey, but somehow the whole To_Marshaller concept doesn’t add up.
I’m trying to understand this section: https://docs.adacore.com/gtkada-docs/gtkada_ug/\_build/html/signals.html#connecting-via-the-gtk-handlers-package

I don’t understand, for example, how I can send a Record or other custom data type as user_data? I really just need a simple example. I’m trying to browse the testgtk/ folder in the GtkAda source, but it wants to show everything in bulk…

The signal is called “clicked.” On_Clicked is a primitive operation of Gtk_Button_Record. See Gtk.Button package.

The first step is to choose the type of the emitter. It can be any antecedent of Gtk_Button_Record. E.g. it can be Gtk_Button_Record or Gtk_Widget_Record or GObject_Record. I frequently use GObject_Record for the sake of reusing the instantiation for signals having the same parameter profile (same user data). This will be the first parameter of the handler, which is usually irrelevant, which is why On_Clicked is useless.

The second step is to choose the right package in Gtk.Handlers. The choice depends on the signal parameters. Look for the clicked signal:

As you see there is no parameters. Self is the first parameter, user data is the second. The first parameter we selected in the step one. There is no result, so it must be a procedure. Ergo, the right package is User_Callback in Gtk.Handlers:

generic
   type Widget_Type is new Glib.Object.GObject_Record with private;
   type User_Type (<>) is private;
package User_Callback is

This package defines the handler profile:

type Simple_Handler is access procedure
       (Widget    : access Widget_Type'Class;
        User_Data : User_Type);

This is exactly what we need as it matches the signal parameters. Now, we want to pass a record type as user data. Note that this would be totally useless, but you asked, so I respond… :upside_down_face:

Let we have the record type declared as:

type My_Record_Type is record
   I : Integer;
end record;

Then we instantiate the package like this:

package My_Record_Handlers is
   new Gtk.Handlers.User_Callback (Gtk_Button_Record, My_Record_Type);

That is it!

We declare a procedure to handle the signal with the profile of Simple_Handler:

procedure My_Handler (Widget : access Gtk_Button_Record'Class;
                     User_Data : My_Record_Type) is
begin
   ... -- Do something useful on button clicked
end My_Handler;

Then we connect it to a button like this. Given My_Button (of Gtk_Widget) and My_Record (of My_Record_Type):

My_Record_Handlers.Connect (My_Button, "clicked", My_Handler'Access, My_Record);

Now in the real life. User_Data is almost always an access to a widget. Rather than record. Note that user data is marshalled = copied. So it must an access type anyway. The user data widget is usually a container that keeps the button. As I said before, you would derive your widget type from, say, Gtk_Grid_Record. Add custom members into the type extension, like Integer above. You do not want global data hanging around. So the custom widget is the right place to put any custom stuff into. Place the button into the grid and connect the handler from Initialize. Enjoy!

In the dependents of the gtkada Alire crate:

  1. adagl_gtk3
  2. aicwl
  3. bingada
  4. cheddar
  5. dashera
  6. eagle_lander
  7. emdee
  8. hexapod_simulation_telemetry
  9. lsystem_editor
  10. ppa_gtkada
  11. spawn_glib

Thank you very much!
I made the example program based on your description and it works! I also made a button that calls a handler that expects a window as a parameter and modifies its title. It doesn’t have much practical use, I was just experimenting:

callbacks.ads:

package My_Window_Handlers is new Gtk.Handlers.Callback (Gtk_Window_Record);

procedure My_Win_Handler (Widget : access Gtk_Window_Record'Class);

callbacks.adb:

procedure My_Win_Handler (Widget : access Gtk_Window_Record'Class) is
begin
   Widget.Set_Title ("OK!");
   Ada.Text_IO.Put_Line ("OK!");
end My_Simple_Handler;

And in the main.adb:

My_Window_Handlers.Object_Connect (Button3, "clicked",
-- My_Window_Handlers.To_Marshaller (My_Win_Handler'Access),
   My_Win_Handler'Access,
   Main_Window);

What is not entirely clear is in which cases the To_Marshaller function is needed? Only because my Gtk.Window example works without it. I’m really sorry for the stupid questions, but even Ada itself is very new to me. I’ve been developing C++ / Qt5 for most of my life. Plus, I like to understand why and how something works…

To_Marshaler is used when generics get confused about the types and Connect cannot be resolved. Then you can explicitly help it like this (from AICWL):

   Layer.Changed :=
      Handlers.Connect
      (  Adjustment,
         "changed",
         Handlers.To_Marshaller (Changed'Access),
         Layer'Unchecked_Access
      );

Here Changed is a handler of the adjustment’s signal “changed.”

   procedure Changed
             (  Adjustment : access GObject_Record'Class;
                Needle     : Needle_Ptr
             );

Needle_Ptr is not exactly the access Layer’Class, so To_Marshaller is needed.

Adjustment is a general Gtk class for moving things like sliders. In this case it is used to move a gauge needle. The needle itself is a layer of an instrument. The needle is the user data. The handler takes the layer from the user data and the adjustment position and rotates the needle accordingly.

If it works without To_Marshaller then you are good.

Your questions are not stupid and you are welcome to ask.

Thank you very much for your help! Unfortunately, I haven’t had time to deal with the topic in the past few days, but now again!