Signaux

Connexion aux gestionnaires de signal
Les classes d'éléments graphiques gtkmm ont des fonctions membres d'accès aux signaux, comme Gtk::Button::signal_clicked(), qui permettent de se connecter au gestionnaire de signal. Grâce à la souplesse de libsigc++, la bibliothèque utilisée par gtkmm, le gestionnaire de signal peut être n'importe quel type de fonction, mais vous souhaiterez probablement utiliser une fonction membre de la classe. Les programmeurs C de GTK+ nomment souvent ces gestionnaires de signal « fonctions de rappel » (callback).
Voici un exemple de gestionnaire de signal connecté à un signal :

#include <gtkmm/button.h>

void on_button_clicked()
{
    std::cout << "Hello World" << std::endl;
}

main()
{
    Gtk::Button button("Hello World");
    button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
}

Il y a un certain nombre de choses à considérer à propos de ce code (non fonctionnel). D'abord identifions les parties concernées :
Le gestionnaire de signal est on_button_clicked().
Nous le raccrochons à l'objet Gtk::Button nommé button.
Quand le bouton émet le signal clicked, on_button_clicked() est appelé.
Maintenant ré-examinons la connexion :

    ...
    button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
    ...

Notez que nous ne passons pas directement un pointeur sur on_button_clicked() à la fonction membre connect() du signal. À la place, nous faisons appel à sigc::ptr_fun() et passons le résultat à connect().
sigc::ptr_fun() génère un objet sigc::slot. Un connecteur est un objet qui ressemble et réagit comme une fonction, mais qui, en réalité, est bien un objet. Il est également connu sous le nom d'objet fonction ou « functor ». sigc::ptr_fun() génère un connecteur pour une fonction autonome ou une fonction membre statique. sigc::mem_fun() génère un connecteur pour une fonction membre d'une instance donnée.
Voici un exemple un peu plus développé de connecteurs en action :

void on_button_clicked();

class some_class
{
    void on_button_clicked();
};

some_class some_object;

main()
{
    Gtk::Button button;
    button.signal_clicked().connect( sigc::ptr_fun(&on_button_clicked) );
    button.signal_clicked().connect( sigc::mem_fun(some_object,
                                     &some_class::on_button_clicked) );
}

Le premier appel à connect() est identique à celui que nous avons vu la fois précédente ; rien de plus à dire.
Le suivant est plus intéressant. sigc::mem_fun() est appelé avec deux paramètres. Le premier paramètre est some_object, qui est l'objet sur lequel notre nouveau connecteur pointe. Le second paramètre est un pointeur sur une de ses fonctions membres. Cette version particulière de sigc::mem_fun() crée un connecteur qui, quand il sera « appelé », fera lui-même appel à la fonction membre pointée de l'objet désigné, dans ce cas some_object.on_button_clicked().
Autre chose à noter dans cet exemple : nous appelons connect() deux fois pour le même objet signal. Cela est parfaitement correct — quand le bouton est cliqué, les deux gestionnaires de signal sont appelés.
Nous venons de vous dire que le signal clicked d'un bouton s'attend à appeler une fonction membre sans paramètre d'entrée. Tous les signaux ont des exigences de ce type — vous ne pouvez pas accrocher une fonction avec deux paramètres à un signal n'en attendant aucun (sauf si vous utilisez un adaptateur, tels que sigc::bind(), bien entendu). C'est pourquoi il est important de savoir quel type de gestionnaire de signal vous devez connecter à un signal donné.

Écriture de gestionnaires de signal
Pour savoir quel type de gestionnaire de signal vous pouvez connecter, regardez dans la documentation de référence ou dans le fichier d'en-tête. Voici un exemple de déclaration de signal que vous pouvez voir dans les fichiers d'en-tête de gtkmm :

Glib::SignalProxy1<bool, Gtk::DirectionType> signal_focus()

Outre le nom du signal (focus), deux choses sont importantes à noter ici : le nombre suivant le mot SignalProxy (1, dans notre cas), et les types de la liste (bool et Gtk::DirectionType). Le nombre indique le nombre de paramètres que le gestionnaire de signal doit posséder, le premier type, bool, est le type de retour du gestionnaire de signal et le suivant, Gtk::DirectionType, est le type du premier et unique paramètre de ce signal. En consultant la documentation de référence, vous pouvez également prendre connaissance des noms des arguments.
The same principles apply for signals which have more arguments. Here's one with three (taken from <gtkmm/textbuffer.h>):

Glib::SignalProxy3<void, const TextBuffer::iterator&, const Glib::ustrin&, int> signal_insert();

Il est de la même forme. Le nombre 3 à la fin du nom du type indique que le gestionnaire de signal nécessite trois paramètres. Le premier type dans la liste des types est void, c'est le type de retour du gestionnaire de signal. Les trois types suivants sont les types des paramètres, dans l'ordre. Notre prototype de gestionnaire de signal ressemble à quelque chose comme :

void on_insert(const TextBuffer::iterator& pos, const Glib::ustring& text, int bytes)

Déconnexion des gestionnaires de signal
Examinons à nouveau la fonction membre connect d'un signal :

sigc::signal<void,int>::iterator signal<void,int>::connect( const sigc::slot<void,int>& );

Notice that the return value is of type sigc::signal<void,int>::iterator. This can be implicitly converted into a sigc::connection which in turn can be used to control the connection. By keeping a connection object you can disconnect its associated signal handler using the method sigc::connection::disconnect().

Surdéfinition du gestionnaire de signal par défaut
Jusqu'ici nous vous avons montré la réalisation d'actions en réponse à une pression de bouton à l'aide d'un gestionnaire de signal. C'est assurément un bon moyen de réaliser les choses, mais ce n'est pas le seul.
Au lieu de connecter laborieusement les gestionnaires de signal aux signaux, vous pouvez simplement créer une nouvelle classe qui hérite d'un élément graphique — disons, un bouton —, puis surdéfinir le gestionnaire de signal par défaut, tel que Button::on_clicked(). Cela peut se révéler plus simple que d'accrocher des gestionnaires de signal pour chaque chose.
Le sous-classement n'est pas toujours la meilleure façon de réaliser les choses. Il est uniquement utile quand vous souhaitez que l'élément graphique gère son propre signal par lui-même. Si vous souhaitez qu'une autre classe gère le signal, il faut se connecter à un gestionnaire séparé. C'est encore plus vrai si vous voulez que plusieurs objets gèrent le même signal ou si vous voulez qu'un seul gestionnaire de signal réponde au même signal à partir d'objets différents.
Les classes gtkmm ont été conçues dans l'optique de surdéfinitions ; elles comportent des fonctions membres virtuelles spécialement pensées pour être surdéfinies.
Regardons un exemple de surdéfinition :

#include <gtkmm/button.h>

class OverriddenButton : public Gtk::Button
{
protected:
    virtual void on_clicked();
}

void OverriddenButton::on_clicked()
{
    std::cout << "Hello World" << std::endl;

// appeler la version de la classe de base de la fonction membre:
    Gtk::Button::on_clicked();
}

Ici nous définissons une nouvelle classe nommée OverriddenButton qui hérite de Gtk::Button. La seule chose que nous modifions est la fonction membre on_clicked() ; cette fonction membre est appelée chaque fois qu'un objet Gtk::Button émet le signal clicked. Elle affiche « Hello World » sur stdout, puis appelle la fonction membre originelle pour que Gtk::Button fasse ce qu'il a à faire quand il n'est pas surdéfini.
Il n'est pas toujours nécessaire d'appeler la fonction membre du parent ; certaines fois vous ne le souhaiterez pas. Notez que nous avons appelé la fonction membre du parent après avoir écrit « Hello World », mais nous aurions pu l'appeler avant. Dans cet exemple simple, cela n'a pas beaucoup d'importance, mais parfois si. Avec les signaux, il n'est pas facile de modifier des détails de ce type, et vous pouvez ici faire des choses que vous ne pourrez absolument pas faire avec des gestionnaires de signal connectés : vous pouvez appeler la fonction membre du parent au milieu de votre code personnalisé.

Liaison avec des paramètres supplémentaires
If you use one signal handler to catch the same signal from several widgets, you might like that signal handler to receive some extra information. For instance, you might want to know which button was clicked. You can do this with sigc::bind(). Here's some code from the helloworld2 example.

m_button1.signal_clicked().connect( sigc::bind<Glib::ustring>( sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 1") );

This says that we want the signal to send an extra Glib::ustring argument to the signal handler, and that the value of that argument should be "button 1". Of course we will need to add that extra argument to the declaration of our signal handler:

virtual void on_button_clicked(Glib::ustring data);

Of course, a normal "clicked" signal handler would have no arguments.
sigc::bind() n'est pas d'usage courant, mais peut-être le trouverez-vous commode parfois. Si vous êtes un familier de la programmation avec GTK+, vous avez alors probablement noté la similitude avec les paramètres supplémentaires gpointer data que toutes les fonctions de rappel possèdent. Généralement, cette possibilité est utilisée à l'excès dans GTK+ pour passer des informations qui devraient être stockées dans une donnée membre d'un élément graphique dérivé, mais la dérivation des éléments graphiques est très difficile en C. Nous avons beaucoup moins besoin de cette façon de faire dans gtkmm.

Signaux des événements X
The Widget class has some special signals which correspond to the underlying X-Windows events. These are suffixed by _event; for instance, Widget::signal_button_press_event().
You might occasionally find it useful to handle X events when there's something you can't accomplish with normal signals. Gtk::Button, for example, does not send mouse-pointer coordinates with its clicked signal, but you could handle button_press_event if you needed this information. X events are also often used to handle key-presses.
Ces signaux se comportent de façon légèrement différente. La valeur renvoyée par le gestionnaire de signal indique si l'événement a été pleinement « géré ». Si la valeur est false, alors gtkmm passe l'événement au gestionnaire de signal suivant. Si la valeur est true, aucun autre gestionnaire de signal n'est appelé.
Handling an X event doesn't affect the Widget's other signals. If you handle button_press_event for Gtk::Button, you'll still be able to get the clicked signal. They are emitted at (nearly) the same time.
Notez aussi que tous les éléments graphiques ne reçoivent pas tous les événements X par défaut. Pour recevoir des événements X supplémentaires, vous pouvez utiliser Gtk::Widget::set_events() avant d'afficher l'élément graphique ou Gtk::Widget::add_events() après. Toutefois, certains éléments graphiques doivent préalablement avoir été placés dans un élément graphique EventBox. Consultez le chapitre Éléments graphiques sans fenêtre-X.
Here's a simple example:

bool on_button_press(GdkEventButton* event);
Gtk::Button button("label");
button.signal_button_press_event().connect( sigc::ptr_fun(&on_button_press) );

Quand la souris est au dessus du bouton et qu'un bouton de souris est pressé, on_button_press() est appelé.
GdkEventButton est une structure contenant les paramètres de l'événement, tels que les coordonnées du pointeur de souris et l'heure où le bouton a été pressé. Les structures GdkEvent sont différentes selon les événements.

Séquencement des gestionnaires de signaux
By default, your signal handlers are called after any previously-connected signal handlers. However, this can be a problem with the X Event signals. For instance, the existing signal handlers, or the default signal handler, might return true to stop other signal handlers from being called. To specify that your signal handler should be called before the other signal handlers, so that it will always be called, you can specify false for the optional after parameter. For instance,

button.signal_button_press_event().connect( sigc::ptr_fun(&on_mywindow_button_press), false );

The event is delivered first to the widget the event occurred in. If all signal handlers in that widget return false (indicating that the event has not been handled), then the signal will be propagated to the parent widget and emitted there. This continues all the way up to the top-level widget if no one handles the event.

Exceptions in signal handlers
When a program is aborted because of an unhandled C++ exception, it's sometimes possible to use a debugger to find the location where the exception was thrown. This is more difficult than usual if the exception was thrown from a signal handler.
This section describes primarily what you can expect on a Linux system, when you use the gdb debugger.
First, let's look at a simple example where an exception is thrown from a normal function (no signal handler).

// without_signal.cc
#include <gtkmm.h>

bool throwSomething()
{
  throw "Something";
  return true;
}

int main(int argc, char** argv)
{
  throwSomething();
  Glib::RefPtr<Gtk::Application> app =
    Gtk::Application::create(argc, argv, "org.gtkmm.without_signal");
  return app->run();
}

Here is an excerpt from a gdb session. Only the most interesting parts of the output are shown.

> gdb without_signal
(gdb) run
terminate called after throwing an instance of 'char const*'

Program received signal SIGABRT, Aborted.
(gdb) backtrace
#7  0x08048864 in throwSomething () at without_signal.cc:6
#8  0x0804887d in main (argc=1, argv=0xbfffecd4) at without_signal.cc:12

You can see that the exception was thrown from without_signal.cc, line 6 (throw "Something";).
Now let's see what happens when an exception is thrown from a signal handler. Here's the source code.

// with_signal.cc
#include <gtkmm.h>

bool throwSomething()
{
  throw "Something";
  return true;
}

int main(int argc, char** argv)
{
  Glib::signal_timeout().connect(sigc::ptr_fun(throwSomething), 500);
  Glib::RefPtr<Gtk::Application> app =
    Gtk::Application::create(argc, argv, "org.gtkmm.with_signal");
  app->hold();
  return app->run();
}

And here's an excerpt from a gdb session.

> gdb with_signal
(gdb) run
(with_signal:2703): glibmm-ERROR **:
unhandled exception (type unknown) in signal handler

Program received signal SIGTRAP, Trace/breakpoint trap.
(gdb) backtrace
#2  0x0063c6ab in glibmm_unexpected_exception () at exceptionhandler.cc:77
#3  Glib::exception_handlers_invoke () at exceptionhandler.cc:150
#4  0x0063d370 in glibmm_source_callback (data=0x804d620) at main.cc:212
#13 0x002e1b31 in Gtk::Application::run (this=0x804f300) at application.cc:178
#14 0x08048ccc in main (argc=1, argv=0xbfffecd4) at with_signal.cc:16

The exception is caught in glibmm, and the program ends with a call to g_error(). Other exceptions may result in different behaviour, but in any case the exception from a signal handler is caught in glibmm or gtkmm, and gdb can't see where it was thrown.
To see where the exception is thrown, you can use the gdb command catch throw.

> gdb with_signal
(gdb) catch throw
Catchpoint 1 (throw)
(gdb) run
Catchpoint 1 (exception thrown), 0x00714ff0 in __cxa_throw ()
(gdb) backtrace
#0  0x00714ff0 in __cxa_throw () from /usr/lib/i386-linux-gnu/libstdc++.so.6
#1  0x08048bd4 in throwSomething () at with_signal.cc:6
(gdb) continue
Continuing.
(with_signal:2375): glibmm-ERROR **
unhandled exception (type unknown) in signal handler

Program received signal SIGTRAP, Trace/breakpoint trap.

If there are many caught exceptions before the interesting uncaught one, this method can be tedious. It can be automated with the following gdb commands.

(gdb) catch throw
(gdb) commands
(gdb)   backtrace
(gdb)   continue
(gdb)   end
(gdb) set pagination off
(gdb) run

These commands will print a backtrace from each throw and continue. The backtrace from the last (or possibly the last but one) throw before the program stops, is the interesting one.