Xfce Foundation Classes
 « Main Page

The XFC Signal System

Table of Contents

  1. GTK+ Signals
  2. Two XFC Signal Systems
  3. Libsigc++ Signals and Slots
  4. GDK Events
  5. The Signal Connection
  6. Virtual Signal Classes

GTK+ Signals

GTK+ is an event driven toolkit. When an application-specific event occurs, such as a keystroke or a mouse event, GTK+ generates a signal emission. Signals are emitted on a given type instance and are identified by strings, like "clicked" for the GtkButton clicked signal. When a signal is emitted, the set of callbacks connected to the signal are invoked in a precisely defined manner. There are two distinct callback types, per-object callbacks and user provided callbacks.

Per-object callbacks are also called object signal handlers, and are virtual because they are inherited and can be overridden in derived objects. GTK+ objects provide default signal handlers at signal creation time. Derived objects can then assign object signal handlers to override one or more of the parent's default signal handlers. It is important to note that derived object signal handlers may or may not choose to call the parent's default signal handler. This allows a derived object to override its default implementation, changing the way it behaves.

User provided callbacks are just called signal handlers, and are frequently connected to and disconnected from certain signals on certain object instances. Signal handlers are called in the order they were connected in. All handlers may prematurely stop a signal emission, and any number of handlers may be connected, disconnected, blocked or unblocked during a signal emission. Unlike per-object callbacks, user provided callbacks cannot override or change an object's default implementation.

Two XFC Signal Systems

XFC provides the same signal functionality as GTK+ by implementing two separate signal systems, libsigc++ signals and slots and abstract virtual signal classes. Each system is distinct from the other so it's important to understand the difference. Libsigc++ signals and slots are used to connect user provided callbacks and the virtual signal classes are used to override per-object callbacks in derived classes.

Libsigc++ signals and slots are template classes that wrap the GTK+ signal connection and emission process, and provide a mechanism for typesafe user provided callbacks. XFC objects that can receive a signal's emission wrap the signal's connection and emission mechanism in a protected signal template. Protected signals are declared static and so require an object instance. For each protected signal an XFC object also provides a public proxy signal function. Proxy signals are instance specific. Their only purpose is to pass the object's 'this' pointer to its protected signals as the instance argument.

Virtual signal handlers wrap the GTK+ per-object callbacks and are implemented in XFC as an abstract signal class hierarchy. To override one or more object virtual signal handlers your class must multiplely inherit from the object and either the object's signal class or one of its parent signal classes. This separation of object and virtual signal handlers helps to reduce application size and improve application performance.

Other C++ language bindings put the virtual signal handlers directly into the object classes, and then hard link the virtual function calls into the GTK+ signal emission mechanism. This increases application size because each object ends up with a large virtual function table, and it reduces application performance because each virtual signal handler is called on every signal emission whether your class uses the handler or not. For example, widgets inherit some 70+ virtual signal handlers, but most applications use standard widgets that don't need them. In a large application this gratuitous overhead can become significant.

In XFC, the virtual function overhead is minimized because virtual signal handlers are implemented in a separate abstract class hierarchy that must be specifically inherited from. When you use standard widgets in your application and connect signal handlers using libsigc++ slots there is no hidden virtual function overhead. As result your application will be smaller and faster than if it were written with any comparable C++ language binding.

Libsigc++ Signals and Slots

The template classes used to implement GTK+ signals can be found in the file <xfc/glib/signals.hh>. The class G::Signal<> is a convenience wrapper around numbered G::Signal#<> classes. G::Signal<> determines which numbered signal template to use based on the number of template arguments supplied.

Class signals are protected data members and are declared static. As an example take a look at Gtk::Widget's "size_request" signal:

typedef G::Signal<void, Gtk::Requisition*> SizeRequestSignalType;
typedef G::SignalProxy<G::TypeInstance, SizeRequestSignalType> SizeRequestSignalProxy;
static const SizeRequestSignalType size_request_signal;
 
For convenience, the first line typedefs the size_request type G::Signal<void, Requisition*>. The first template argument specifies the signal handler's return value. All other template arguments specify the parameters passed to the signal handler, in order. In the case of SizeRequestSignalType, connected signal handlers take only one argument, a Gtk::Requisition pointer.

The second line declares the return type for the signal's proxy signal function. The third line declares the static const variable 'size_request_signal'. You can use this variable to connect a signal handler to the "size_request" signal, but only in derived classes because it's declared protected.

You would connect to size_request_signal like this:

size_request_signal.connect(this, sigc::mem_fun(this, &MyWindow::on_size_request));

sigc::mem_fun() is new in libsigc++ 2.0 and simply returns a member function slot.

You wont connect to signals in derived classes like this very often. Instead you would use a signal's public proxy function. For size_request_signal the proxy signal function is defined inline like this:

inline const Xfc::Gtk::Widget::SizeRequestSignalProxy
Xfc::Gtk::Widget::signal_size_request()
{
    return SizeRequestSignalProxy(this, &size_request_signal);
}
 
SizeRequestSignalProxy is an inline proxy object that provides access the protected signal's connect() method. As you can see signal_size_request() returns a temporary proxy object by value and passes the object's 'this' pointer as the object instance (to size_request_signal).

Using the proxy signal function you would connect to the "size_request" signal like this:

signal_size_request().connect(sigc::mem_fun(this, &MyWindow::on_size_request));

and the on_size_request() signal handler would be declared like this:

void
MyWindow::on_size_request(Gtk::Requisition *requisition)
{
    requisition.set(width, height);
}

Libsigc++ slots are type-safe representations of callback methods and functions. A slot can be constructed from any function, regardless of whether it is a global function, a member method, static, or virtual.

Earlier versions of libsigc++ (< 2.0) used a slot() function to return a slot. In libsigc++ 2.0 sigc::slot<> refers to a convenience wrapper around the numbered sigc::slot#<> templates. Like G::Signal<>, sigc::slot<> determines which numbered slot template to use based on the number of template arguments supplied. In libsigc++ 2.0, sigc::mem_fun() returns a member function slot and sigc::ptr_fun() returns a global function slot.

To create a new member function slot use sigc::mem_fun():

sigc::slot<void, Gtk::Requisition*> s = sigc::mem_fun(this, &MyWindow::on_size_request);

and to create a new global function slot use sigc::ptr_fun():

sigc::slot<void> s = sigc::ptr_fun(&somefunction);

You don't have to declare slot variables though. You can just pass sigc::mem_fun() and sigc::ptr_fun() directly to a signal's connect method or any API method. The compiler will complain if the slot has the wrong signature.

Two other useful libsigc++ functions are sigc::bind() and sigc::hide().

The main use of sigc::bind() with signals is to connect a signal handler taking an extra argument(s) to a signal expecting a lesser number of arguments. sigc::bind() is useful when you want to connect several widgets to the one signal handler. For example, the hellobuttons example application uses sigc::bind() to pass the name of the button clicked to a common signal handler that prints out the button's name.

button->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &HelloButtons::on_clicked), "button 1"));
button->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &HelloButtons::on_clicked), "button 2"));


The on_clicked() handler is defined like this:

void
HelloButtons::on_clicked(const char *text)
{
    std::cout << "Hello again" << " - " << text << " " << "was pressed" << std::endl;
}

What sigc::bind() does here is take the on_clicked() method that accepts one argument, and returns a function slot accepting no arguments that can be connected to the signal_clicked() proxy signal. When the "clicked" signal is emitted the HelloButtons on_clicked() method is called and the bound argument is passed as the text parameter.

sigc::hide() does the reverse. It allows you to connect a handler taking a lesser number of arguments to a signal expecting more arguments. For example, the expander example application uses sigc::hide() to connect the main window's inherited dispose() method, which takes no arguments, to the Gtk::Dialog "response" signal which expects one argument, an integer.

signal_response().connect(sigc::hide(sigc::mem_fun(this, &ExpanderDialog::dispose)));

sigc::bind() and sigc::hide() are more powerful than these simple usages so you should take a look at the libsigc++ documentation. For example, sigc::bind() lets you bind up to 7 arguments at a time and lets you specify the zero-based index of each bound function argument.

GDK Events

In addition to the widget signals described above, there is a set of GDK signals that reflect the X event mechanism. Slots can be connected to these signals using the proxy signal functions declared in the Gtk::Widget class:
  • signal_event()
  • signal_button_press_event()
  • signal_button_release_event()
  • signal_scroll_event()
  • signal_motion_notify_event()
  • signal_delete()
  • signal_destroy_event()
  • signal_expose_event()
  • signal_key_press_event()
  • signal_key_release_event()
  • signal_enter_notify_event()
  • signal_leave_notify_event()
  • signal_configure_event()
  • signal_focus_in_event()
  • signal_focus_out_event()
  • signal_map_event()
  • signal_unmap_event()
  • signal_property_notify_event()
  • signal_selection_clear_event()
  • signal_selection_request_event()
  • signal_selection_notify_event()
  • signal_proximity_in_event()
  • signal_proximity_out_event()
  • signal_visibility_notify_event()
  • signal_client_event()
  • signal_no_expose_event()
  • signal_window_state_event()
A function slot connected to one of these signals gets passed an event structure containing data specific to the event that occurred. For example, the event strucure passed to a button event handler is Gdk::EventButton. You would declare a 'button_press_event' handler like this:

bool on_button_press_event(const Gdk::EventButton& event);

The value returned from this function indicates whether the event should be propagated further by the GTK+ event handling mechanism. Returning true indicates that the event has been handled, and that it should not propagate further. Returning false will continue the event's normal handling.

The GDK selection and drag-and-drop APIs also emit a number of events which are reflected in GTK+ by signals. These signals are also declared in the Gtk::Widget class and are:
  •  signal_selection_received()
  •  signal_selection_get()
  •  signal_drag_begin_event()
  •  signal_drag_end_event()
  •  signal_drag_data_delete()
  •  signal_drag_motion()
  •  signal_drag_drop()
  •  signal_drag_data_get()
  •  signal_drag_data_received()

The Signal Connection

Lets take a closer look at the signal connection methods defined in <xfc/glib/signals.hh>. The signal connection method for a protected signal is declared like this:

sigc::connection connect(G::TypeInstance *instance, const SlotType& slot, const char *detail = 0, bool after = false) const;

and the connection method for the public proxy signal is declared like this:

sigc::connection connect(const SlotType& slot, bool after = false) const;

Protected signals are declared static so their connection method takes an instance pointer. Public proxy signals are instance specific so their connection method does not need an instance pointer, it uses the object 'this' pointer instead. The 'detail' argument is only used by the G::Object "notify" signal so you can mostly ignore it. The thing to note about both these connection methods is the return value.

All signal connection methods return a 'sigc::connection' object that identifies your slot. Keeping a copy of this object gives you further control over your connection.

sigc::connection c = signal_size_request().connect(sigc::mem_fun(this, &MyWindow::on_size_request));

sigc::connection is a convinience class for safe disconnection and can be used to disconnect the referred slot at any time:

c.disconnect();

If the slot has already been destroyed, disconnect() does nothing. You can call empty(), connected() or operator bool() to test whether the connection is still active.

if (c.empty())
    do_nothing();
else
    do_something();

or

if (c.connected())
    do_something();
else
    do_nothing();

or

if (c())
    do_something();
else
    do_nothing();

sigc::connection::empty() returns false if the connection is still active. Conversely, connected() and operator bool() return true if the connection is still active.

The signal connection can also be blocked and unblocked at any time:

c.block();

or

c.unblock();

You can call blocked() to test whether a signal connection is blocked:

if (c.blocked())
    c.unblock();

Virtual Signal Classes

Virtual signal classes are and interesting new feature in XFC, and something you wont find in other C++ language bindings. Their purpose is to improve application performance by minimizing the overhead associated with using virtual signal handlers. XFC provides a virtual signal handler for almost every GTK+ signal. GtkWidget has 63 signals. Add to this the widget-specific signals and GtkButton has 73 signals, GtkEntry has 75 signals and GtkMenuItem has 74 signals, and so on. Providing a virtual signal handler for all these signals can add a significant virtual function overhead to an application.

The approach taken by other C++ language bindings is to put the virtual signal handlers into the object or widget class that emits the corresponding signal. What this does is hard-link the virtual signal handler directly into the GTK+ signal emission chain, so that every time a GTK+ signal is emitted its virtual signal handler gets called. These virtual function calls really are gratuitous because they do nothing other than call the default GTK+ signal handler. Virtual signal handlers can only be used by derived classes. Considering that most widgets used in an application are standard widgets, virtual signal handlers are unnecessary.

What XFC has done is remove all the virtual signal handlers from the object and widget classes and puts them into an abstract class hierarchy.  These classes have names that correspond to the object and widget class names, such as Gtk::WidgetSignals, Gtk::ButtonSignals and Gtk::MenuItemSignals. Only those classes that emit signals have a corresponding signal class. To override one or more widget virtual signal handlers your widget class must derive from the widget and either the widget's signal class or one of its parent signal classes.

For example, a main window might be declared like this:

#include <xfc/gtk/window.hh>
#include <xfc/gtk/windowsignals.hh>

using namespace Xfc;

class MyWindow : public Gtk::Window, protected Gtk::WindowSignals
{
public:
    MyWindow();
};

or

#include <xfc/gtk/window.hh>
#include <xfc/gtk/widgetsignals.hh>

using namespace Xfc;

class MyWindow : public Gtk::Window, protected Gtk::WidgetSignals
{
public:
    MyWindow();
};

The first MyWindow declaration inherits from Gtk::WindowSignals. The second inherits from Gtk::WidgetSignals because it only needs to override Gtk::Widget virtual signal handlers.

The MyWindow constructors should be defined like this:

MyWindow::MyWindow()
: Gtk::WindowSignals(this)
{
}

or

MyWindow::MyWindow()
: Gtk::WidgetSignals(this)
{
}

There is only one constructor for each signal class and it takes a pointer to the corresponding object or widget.

The Gtk::WidgetSignals constructor is declared like this:

WidgetSignals(Widget *widget);

and the Gtk::WindowSignals constructor is declared like this:

WindowSignals(Window *window);

Virtual signal handlers are only hard-linked into the GTK+ signal emission chain when you inherit from a virtual signal class. This linking is done in the signal class constructor and not in the class_init function as is usual. This ensures that their is no unecessary virtual function overhead and it improves application performance.


Copyright © 2004-2005 The XFC Development Team
Top
XFC 4.4