[gtkada] GtkAda signal handlers interface proposal
Dmitry A. Kazakov
mailbox at dmitry-kazakov.de
Thu Apr 28 11:24:32 CEST 2016
On 28/04/2016 10:04, Emmanuel Briot wrote:
>> 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.
That reason and because it requires instantiation from Gtk.Handlers
anyway. Automatic handler disconnection is rarely an issue.
>> 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.
The abstract procedure/function could be named "Handle" or "Execute",
but Something_<signal name> has that big advantage that the handler
object may handle many different signals by implementing multiple
interfaces:
type My_Widget_Record is ...
and Clicked_Handler
and Button_Press_Handler
and Button_Release_Handler
...
So you would not want conflicting signal handler names.
> 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.
Actually I considered means to prevent this sort of misuse. E.g. by
having the handler object as a plain parameter rather than
access-to-object. This construct does not make sense in my view, because
the purpose is to have some existing (visible elsewhere) object passed
to the handler callback. If the handler object can be that sort
anonymous, the existing callback procedures do the job nicely.
> 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).
I thought about that, but unfortunately Ada does not have proper
multiple-inheritance to deal with that. The interface would be a
GObject_Record descendant if Ada didn't have that flaw.
I think it could still be possible to fool the compiler into it. We
could change Glib.Object this way:
type GObject_Interface is interface;
-- Primitive operations of GObject_Record are declared on
GObject_Interface:
procedure Ref (Object : access GObject_Interface) is abstract;
Now the GObject_Record:
type GObject_Record is new GObject_Interface with private;
-- Primitive operations of GObject_Record are overriding of
GObject_Interface:
overriding procedure Ref (Object : access GObject_Record) is abstract;
The handler interfaces will then be
type Clicked_Handler is interface and GObject_Interface;
This will allow calling Ref/Unref or even conversion to GObject_Record
(risking Constraint_Error at run-time). The user will not be able to use
an arbitrary type as a handler's base. Only descendants of
GObject_Record will be eligible or else he will be forced to implement
reference counting operations.
>
>> 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.
The purpose is to have My_Widget_Record as a parameter. Handling
automatic disconnection is not so interesting, however, having *_Handler
a descendant of GObject_Interface will allow transparent disconnection
handling if the handler gets prematurely destroyed.
>
>> 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.
The purpose was to illustrate that handling a signal from multiple
sources were possible. It is worth to consider an additional parameter
for the procedure On_Click (in your nomenclature) connecting the handler
object, Connection_ID, a plain Positive passed along to the handler
procedure On_Clicked. It will make implementation slightly more
difficult but more comfortable for the user since he will be able to use
a case-statement instead.
> 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)
This is a very specific case, which better be:
type My_Widget_Record is new Gtk_Button_Record with private;
type My_Click_Handler is new Clicked_Handler with null record;
overriding procedure On_Click (...)
Handler : My_Click_Handler; -- Or placed in My_Widget_Record
Widget.Button1.On_Click (My_Click_Handler);
Existing callback procedures do this better.
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
-------------- next part --------------
An HTML attachment was scrubbed...
URL: </pipermail/gtkada/attachments/20160428/e596ca2a/attachment-0001.html>
More information about the gtkada
mailing list