Re: Exporting the Gtk+ object system to interpreters



Tim Janik <timj@gtk.org> writes:

> On 7 Dec 1998, Marius Vollmer wrote:
> 
> > I'm currently hacking (rather unfocused) on exporting the Gtk+ object
> > system to Guile Scheme. [...]
> 
> i've added a gtk_type_query() interface (similar to gtk_signal_query)
> as you may have noticed, please tell me if that fullfills your needs.

In principle yes.  I don't understand why it is `better' than my
original gtk_type_get_info, but I don't have to.

[ I was a little bit ticked off when I noticed that you had replaced
  my perfectly working gtk_type_get_info with gtk_type_query that did
  *not* work.  I have calmed down since.  Sorry, if I said something
  offensive.
]

> 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.

I'm going to address your posting point for point first, but at the
end there's also a more concise exposition of what I think we should
do.  It's not much different, but still.

> so if i understood your (and keneth) needs correctly, something like
> the following should be implemented:
> 
> typedef enum                    /*< flags >*/
> {
>   GTK_RUN_FIRST         = 1 << 0,
>   GTK_RUN_LAST          = 1 << 1,
>   GTK_RUN_BOTH          = (GTK_RUN_FIRST | GTK_RUN_LAST),
>   GTK_RUN_NO_RECURSE    = 1 << 2,
>   GTK_RUN_ACTION        = 1 << 3,
>   GTK_RUN_NO_HOOKS      = 1 << 4,
> + GTK_RUN_UNMARSHALLED  = 1 << 5

I'm not sure if we need this one.  Just setting marshaller=NULL should
be indicative enough.

> } GtkSignalRunType;
> 
> typedef void (*GtkUnmarshalledFunc) (GtkObject   *object,
> 				     GtkType	  instance_type,
>   				     guint	  signal_id,
> 				     guint        n_params,
> 				     GtkArg      *params,
> 				     gpointer	  data);

I don't think we should introduce another type of function for this
purpose.  Gtk+ generally uses functions of type GtkCallbackMarshal to
invoke umarshalled functions.  For reference:

typedef void (*GtkCallbackMarshal) (GtkObject *object,
				    gpointer   data,
				    guint      n_args,
				    GtkArg    *args);

It would be great if we could just use this kind of function instead
of your proposed GtkUnmarshalledFunc because the GtkCallbackMarshal is
already there and supported by the interpreter bindings.

I think we actually can just use GtkCallbackMarshal because the
additional information that you want to pass as a service can be
transported by whatever the interpreter bindings want to put into
DATA.  For example, the Scheme bindings have no problem channeling
arbitrary extra data into a Scheme callback because such callbacks are
actually `closures' that carry their complete environment.

But supporting GtkUnmarshalledFunc is certainly possible from
guile-gtk, if not as elegant.

> /* this function is just an alias for gtk_object_class_user_signal_newv()
>  * with signal_flags|=GTK_RUN_UNMARSHALLED and marshaller=NULL. it is used
>  * to create new signals for classes that are not derived in C.
>  */
> guint gtk_object_class_unmarshalled_signal_newv (GtkObjectClass  *klass,
>                                                  const gchar     *name,
>                                                  GtkSignalRunType signal_flags,
>                                                  GtkType          return_val,
>                                                  guint            n_params,
>                                                  GtkType         *params);

I would prefer not to use the GTK_RUN_UNMARSHALLED flag and just use
gtk_object_class_user_signal_new with marshaller=NULL.  But this
function certainly does no harm.

> /* setting an unmarshalled default handler automatically sets the
> /* corrsponding
>  * class function to NULL, and thus can only be done once per klass
>  * and signal.
>  * (we therefore don't need a GtkDestroyNotify function for the data arg).
>  * this can be used to override inherited default handlers
>  * in derived classes and to specify default handlers for newly created
>  * unmarshalled signals.
>  */
> void  gtk_object_class_set_unmarshalled_handler (GtkObjectClass     *klass,
>                                                  guint               signal_id,
>                                                  GtkUnmarshalledFunc default_handler,
>                                                  gpointer            data);

I would prefer something compatible to the established `full' callback
API.  Something like

void
gtk_object_class_set_default_handler_full (GtkObjectClass    *klass,
                                           guint              signal_id,
					   GtkSignalFunc      func,
                                           GtkCallbackMarshal marshal,
                                           gpointer           data,
                                           GtkDestroyNotify   notify);

> /* 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);

This is fine with me.

> the default handlers will be invoked as:
> void
> interp_default_handler (GtkObject      *object,
> 		        GtkType	        instance_type,
> 			guint           signal_id,
> 			guint           n_params,
>                         GtkArg         *params,
>                         gpointer        data)

As previously, I'd like it to conform to GtkCallbackMarshal.

> {
>   /* the GtkArg params[] array conforms to the GtkType*params types, specified
>    * for gtk_object_class_unmarshalled_signal_newv(). a possible return
>    * value is stored in params[n_params].
>    */
>  
>   /* chaining of the parent class' handler (e.g. required for ::destroy) */
>   gtk_object_invoke_default_handler (object,
>                                      gtk_type_parent (instance_type),
>                                      signal_id,
>                                      params);
> }
> 
> the instance_type parameter cares about proper chaining of default handlers.
> e.g. a scheme type SchemePrompt could derive from SchemeInput which would
> in turn derive from GtkEntry, and both of them would override
> GtkEditable::insert_text:
> gtk_object_class_set_unmarshalled_handler (gtk_type_class ("SchemeInput"),
>                                            gtk_signal_lookup ("insert_text",
>                                                               GTK_TYPE_EDITABLE),
>                                            scheme_input_default_handler_insert_text,
>                                            data);

This would be more like

gtk_object_class_set_default_handler_full (gtk_type_class ("SchemeInput"),
    				           gtk_signal_lookup ("insert_text",
                                                              GTK_TYPE_EDITABLE),
                                           NULL,
					   sgtk_callback_marshal,
                                           scm_proc,
                                           sgtk_callback_destroy);

where sgtk_callback_marshal and sgtk_callback_destroy are completely
general routines (they do exist already and are used for all other
callbacks right now) and scm_proc would be a SCM value that refers to
the interpreted Scheme routine that is to be used as the insert_text
default handler.  There is no way I could provide a separate C
function for every signal that I might want to have a Scheme default
handler.

For extra wrapping convenience (which might go a bit far, I'm not
sure), it would be nice to change the interface of
gtk_object_class_set_default_handler_full to be

void
gtk_object_class_set_default_handler_full (GtkType            type,
                                           gchar             *signal_name,
					   GtkSignalFunc      func,
                                           GtkCallbackMarshal marshal,
                                           gpointer           data,
                                           GtkDestroyNotify   notify);

that is, use GtkType instead of a class pointer, and use the name of
the signal instead of its id.  That way, I could just add

    (define-func gtk_object_class_set_default_handler_full
      none
      ((type type)
       (string signal_name)
       (full-callback function)))

to gtk-1.1.defs and be done with it.  One can certainly provide both
forms of the interface and it is no problem to add the latter one from
outside of Gtk+.

> upon emission of the signal:
> gtk_signal_emitv (GTK_OBJECT (scheme_prompt),
>                   gtk_signal_lookup ("insert_text", GTK_TYPE_EDITABLE),
>                   &params);
> the call sequence will be as follows:

this has all to happen in Scheme, of course.  Not a single line of C
code should need to be written to define the two new types
SchemePrompt and SchemeInput from Scheme.

> void
> scheme_prompt_default_handler_insert_text (GtkObject *object,
>                                            GtkType    instance_type,
>                                            ...)
> {
>   /* GTK_OBJECT_TYPE (object) is gtk_type_from_name ("SchemePrompt"),
>    * instance_type is gtk_type_from_name ("SchemePrompt").
>    * the next call invokes scheme_input_default_handler_insert_text()
>    */
>   gtk_object_invoke_default_handler (object,
>                                      gtk_type_parent (instance_type),
> [...]
>
> i'm passing in the signal_id to GtkDefaultHandler, because it's required
> to chain the parent class' default handler,

I think that a default handler `knows' for what signal and class it is
the default handler, so it also knows the signals name and the class'
type.  It is not necessary to pass in this information.  Doing it
nevertheless leads to an incompatability between the callback
interface provided by the rest of Gtk+ and the default handler
mechanism.  I like to avoid this incompatibility.

> 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.

>  instance_type will always be GTK_TYPE_OBJECT
> (object) in such cases (or we may define another
> GtkUnmarshalledHandler type for them, having the signature of
> GtkUnmarshalledFunc without the instance_type arg).

That would be GtkCallbackMarshal.

----------

Ok, now.  Let me try to explain how I would like it to be in toto.  

[ Sorry for being repetive and boring, but I like to write those
  things down in the most general way to check whether the basic
  assumptions make sense.
]

Currently, there is enough support in Gtk+ for nicely connecting
interpreted handlers to existing signals.  This is done via a
`advanced' concept of what a callback is.  A callback is not just a
pointer to a C function that has a certain signature (that is only
enforced by convention).  Rather, it also includes a way to
dynamically pass arguments in a type-safe way, to allow the callback
to carry an environment with it (the DATA thing), and to get a
notification when the callback is no longer needed by Gtk+.

The signal mechanism was extended to allow for this more complicated
notion of what a callback is but the default_handler mechanism cannot
deal with it.  This is the basic problem I want to fix.  The fix
should not interfere with the current way of handling
default_handlers, as this way is very reasonable and efficient for the
common case.

So we need to have default_handlers that are not just pointers, but
offer the full palette of options that is available to the normal
handlers (analog to gtk_signal_connect_full).  We need a way to invoke
them in a generic way (analog to gtk_signal_emit and gtk_signal_emitv).

Plus, we need to define new signals that have no default_marshaller
(and no class function slot), because we can't--in general--provide
them from interpreted languages.

I like to extend the semantics of gtk_signal_newv and add two new
functions, gtk_signal_set_default_handler_full and
gtk_signal_invoke_default_handler_v.  [Very, very similar to the
things you have proposed.]

gtk_signal_newv
---------------

guint
gtk_signal_newv	(const gchar	    *name,
		 GtkSignalRunType    signal_flags,
		 GtkType	     object_type,
		 guint		     function_offset,
		 GtkSignalMarshaller marshaller,
		 GtkType	     return_val,
		 guint		     nparams,
		 GtkType	    *params);

The prototype remains the same, but it is also OK to set marshaller to
NULL.  Setting marshaller to NULL also requires function_offset to be
0 so that the new signal can not have a class function slot.  This is
required because one cannot invoke the default_handler via the class
function slot without a marshaller.

The consequence of marshaller being NULL is that one can only connect
handlers that have their own custom marshaller (via
gtk_signal_connect_full) and that one--likewise--can only use
default_handlers with their own custom marshaller (via
gtk_signal_set_default_handler_full).

Because there is no class function slot, default handlers for this
signal can only be invoked via gtk_signal_invoke_default_handler_v
(and friends) and not directly via the class function slot.


gtk_signal_set_default_handler_full
-----------------------------------

void
gtk_object_class_set_default_handler_full (GtkType            klass,
                                           gchar             *signal_name,
					   GtkSignalFunc      func,
                                           GtkCallbackMarshal marshal,
                                           gpointer           data,
                                           GtkDestroyNotify   notify);

This is a new function that can be used to change (or set for the
first time) the default handler of a signal.  It works with any kind
of signal, regardless whether it has a default marshaller or not, and
regardless whether it has a class function slot or not.

The default handler can make use of all the features of the `full'
callback interface.

When the signal has a class function slot (and therefore also is
required to have a default marshaller) and the new default handler has
no custom marshaller (MARSHAL==NULL), then the function in the class
function slot is simply replaced with FUNC.  DATA and NOTIFY are *not*
meaningful in this situation.  [DATA can't be because the default
handler might be invoked directly via the class function slot.  NOTIFY
could be handled, but I think it makes the implementation simpler when
it isn't and it's not a big loss.]

When MARSHAL is not NULL or the signal has no class function slot,
then the full semantics apply.  It does not matter whether the signal
has a default marshaller or not in this case.  The default handler is
invoked with DATA and NOTIFY is called at the right time.

In this situation, the default handler can not be invoked directly via
the class function slot, only via gtk_signal_invoke_default_handler.
So, when the signal has such a slot, it is set to a special function
that will trigger an error whenever it is called.  It is expected that
this error function is never called, of course.  It is assumed that
the default handler is only ever set from code that implements KLASS.
That code can make the guarantee that there will always be a working
class function slot.  Or might choose not to make this guarantee, but
in any case this guarantee is a static feature of a class and code
that uses the class function slots knows whether the guarantee holds.
All existing class effectively give this guarantee and all classes
defined in C should to do so.


gtk_signal_invoke_default_handler_v
-----------------------------------

void
gtk_signal_invoke_default_handler_v_by_name (GtkObject *object,
                                             GtkType    klass,
                                             gchar     *signal_name,
                                             GtkArg    *parms);

This function can be used to invoke any default handler whatsoever.
We could also provide more functions of this kind:

void
gtk_signal_invoke_default_handler_v (GtkObject *object,
                                     GtkType    klass,
                                     guint      signal_id,
                                     GtkArg    *parms);

void
gtk_signal_invoke_default_handler (GtkObject *object,
                                   GtkType    klass,
                                   guint      signal_id,
                                   ...);

Etc.

------------

So, this is very, very, very much like what you have proposed, but I
like it slightly better because I think it fits better into the
existing conventions.

As a first step I would be happy to have the ability to define new
signals that have no default marshaller, no class function slot and no
default handler.  This can be done by slightly extending
gtk_signal_newv to allow marhsaller to be NULL (and simultanously
requiring FUNCTION_OFFSET to be 0, too), and changing
gtk_signal_connect to only allow handlers with custom marshallers to
connect to those signals.

I don't feel like implementing the more involved
gtk_signal_set_default_handler_full stuff before 1.2.  But it would be
nice if something like my original patches (gtk_type_query and the
extended semantics of gtk_signal_new/gtk_signal_connect_full) could be
included.

Anyone who has managed to read upto here is invited to Free and Open
beer, should we ever meet.

cheers,
 Marius



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