Re: Exporting the Gtk+ object system to interpreters



On 19 Dec 1998, Marius Vollmer wrote:

> Tim Janik <timj@gtk.org> writes:
> 

> > please read the C comments below more carefully. default handlers
> > need to be setup in class_init() functions and from then on are
> > considered static data.
> 
> Why should they be static?  Granted, most of them will be static, but
> it would sure be handy to change a default handler at any time while
> developing a program in a more dynamic language.
> 
> > we shouldn't break that convention, not even for interpreter
> > bindings, i couldn't even come up with a reason why you'd want to
> > subsequently change default handlers.
> 
> But that's only you.  You don't hold the complete wisdom of the world
> in your little hands, Tim.  Neither do I, of yourse, but I have a
> different part of that wisdom.  We should try to implement the union
> of both of our parts, not only the intersection.

i don't really understand what your first two sentences meant to express.
we have a great portion of code that is using gtk already and we want to
extend gtk's facilities to allow full widget and signal creations from
interpreter bindings, that's certainly a noble goal. while we want to
implement additional functionality, we still have to take care to adhere to
existing concepts and to preserve semantics and assumptions that gtk-based
code currently makes, one of which is, that signal default handlers provide
certain functionality and behaviour that doesn't change throughout a programs
life time. thus, their setting occours only once and remains static from then
on, e.g. default handler chaining becomes pointless otherwise.
i'm not trying to put your suggestions down and am not attempting to keep
gtk's facilities within some limited, or privately known, scope, but it appears
to me that you haven't fully understood some of gtk's fundamental concepts
and are taking comments from me personally, which are actually meant to be of
technical merit and to reflect those concepts and their constrains.
rather than fighting a battle on some personal level, i'd like us to figure
out an elegant and hopefully comprehensive way to provide the required
functionality while keeping the API and implementation reasonably simple
and efficient, and of course while adhering to existing concepts.

> > thus, the data argument does not need a destroy notifier.
> 
> Yes, it might not need one, but it is part of the "full" callback
> specification.  We can ignore it if the data is never actually
> destroyed, but it should be included in the interface for setting a
> default handler.

i still don't get why a destroy notifier argument should be present,
especially since you are suggesting that we should ignore it.
we do feature destroy notifiers for existing dynamic callbacks, but since
we are just installing a non-dynamic, i.e. *static*, function pointer here
that does not serve callback semantics but takes care about an object methods
implementation, the extra argument would be bogus junk in the API.

> In my current implementation of gtk_signal_set_class_function_full I
> can't call the destroy notifier either, because I can't track the
> number of copies I have made of a full default handler.  I don't
> consider this a big problem for the time being, but it's a bug,
> nevertheless.
> 
> > also, default handlers for existing C classes cannot (and actually
> > *must not*) be changed. thus, you may only use
> > gtk_object_class_set_unmarshalled_handler() for newly derived
> > classes, from within their class initializer.
> 
> Yes, I want to enforce part of this rule in my Scheme bindings.  It
> will only let you set the default handler for classes defined in
> Scheme.  I don't think we need to restrict the setting of default
> handlers to the class initializer, tho.

it will, however, in most cases be required for default handlers to be setup
appropriatedly upon clases creation time, since they mostly are initialized
during the first object instantiation for this type (or derived types), and
the object initializers already rely on the default handlers to be setup
properly.
if you for instance derive a GtkHBox from GtkBox in scheme, and later someone
derives from that hbox to create a composite widget, you'll need to have
most of the default handlers working correctly from the object_init functions,
otherwise you couldn't add children or set properties for the composite
container.

> > as much as i'd like to keep the above [GtkCallbackMarshall]
> > prototype as an interface, it's not possible to get that going with
> > default handler chaining. you're right in that at least guint
> > signal_id can be eliminated, i meant that as a convenience for you
> > actually.
> 
> The instance_type can be omitted just the same.  The first indication
> why this is the case is that the existing default handlers don't need
> it either.
> 
> Look at this code from gtkwindow.c:
> 
>     static void
>     gtk_window_destroy (GtkObject *object)
>     {
>       [...]
>       GTK_OBJECT_CLASS (parent_class)->destroy (object);
>     }
> 
> This is the default handler of the GtkWindow widget for the "destroy"
> signal.  It `knows' this.  It knows the type of the object it is
> associated with, and it knows the appropriate parent_class.  It knows
> that it is a "destroy" default handler.  This knowledge does not have
> to be passed to it from the Gtk+ signal or type system.  It has been
> `built in' by the writer of the code.  All this can (and has to) be
> built into a Scheme or Perl default handler, too, by the writer of
> that handler, not by the general glue code.

this applies to C code only. since from scheme itself or even from the glue
code you can't invoke GTK_OBJECT_CLASS (parent_class)->destroy (object),
because you can't dynamically build up the parameter list for the signal
handler (most handlers take extra arguments and return values, e.g.
gint (* button_press_event)      (GtkWidget          *widget,
                                  GdkEventButton     *event);
or
void (* size_allocate)       (GtkWidget      *widget,
                              GtkAllocation  *allocation);)
you need gtk to do the marshalling for you (especially since the marshallers
for gtk are already in place in the signal system).
thus you need a function like the one i formerly proposed:

/* function to be used internally from within the signaling system,
 * and from unmarshalled default handlers to chain the parent class'
 * default handler. GTK_TYPE_OBJECT (object) is required to
 * be derived from instance_type.
 */
void gtk_object_invoke_default_handler (GtkObject *object,
                                        GtkType    instance_type,
                                        guint      signal_id,
                                        GtkArg    *params);

without `instance_type', gtk_object_invoke_default_handler() wouldn't know
whether it is supposed to call gtk_window_destroy, gtk_bin_destroy,
gtk_container_destroy, gtk_widget_destroy ot gtk_object_destroy, since they
are all default handlers for the `destroy' signal of `object'.

> More abstractly, using artificial invoke_default_handler and
> set_default_handler functions and signals that have no signal-specific
> parameters:
> 
> Suppose you have these two functions:
> 
>   void
>   set_default_handler (GtkType   klass,
>                        gchar    *signal_name,
>                        void    (*handler) (GtkType instance_type,
>                                            void *data),
>                        void     *data);
> 
>     Set the default handler of KLASS for the signal denoted by
>     SIGNAL_NAME to be HANDLER, and associate it with DATA.

hu? where's the GtkObject* pointer in the default handler prototype?

>   void
>   invoke_default_handler (GtkType  klass,
>                           gchar   *signal_name);
> 
>     Invoke the default handler of KLASS for the signal denoted by
>     SIGNAL_NAME.  This invokes the function last set via
> 
>       set_default_handler (klass, signal_name, data, handler);
> 
>     as
> 
>       handler (klass, data);
> 
>     or, when set_default_handler has never been called for the
>     combination of KLASS and SIGNAL_NAME, it is equivalent to
> 
>       invoke_default_handler (type_parent (klass), signal_name);
> 
>     or, when KLASS has no parent type, it does nothing at all.
> 
> Do we agree that this is the semantic of default handlers?  You can
> coerce the current practice into this `formalism' by replacing things
> like
> 
>     klass->signal_name = handler
> 
> with
> 
>     set_default_handler (klass, "signal_name", handler, NULL);
> 
> and likewise
> 
>     klass->signal_name ();
> 
> with
> 
>     invoke_default_handler (klass, signal_name);
> 
> Now, you can see from this definitions that a HANDLER is *always*
> invoked with an INSTANCE_TYPE parameter set to the KLASS that was used
> when setting the HANDLER itself (unless the same handler has been used
> for more than one invokation of set_default_handler).  That is, after
> 
>     set_default_handler (klass, signal_name, handler, data);
> 
> HANDLER will always be invoked as
> 
>     handler (klass, data);
> 
> when used to act as the default handler for KLASS and SIGNAL_NAME.
> That is, the association between a handler and a klass are made at
> set_default_handler time, not at invoke_default_handler time.  Thus,
> you can use a handler that knows the klass it will be invoked with,
> and you do not need to pass the klass in.
> 
> The only situation when a handler can be invoked with varying
> instance_type parameters is when it has been specified in more than
> one call to set_default_handler for different klasses/signal_names.
> Suppose we have
> 
>     set_default_handler (klass1, signal_name1, handler, data1);
>     set_default_handler (klass2, signal_name2, handler, data2);
> 
> Now, HANDLER can be invoked as either
> 
>     handler (klass1, data1);
> 
> or
> 
>     handler (klass2, data2);
> 
> As you can see, the instance type is no longer associated with the
> precise handler.  But, it is still associated with the call to
> set_default_handler, and thus, DATA1 and KLASS1, as well as DATA2 and
> KLASS2 will always be associated.  We again can avoid passing the
> instance_type parameter because we can put it into the things hanging
> off of the data cookie.

i don't understand this paragraph at all, since you totally omitted the
object pointer that the default handler is to be invoked for. in case
you simply omitted it because of implicit inclusion like for c++ methods,
where you'd have:

object->handler (klass1, data);

the real prototype of this handler would be:

void handler (GtkObject*, GtkObjectClass*, gpointer data);

in which case our *only* difference is that i passed the `instance_type'
as a GtkType argument and you used a GtkObjectClass* argument.
though i'm not sure that's what you are aiming at, since in your above
example, set_default_handler() took a default handler argument of
void    (*handler) (GtkType instance_type,
                    void *data);
which is of course totaly confusing class pointers with type id's in
the way you used that prototype later on.

> For my Scheme bindings, I wouldn't even need to do this, because the
> Scheme procedures can do it for themselves if they want to.
> 
> [ The exact same argumentation can be used to eliminate the signal_id
>   parameter, btw, and I'm sure you used it already for discovering
>   this fact.
> ]

well, you could probably demand that the user specifies which default
handler should be invoked, and use that as a `instance_type' argument
for gtk_object_invoke_default_handler(), though something like
"(chain-parent)" would probably be much nicer. in any case, by omitting
the `instance_type' and `signal_id' arguments, default handlers could
never be totally automated functions since both of these parameters
couldn't be figured without extra user intervention.
if you are so concerned about using the same prototype for GtkCallbackMarshal
handlers and GtkUnmarshalledFunc, we can change that prototype to

typedef void (*GtkUnmarshalledFunc) (GtkObject   *object,
                                     gpointer     data,
                                     guint        n_params,
                                     GtkArg      *params,
                                     GtkType      instance_type,
                                     guint        signal_id);
so it adheres to
typedef void (*GtkCallbackMarshal) (GtkObject *object,
                                    gpointer   data,
                                    guint      n_args,
                                    GtkArg    *args);
and you don't have to worry about different semantics.

> Below, you mention "(chain-parent)", which is supposed to
> automatically invoke the default handler for the `current' signal of
> the parent of the `current' type.  I don't want to have such a thing
> at this stage, and in any case, implementing it is outside the scope
> of the Gtk+ type system (or even my Scheme bindings for that matter).

a possible implementation of a gtk_object_invoke_default_handler()
function would not be part of gtk's type system, but gives an API to
the marshalling abilities provided by gtk's signal system.
i don't think we should omit `instance_type' and `signal_id', because
you personally don't want to provide "(chain-parent)" fucntionality at this
point, i'm pretty sure these parameters could come in handy for other
interpreter bindings that want to provide (chain-parent) fucntionality or
other generic default handler implementations that haven't yet been
considered.

in a previous mail, you already mostly agreed with the points that i outlined,
[
On 13 Dec 1998, Marius Vollmer wrote:

> hm, there's actually a bunch of things that needs to be adressed to
> get the signaling behaviour you require.
> 1) you need to create signals that don't provide normal marshallers
>    at all (i.e. the default handlers and possibly connected handlers
>    will always be invoked with unmarshalled parameters: guint n_params,
>    GtkArg *params, etc).

Yes.

> 2) you need a mechanism to specify a default-handler with gpointer *data
>    arg that is not retrived as a function pointer through the normal class
>    offset, and which gets invoked with unmarshalled parameters.

Yes, this would be the `full' interface for callbacks.  We already
have gtk_signal_connect_full, and now we need something like
gtk_signal_set_class_function_full.

> 3) you need a mechanism to eventually chain the default handler of the
>    parent class (e.g. if you create a new container type in scheme, you'd
>    probably want to chain the GtkContainer::add handler of the parent
>    class from within your scheme-default-handler for GtkContainer::add).
>    for GtkObject::destroy this is even mandatory.

Yes.
]
but we do seem to have some conceptual diferences in mind, regarding the
dynamic/static nature of normal signal connections and default handlers,
and with that as background we simply tend to misunderstand each other
on merely minor items.

i'm glad you attempted to outline these in your last mail.

> > > > C functions that want to connect to unmarshalled signals (though
> > > > that behaviour is questionable, it should be taken care of) will be
> > > > invoked with the same signature as the default handler, i.e. that of
> > > > GtkUnmarshalledFunc.
> > > 
> > > Again, I would strongly prefer to keep the old mechanism for this.  C
> > > functions can already be connected to signals without a default
> > > marshaller via gtk_signal_connect_full.  They would then be invoked as
> > > a GtkCallbackMarshal.
> > 
> > i'm not talking about marshaller-connections here like you can specify
> > with gtk_signal_connect_full() when passing func=NULL, i'm talking
> > about normal gtk_signal_connect() invokations here.
> 
> Ahh, I think I understand now where our interpretions differ.  The
> status quo is:
> 
>     Non-default handlers can be connected as plain C functions that
>     make use of the default marshaller provided by the signal.
>     Non-default handlers can also be connected as custom-marshalled
>     functions that bring their own marshaller.
> 
>     Default handlers can be set as plain C functions that make
>     use of the default marshaller provided by the signal.
> 
>     To connect a non-default handler, you use gtk_signal_connect_full
>     which allows for both plain C functions as well as
>     custom-marshalled ones.  You can also use gtk_signal_connect as a
>     convenience function for plain C function.
>     To set a default handler, you just put a pointer to the function
>     in the appropriate class field.
> 
> (I gloss over how to invoke handlers here.)
> 
> I want to even out this unsymmetric situation so that non-default
> handlers and default handlers are more `equal'. 

stop here, please... ;)

the situation is "unsymmetric" for good reasons actually, default
handlers and non-default handlers are conceptually very different things,
and that is reflected by various technical details in gtk's API and
implementation. once you gathered the conceptual differences, i hope you
figured why "equalizing" them is a bad idea ;)

a default handler is the implementation of an object method associated
with certain object classes. the reason for default handlers to be invoked
through function pointers is not because it is a dynamic callback method like
normal user provided signal handlers are, but to give an ability to have
user provided signal handlers be invoked before, after or inbetween invokations
of default handlers, and to give the ability to "chain" parent classes default
handlers. this could have been handled through direct function invocations
from the code portions that do the gtk_signal_emit() calls, but by using
function pointers, a marshaller and exposing their prototypes to the signal
system, they can be dealt with in a generic fashion, and the signal emitting
code is greatly simplified.
thus, a default handler
-	is implemented and set only once per class and signal
-	usually implements certain constrains that an objects method
	requires
-	remains set throughout a programs life time and gets invoked for
	every object that is an instance of the default handlers class
-	may (sometimes *must*) chain default handlers of parent classes,
	and thus needs to be "chainable"
-	are provided by the widget writer only

user provided signal handlers serve in principle pure notification semantics,
thus signal handler connections
-	are associated with objects and signals (and end their life time with
	an object's life time)
-	may be blocked or disconnected again
-	are not present for every object's instance of a specific class
-	may exist multiple times per object and signal
-	have a destruction notifier because they are handled completely dynamic
-	are provided by any party

in general, user provided signal handlers are used for notification of user
code for certain objects upon elected events, or to adjust a certain object's
specific behaviour for concrete situations (that's why invokation of default
handlers may be intercepted for *some* emissions of *some* signals for elected
objects only).
default handlers in contrast affect *all* objects of a certain class and
often all objects of dervied classes as well. thus the constrains of not
altering existing default handler setups, and preserving implemented behaviour
for all derived code portions.
if you encounter a situation where you'd actually want to change an existing
default handler, you should either use signal connections instead or design
the default handler to be more flexible in the first place. (there are very
rare exceptions to this, for instance playing sounds upon every button press
and somesuch, and that is appropriately taken care of through
GtkEmissionHooks).

whether those callbacks or default handlers are called in unmarshalled or
in marshalled forms, are actually technical particularities, that don't
relate to their fundamental concepts at all.
unmarshalled callback invokations have been brought up only for the sake of
interpreter bindings anyways, and thus you should be pretty satisfied with my
formerly proposed introduction of GTK_RUN_UNMARSHALLED signals and the
constrain that default handlers with a function_offset of 0 are always called
in unmarshalled form.
the only additional information/idea you gave in your last mail, is that
people might be interested to specify default handler implementations for
user signals as well, and since they already provided a marshaller for 
these signals, it wouldn't be nice from gtk to invoke their default
handlers in unmarshalled form. that's certainly a worthwhile concern/
usefull addition to existing concepts that should be taken care off when
something like gtk_object_class_set_unmarshalled_handler() gets implemented,
but it may not be used to blur the destinction between technical merits and
concepts like marshalling vs. default handlers and signal callbacks.

> 
> - Marius
> 

---
ciaoTJ




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]