[gtkada] GtkAda signal handlers interface proposal

Emmanuel Briot briot at adacore.com
Thu Apr 28 10:04:16 CEST 2016


> The use case is a widget (or GObject) handling signals from other widgets, usually its children. Presently this requires instantiation of a Gtk.Handlers.* package because it requires passing the widget as a signal parameter. This quite difficult to get right, especially the Connect call.

From our conversation on comp.lang.ada, I am wondering whether you missed the Slot parameter
to the On_* signal procedures, or the Object_Connect procedures in Gtk.Handlers ?
These seem to be exactly what is needed here, and for sure this is what we use when we develop
GPS.
The only drawback is that the Slot object is passed as a GObject, so in the callback you need to convert
back to your widget type.


> Now the package Gtk.Button will also have an interface declaration:
> 
>    type Clicked_Handler is interface;
>    procedure On_Clicked
>              (  Emitter : not null access Gtk_Button_Record'Class;
>                        -- Additional signal-specific parameters go here
>                 Handler : not null access Clicked_Handler 
>              )  is abstract;
> 
> The Gtk_Button_Record type will get yet another primitive operation
> 
>    procedure Handle_Clicked
>              (  Self    : not null access Gtk_Button_Record;
>                 Handler : not null access Clicked_Handler'Class;
>                 After   : Boolean := False
>              )


I was thinking along the same line yesterday: callbacks could be objects, rather than access to
subprograms. We also use something like that in GPS, for all actions exported to the user, or
for internal "hooks". 
This is indeed very flexible, since the actual handler can then embed its own data (and thus we
no longer need, ever, to instantiate the complex handlers packages from gtk.handlers with
User_Data.
The minor drawback is that it is slightly heavier to write (need to create a new type), and slightly
harder to navigate the code (in the case of GPS, we have lots of "Execute" subprograms which
we then need to look at). In your proposal, we would have On_Clicked, On_Draw,... so this 
problem is slightly less visible, and the proposal is nice because one object can then handle
multiple events.
One other thing we need to pay attention to is to allow something like:

    Button.On_Click           --  I prefer to keep the current names rather than Handle_Clicked
          (new Click_Handler);    --  unless we do something, this raises a compiler error because of
                                                --  accessibility checks

To handle this, we need unfortunately a 'Unrestricted_Access somewhere internally.

And the other difficulty is for the lifecycle, since we need to know when the handler can be freed
(which also either requires the handler to be refcounted, or the user has to guarantee that he doesn't
pass the same handler object to multiple widgets).  That part is difficult to get right (you might have
missed that part since in your later example the handler is the widget itself, whose lifecycle is
already handled -- but nothing prevents the handler from being a different object).


>    procedure Initialize
>              (  Widget : not null access My_Widget_Record'Class
>              )  is
>    begin
>       ...
>       Widget.Button1.Handle_Clicked (Widget);
>       Widget.Button2.Handle_Clicked (Widget);
>    end Initialize;

Right now, you can already use

     Widget.Button1.On_Click (On_Clicked'Access, Slot => Widget);

to achieve a very similar effect. The full power of having objects for the handlers only comes when
you need to pass additional user data to the callback, which is not so frequent but still worth handling
properly.


>    procedure On_Clicked
>              (  Emitter : not null access Gtk_Button_Record'Class;
>                 Widget  : not null access My_Widget_Record
>              )  is
>    begin
>       if Emitter = Widget.Button1 then
>          ... -- This is from Button1
>       elsif Emitter = Widget.Button2 then
>          ... -- This is from Button2
>       end if;
>    end On_Clicked;

I am not sure I would use this style of a long list of "if .. elsif", but that of course depends on the code
you need to write.

As you set up your example (a primitive On_Clicked operation on the widget itself, used by all nested
widgets), this is fairly restrictive. You need this long list, and end up with a design similar to what the
first versions of Java were doing (was is AWT at the time, I don't remember the name) ? They were
forcing this approach, and this ended up being so restrictive that later versions chose a different and
more flexible approach. Your proposal is flexible, but the example you give is not really a good one I
think.

I would have:

     type My_Widget_Record is new Gtk_Button_Record with ...

     type My_Click_Handler is new Clicked_Handler with null record;
     overriding procedure On_Click (...)

     Widget.Button1.On_Click (new My_Click_Handler)


Emmanuel





More information about the gtkada mailing list