GTK+ / Gnome Application Development | |||
---|---|---|---|
<<< Previous | Home | Next >>> |
A GtkObject can emit a signal. Signals are stored in a global table by GTK+. Handlers or callbacks can be connected to signals; when a signal is emitted, its callbacks are invoked. The process of invoking all handlers for a signal is called emission.
Abstractly, a signal is a kind of message that an object wants to broadcast; the kind of message is associated with certain conditions (such as the user selecting a list item) and with message-specific parameter types which are passed to connected callbacks (such as the index of the row the user selected). User callbacks are connected to a particular signal and to a particular object instance. That is, you do not connect callbacks to the "clicked" signal of all buttons; rather, you connect to the "clicked" signal of a particular one. (However, there is a way to monitor all emissions of a signal---these callbacks are called "emission hooks.")
Signals are typically associated with a class function pointer which is invoked every time the signal is emitted; if non-NULL, the pointed-to class function serves as a default handler for the signal. It is up to the author of each GtkObject subclass whether to provide a space in the class struct for a default handler, and whether to implement the default handler in the base class. Conventionally, signals have the same name as the class function they are associated with.
For example, the GtkButtonClass struct has a member called clicked; this member is registered as the default handler for the "clicked" signal. However, the GtkButton base class does not implement a default handler, and leaves the clicked member set to NULL. Subclasses of GtkButton could optionally fill it in with an appropriate function. If GtkButton did implement a default clicked handler, subclasses could still override it with a different one.
Note that GTK+ signals have nothing to do with UNIX signals. Sometimes new GTK+ users confuse the two.
Once you understand the GTK+ type system and GtkArg, signal registration is fairly transparent. Here is the signal registration code from GtkButton again:
button_signals[PRESSED] = gtk_signal_new ("pressed", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkButtonClass, pressed), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); button_signals[RELEASED] = gtk_signal_new ("released", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkButtonClass, released), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); button_signals[CLICKED] = gtk_signal_new ("clicked", GTK_RUN_FIRST | GTK_RUN_ACTION, object_class->type, GTK_SIGNAL_OFFSET (GtkButtonClass, clicked), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); button_signals[ENTER] = gtk_signal_new ("enter", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkButtonClass, enter), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); button_signals[LEAVE] = gtk_signal_new ("leave", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkButtonClass, leave), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, button_signals, LAST_SIGNAL); |
Earlier in gtkbutton.c, an enumeration and an array were declared as follows:
enum { PRESSED, RELEASED, CLICKED, ENTER, LEAVE, LAST_SIGNAL }; static guint button_signals[LAST_SIGNAL] = { 0 }; |
gtk_signal_new() has the following effects:
It registers the name of the signal.
It associates the signal with a particular GtkType.
It tells GTK+ where to find the default handler in the class struct, if any.
It tells GTK+ what signature the signal's callbacks will have.
It registers a marshaller, a function which invokes the signal's callbacks in an appropriate way.
It generates an integer identifier which can be used to refer to the signal. (If you refer to the symbol by name, GTK+ will find the ID associated with the name and then use the ID.)
gtk_object_class_add_signals() attaches signal identifiers to the object's class struct, so the signals for a given class can be rapidly located. Conventionally, the argument to this function is an enumeration-indexed static array, as shown for GtkButton. The static array is also useful when implementing the functionality of the class (the signal identifiers are used to emit the signals).
The first argument to gtk_signal_new() is a name for the signal; you refer to the signal by name when you call gtk_signal_connect(), for example. The third argument is the GtkType of the object type emitting the signal, and the fourth is the location of the associated class function in the type's class struct. A macro is provided to compute the offset. If you specify an offset of 0, no class function will be associated with the signal. Note that giving a zero offset is distinct from giving a valid offset but setting the function member in the struct to NULL; in the latter case, subclasses of the object can fill in a value for the default handler.
The second argument is a bitfield. Here are the associated flags:
GTK_RUN_FIRST means that the default handler in the class struct, if any, will run before user-connected callbacks. If this flag is set, signal handlers should not return a value.
GTK_RUN_LAST means the opposite, the default handler will run last. (Caveat: user callbacks connected with gtk_signal_connect_after() run after a GTK_RUN_LAST default handler. There is no way to ensure a default handler is always run last. GTK_RUN_FIRST handlers are always first, however.)
GTK_RUN_BOTH is an alias for (GTK_RUN_FIRST | GTK_RUN_LAST), so the default handler will run twice (on either side of user-connected callbacks).
GTK_RUN_NO_RECURSE means that the signal should not be called recursively. If a handler for a signal emits the same signal again, normally the second emission is performed as usual (calling all handlers), and then the first emission continues, invoking its remaining handlers. With GTK_RUN_NO_RECURSE in effect, a second emission aborts the first emission (ignoring any handlers that remain), and restarts the emission process. So only one emission is in progress at a time. (Right now this is used only for GtkAdjustment's "changed" and "value_changed" signals. Usually you don't care about how many times a value changed, only whether it changed and its most recent value. GTK_RUN_NO_RECURSE "compresses" multiple emissions into a single emission.)
GTK_RUN_ACTION means the signal can be "bound" and invoked by the user. In other words, no special setup or shutdown is required in order to emit it. Among other things, GTK+ will allow users to bind keyboard accelerators to these signals using statements in the .gtkrc configuration file.
GTK_RUN_NO_HOOKS means that emission hooks are not allowed (you can't monitor this signal for an entire object type, only for particular object instances). It is used for GtkObject's "destroy" signal because hooks are not invoked on objects with the GTK_DESTROYED flag set and that flag is set before emitting "destroy". It's probably not good for anything else.
The last few arguments to gtk_signal_new() provide a marshaller, and tell GTK+ the marshaller's type. A marshaller invokes a callback function, based on an array of GtkArg it receives from GTK+. Marshallers are needed because C function argument lists cannot be constructed at runtime. GTK+ comes with a number of prewritten marshallers; here is the one used for all GtkButton signals:
typedef void (*GtkSignal_NONE__NONE) (GtkObject* object, gpointer user_data); void gtk_marshal_NONE__NONE (GtkObject * object, GtkSignalFunc func, gpointer func_data, GtkArg * args) { GtkSignal_NONE__NONE rfunc; rfunc = (GtkSignal_NONE__NONE) func; (*rfunc) (object, func_data); } |
As you can see, the NONE__NONE refers to the fact that the expected callback type returns no value and has no "special" arguments. GTK+ automatically passes the object emitting the signal and a user_data field to all callbacks; special signal arguments are inserted in between these two. Since there are no signal-specific arguments in this case, the array of GtkArg is ignored.
The naming convention for marshallers places a double underscore between the return value and the special arguments, if any. Here's a more complex example:
typedef gint (*GtkSignal_INT__POINTER) (GtkObject * object, gpointer arg1, gpointer user_data); void gtk_marshal_INT__POINTER (GtkObject * object, GtkSignalFunc func, gpointer func_data, GtkArg * args) { GtkSignal_INT__POINTER rfunc; gint *return_val; return_val = GTK_RETLOC_INT (args[1]); rfunc = (GtkSignal_INT__POINTER) func; *return_val = (*rfunc) (object, GTK_VALUE_POINTER (args[0]), func_data); } |
Notice that the last element of the array of GtkArg is a space for the return value; if there is no return value, this element will have type GTK_TYPE_NONE and can be ignored. GTK+ provides macros such as GTK_RETLOC_INT() to extract a "return location" from a GtkArg. Similar GTK_RETLOC_ macros exist for all the fundamental types.
The function pointer signatures in the class structure for an object will correspond to the type of the signal. This is a convenient way to find out what signature the callbacks connected to a signal should have, if the GTK+ header files are readily available on your system.
The last arguments to gtk_signal_new() give the type of the signal's marshaller. First a return value type is given, then the number of special arguments, then a variable argument list containing that many GtkType values in the appropriate order. Since GtkButton has no examples of signals with arguments, here is one from GtkWidget:
widget_signals[BUTTON_PRESS_EVENT] = gtk_signal_new("button_press_event", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (GtkWidgetClass, button_press_event), gtk_marshal_BOOL__POINTER, GTK_TYPE_BOOL, 1, GTK_TYPE_GDK_EVENT); |
"button_press_event" returns a boolean value, and has a GdkEvent* argument. Notice that the marshaller works with any GTK_TYPE_POINTER, but the signal requires the more-specific boxed type GTK_TYPE_GDK_EVENT, allowing language bindings to query the correct kind of pointer.
Signals can have many arguments; here is one from GtkCList:
clist_signals[SELECT_ROW] = gtk_signal_new ("select_row", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkCListClass, select_row), gtk_marshal_NONE__INT_INT_POINTER, GTK_TYPE_NONE, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_GDK_EVENT); |
The "select_row" signal returns no value, but has three arguments (the selected row and column number, and the event that caused the selection).
Figure 4 shows the wide array of functions available for manipulating signals. You should already be familiar with the most fundamental signal operation: connecting a signal handler to be invoked when the signal is emitted, like this:
gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(delete_event_cb), NULL); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(button_click_cb), label); |
You may not be aware that gtk_signal_connect() returns a "handler ID" which can be used to refer to the connection it creates. Using the handler ID, you can unregister the callback with gtk_signal_disconnect(). You can also temporarily "block" the callback by calling gtk_signal_handler_block(). This increments a "block count"; the callback will not be invoked until the block count returns to 0. gtk_signal_handler_unblock() decrements the block count. Both gtk_signal_disconnect() and gtk_signal_handler_unblock() have variants that search for the handler ID given a callback function or user data pointer; these are possibly more convenient, with some loss of efficiency.
It can be useful to block signal handlers if you'll be changing some aspect of an object yourself, and thus don't need to run the callbacks you use to respond to user actions. For example, you normally change some boolean variable if the user clicks a toggle button, in a callback to the "toggled" signal. If you update the toggle button programmatically because the flag was changed via some mechanism other than the button, "toggled" will still be emitted; but you want to block your callback, since the flag is already correct.
gtk_signal_connect() is not the only way to connect to a signal. You can also use gtk_signal_connect_object(); this simply swaps the signal-emitting object pointer and the user data pointer in the arguments passed to the callback. Normally, the object comes first, then any arguments unique to the signal, and finally the user data pointer; with gtk_signal_connect_object(), the object is last and user data is first. This function is useful when you want to use a pre-existing function as a callback without writing a wrapper to move its arguments. For example:
gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(dialog)); |
Because the user data and the button are swapped, the first argument to gtk_widget_destroy() will be the dialog rather than the button, closing the dialog. When using gtk_signal_connect_object(), your callback data must be a GtkObject to avoid confusing marshallers that expect an object as their first argument.
gtk_signal_connect_after() asks GTK+ to run the callback after the object's default signal handler, rather than before it. This only works with certain signals, those with the GTK_RUN_LAST flag set; the section called Adding a New Signal explains this flag.
gtk_signal_connect_object_after() combines the effects of gtk_signal_connect_object() and gtk_signal_connect_after().
gtk_signal_connect_full() gives you complete control over the connection and is mostly useful in language bindings. The object_signal and after arguments can be TRUE or FALSE, toggling argument order and time of callback invocation. The functions we just mentioned also let you change this, so gtk_signal_connect_full() adds little. Its unique features are the ability to specify a callback marshaller, and the ability to specify a GtkDestroyNotify function. Notice that gtk_signal_connect_full() does not expect the same kind of marshaller described in the section called Adding a New Signal; it expects a more general marshaller appropriate for marshalling functions written in languages other than C. If you give a non-NULLGtkDestroyNotify function, it will be invoked on the user data pointer when this handler is disconnected or the GtkObject is destroyed. Here is the proper signature for the function:
typedef void (*GtkDestroyNotify) (gpointer data); |
Conveniently, you can use g_free() or gtk_object_destroy() as a GtkDestroyNotify. Of course, if these aren't appropriate you can write a custom function.
gtk_signal_connect_while_alive() is a variant on gtk_signal_connect(); its additional argument is an object to monitor. When the monitored object is destroyed (emits the "destroy" signal), the handler will be disconnected. That is, handlers connected with this function are automatically disconnected when a specified object no longer exists.
There's rarely a need to do so, but you can look up a signal's ID number given the signal name and the object type that emits it. This function is gtk_signal_lookup(). Note that names are not globally unique, but they are unique with respect to a particular object type. On the other hand, signal IDs are globally unique.
During the emission of a signal (that is, during the process of invoking its handlers), you can call gtk_signal_emit_stop() (or its _by_name() variant) to halt the emission. These functions are only useful from within signal handlers, because they must be called during the emission process or they won't have anything to stop. They do not take effect immediately; instead, they set a variable that GTK+ checks at key points during emission. the section called What Happens When A Signal Is Emitted describes this in detail.
Emission hooks can be used to monitor all emissions of a given signal (regardless of the object instance doing the emitting). Emission hooks have the following signature:
typedef gboolean (*GtkEmissionHook) (GtkObject *object, guint signal_id, guint n_params, GtkArg *params, gpointer data); |
They are passed the same parameters GTK+ would normally pass to callback marshallers (see the section called Adding a New Signal). You can connect an emission hook with a destroy notify function to be invoked on the user data pointer when the hook is removed. When you add an emission hook, an integer identify is returned. You can remove emission hooks with this ID number.
Emission hooks are rarely useful, but sometimes they are the only way to do something. For example, Gnome optionally plays sound effects when certain signals are emitted (such as button clicks).
Finally, you can ask everything you ever wanted to know about a signal using gtk_signal_query(). This function is intended for GUI builders and language bindings to use; it is probably not useful in application code. It returns a GtkSignalQuery structure filled with information about the signal. The return value should be freed with g_free() but not modified in any way (it contains pointers to internal data which isn't copied). Here is the definition of GtkSignalQuery:
typedef struct _GtkSignalQuery GtkSignalQuery; struct _GtkSignalQuery { GtkType object_type; guint signal_id; const gchar *signal_name; guint is_user_signal : 1; GtkSignalRunType signal_flags; GtkType return_val; guint nparams; const GtkType *params; }; |
#include <gtk/gtksignal.h> |
guint gtk_signal_lookup
(const gchar*
name, GtkType object_type);
gchar* gtk_signal_name
(guint signal_id);
void
gtk_signal_emit_stop
(GtkObject* object, guint signal_id);
void
gtk_signal_emit_stop_by_name
(GtkObject*
object, const
gchar*
name);
guint gtk_signal_connect
(GtkObject*
object, const
gchar* name,
GtkSignalFunc func,
gpointer
func_data);
guint
gtk_signal_connect_after
(GtkObject* object, const gchar*
name, GtkSignalFunc
func, gpointer func_data);
guint
gtk_signal_connect_object
(GtkObject* object, const gchar*
name, GtkSignalFunc
func, GtkObject*
slot_object);
guint
gtk_signal_connect_object_after
(GtkObject*
object, const
gchar* name,
GtkSignalFunc func,
GtkObject*
slot_object);
guint
gtk_signal_connect_full
(GtkObject* object, const gchar*
name, GtkSignalFunc
func,
GtkCallbackMarshal
marshal, gpointer
data, GtkDestroyNotify destroy_func, gint object_signal, gint after);
void
gtk_signal_connect_object_while_alive
(GtkObject*
object, const
gchar* signal,
GtkSignalFunc func,
GtkObject*
alive_object);
void
gtk_signal_connect_while_alive
(GtkObject*
object, const
gchar* signal,
GtkSignalFunc func,
gpointer func_data,
GtkObject *
alive_object);
void
gtk_signal_disconnect
(GtkObject* object, guint handler_id);
void
gtk_signal_disconnect_by_func
(GtkObject*
object,
GtkSignalFunc func,
gpointer
func_data);
void
gtk_signal_disconnect_by_data
(GtkObject *
object, gpointer
func_data);
void
gtk_signal_handler_block
(GtkObject* object, guint handler_id);
void
gtk_signal_handler_block_by_func
(GtkObject*
object,
GtkSignalFunc func,
gpointer
func_data);
void
gtk_signal_handler_block_by_data
(GtkObject*
object, gpointer
func_data);
void
gtk_signal_handler_unblock
(GtkObject* object, guint handler_id);
void
gtk_signal_handler_unblock_by_func
(GtkObject*
object,
GtkSignalFunc func,
gpointer
func_data);
void
gtk_signal_handler_unblock_by_data
(GtkObject*
object, gpointer
func_data);
guint
gtk_signal_add_emission_hook
(guint signal_id,
GtkEmissionHook
hook_func, gpointer
data);
guint
gtk_signal_add_emission_hook_full
(guint
signal_id,
GtkEmissionHook
hook_func, gpointer
data, GDestroyNotify destroy);
void
gtk_signal_remove_emission_hook
(guint signal_id, guint hook_id);
GtkSignalQuery* gtk_signal_query
(guint
signal_id);
Figure 4. Using Signals
It's your object's responsibility to emit its signals at appropriate times. This is very simple; if you've saved the return value from gtk_signal_new(), that identifier can be used to emit the signal. Otherwise, you can emit the signal by name (with some cost in execution speed, since GTK+ will have to look up the identifier in a hash table).
Here is code from gtk/gtkbutton.c which is used to emit the "button_pressed" signal:
void gtk_button_pressed (GtkButton *button) { g_return_if_fail (button != NULL); g_return_if_fail (GTK_IS_BUTTON (button)); gtk_signal_emit (GTK_OBJECT (button), button_signals[PRESSED]); } |
If a signal has arguments (other than the standard two), you must specify those as a variable argument list:
gtk_signal_emit (GTK_OBJECT (widget), widget_signals[SIZE_REQUEST], &widget->requisition); |
If a signal returns a value, you must pass a location for the returned value as the final argument:
gint return_val; return_val = FALSE; gtk_signal_emit (GTK_OBJECT (widget), widget_signals[EVENT], event, &return_val); |
Notice that return_val is initialized to something sane; if there are no signal handlers, none of them will assign a value to return_val. So you must initialize the variable. Each signal handler's return value will be assigned to the same location, so the final value of return_val is determined by the last signal handler to run. Note that certain return values (such as strings) must be freed by the signal emitter.
gtk_signal_emit_by_name() is the same as gtk_signal_emit(), except that the second argument is a signal name rather than a signal ID number. There are also variants of both emission functions that take a vector of GtkArg instead of a variable argument list. These variants expect arrays of n+1GtkArg structs, where n is the number of signal arguments and there is an additional GtkArg for the return value. The GtkArg structs should be initialized with sane values. If the function returns no value, the return value GtkArg will have GTK_TYPE_NONE.
All four signal emission functions are summarized in Figure 5.
#include <gtk/gtksignal.h> |
void gtk_signal_emit
(GtkObject* object, guint signal_id, ...);
void
gtk_signal_emit_by_name
(GtkObject* object, const gchar*
name, ...);
void gtk_signal_emitv
(GtkObject*
object, guint signal_id, GtkArg* params);
void
gtk_signal_emitv_by_name
(GtkObject* object, const gchar*
name, GtkArg* params);
Figure 5. Signal Emission
Keep in mind that it is usually inappropriate to simply emit a signal outside of an object's implementation. Only GTK_RUN_ACTION signals are guaranteed to work properly without special setup or shutdown. Objects often export functions you can use to emit signals properly; for example, to emit the "size_request" signal, GtkWidget provides this function:
void gtk_widget_size_request (GtkWidget *widget, GtkRequisition *requisition) { g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_WIDGET (widget)); gtk_widget_ref (widget); gtk_widget_ensure_style (widget); gtk_signal_emit (GTK_OBJECT (widget), widget_signals[SIZE_REQUEST], &widget->requisition); if (requisition) gtk_widget_get_child_requisition (widget, requisition); gtk_widget_unref (widget); } |
As you can see, particular actions are required before and after emitting the signal; thus it should only be emitted via the gtk_widget_size_request() function.
Given the many different options when creating signals and connecting callbacks, you may be thoroughly confused about what happens when a signal is emitted. Here's a summary of the sequence of events:
If you are emitting the signal by name, the signal ID is looked up.
If another emission of the same signal is in progress, and the signal has the GTK_RUN_NO_RECURSE flag set, GTK+ signals the previous emission to restart and this emission ends.
If the signal is GTK_RUN_FIRST, the default signal handler is called using the signal's marshaller. If the emission is stopped from within the handler, (using gtk_emit_stop_by_name() or one of its cousins), this emission ends. If the signal is re-emitted from within the handler and is GTK_RUN_NO_RECURSE, this emission restarts.
If there are any emission hooks installed for this signal, they are invoked. GTK+ does not check whether the emission has been stopped or re-emitted at this point; it will not check until the next step. Emission hooks should not re-emit the signal they are watching, or try to stop the emission.
Any normally-connected callbacks are invoked using the signal's marshaller. Callbacks connected with gtk_signal_connect_after() are not invoked at this point. After invoking each callback, GTK+ checks whether it stopped the signal and the emission ends if so. GTK+ also checks whether the signal was re-emitted, and if so restarts the emission process for GTK_RUN_NO_RECURSE signals.
If the signal is GTK_RUN_LAST, the default handler is invoked. Afterward GTK+ again checks whether the emission has been stopped or should be restarted.
Any callbacks connected with gtk_signal_connect_after() are invoked. After invoking each one, GTK+ checks whether the emission should be stopped or restarted.
Within each step the handlers are invoked in the order they were connected. The order of the steps is fixed: GTK_RUN_FIRST default handler, emission hooks, normal connections, GTK_RUN_LAST default handler, "after" connections.