Page suivante Page précédente Table des matières

20. Scribble, un programme simple de dessin

20.1 Présentation

Dans cette section, nous construirons un programme simple de dessin. Ce faisant, nous examinerons comment gérer les événements souris, comment dessiner dans une fenêtre, et comment mieux dessiner en utilisant un pixmap en arrière plan. Après avoir créé ce programme, nous l'étendrons en lui ajoutant le support des périphériques Xinput, comme les tables de tracé. GTK dispose de routines de support qui facilitent beaucoup l'obtention des informations étendues (comme la pression et l'inclinaison du stylet) à partir de tels périphériques.

20.2 Gestion d'événement

Les signaux GTK que nous avons déjà vus concernent les actions de haut niveau, comme la sélection d'un choix d'un menu. Cependant, il est quelques fois utile de connaître les cas de bas niveau, comme le déplacement de la souris, ou la pression d'une touche. Il existe aussi des signaux GTK correspondant à ces événements bas niveau. Les gestionnaires de ces signaux ont un paramètre supplémentaire qui est un pointeur vers une structure contenant des informations sur l'événement. Par exemple, les gestionnaires des événements de déplacement recoivent un paramètre vers une structure GdkEventMotion qui ressemble (en partie) à ceci :

struct _GdkEventMotion
{
  GdkEventType type;
  GdkWindow *window;
  guint32 time;
  gdouble x;
  gdouble y;
  ...
  guint state;
  ...
};

type sera initialisé avec le type de l'événement, ici GDK_MOTION_NOTIFY, window est la fenêtre dans laquelle l'événement est survenu. x et y donnent les coordonnées de l'événement et state spécifie l'état du modificateur lorsque l'événement s'est produit (c'est-à-dire quelles sont les touches de modification et les boutons souris qui ont été pressés). Il s'agit d'un OU bit à bit de l'une des valeurs suivantes :

GDK_SHIFT_MASK  
GDK_LOCK_MASK   
GDK_CONTROL_MASK
GDK_MOD1_MASK   
GDK_MOD2_MASK   
GDK_MOD3_MASK   
GDK_MOD4_MASK   
GDK_MOD5_MASK   
GDK_BUTTON1_MASK
GDK_BUTTON2_MASK
GDK_BUTTON3_MASK
GDK_BUTTON4_MASK
GDK_BUTTON5_MASK

Comme pour les autres signaux, on appelle gtk_signal_connect() pour déterminer ce qui se passe lorsqu'un événement survient. Mais nous devons aussi faire en sorte que GTK sache de quels événements nous voulons être avertis. Pour ce faire, on appelle la fonction :

void       gtk_widget_set_events          (GtkWidget           *widget,
                                           gint                 events);

Le deuxième champ spécifie les événements qui nous intéressent. Il s'agit d'un OU bit à bit de constantes qui indiquent différent types d'événements. Pour référence ultérieure, les types d'événements sont :

GDK_EXPOSURE_MASK
GDK_POINTER_MOTION_MASK
GDK_POINTER_MOTION_HINT_MASK
GDK_BUTTON_MOTION_MASK     
GDK_BUTTON1_MOTION_MASK    
GDK_BUTTON2_MOTION_MASK    
GDK_BUTTON3_MOTION_MASK    
GDK_BUTTON_PRESS_MASK      
GDK_BUTTON_RELEASE_MASK    
GDK_KEY_PRESS_MASK         
GDK_KEY_RELEASE_MASK       
GDK_ENTER_NOTIFY_MASK      
GDK_LEAVE_NOTIFY_MASK      
GDK_FOCUS_CHANGE_MASK      
GDK_STRUCTURE_MASK         
GDK_PROPERTY_CHANGE_MASK   
GDK_PROXIMITY_IN_MASK      
GDK_PROXIMITY_OUT_MASK     

Il y a quelques points subtils qui doivent être observés lorsqu'on appelle gtk_widget_set_events(). D'abord, elle doit être appelée avant que la fenêtre X d'un widget GTK soit créée. En pratique, cela signifie que l'on doit l'appeler immédiatement après avoir créé le widget. Ensuite, le widget doit avoir une fenêtre X associée. Pour des raisons d'efficacité, de nombreux types de widgets n'ont pas de fenêtre propre, mais se dessinent dans la fenêtre de leur parent. Ces widgets sont :

GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPaned
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkViewport
GtkAspectFrame
GtkFrame
GtkVPaned
GtkHPaned
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator

Pour capturer les événements pour ces widgets, on doit utiliser un widget EventBox. Voir la section sur Le widget EventBox pour plus de détails.

Pour notre programme de dessin, on veut savoir quand le bouton de la souris est pressé et quand la souris est déplacée, nous indiquons donc GDK_POINTER_MOTION_MASK et GDK_BUTTON_PRESS_MASK. On veut aussi savoir quand il est nécessaire de redessiner notre fenêtre, on indique donc GDK_EXPOSURE_MASK. Bien que nous voulions être avertis via un événement Configure lorsque la taille de notre fenêtre change, on n'a pas besoin de préciser le flag GDK_STRUCTURE_MASK correspondant car il est automatiquement spécifié pour chaque fenêtre.

Il arrive cependant qu'il puisse y avoir un problème en indiquant seulement GDK_POINTER_MOTION_MASK. Cela fera que le serveur ajoutera un nouvel événement de déplacement à la file des événements à chaque fois que l'utilisateur déplace la souris. Si cela nous prend 0,1 seconde pour gérer un événement de déplacement, si le serveur X n'ajoute un nouvel événement de déplacement dans la queue que toutes les 0,05 secondes, nous serons vite à la traîne de l'utilisateur. Si l'utilisateur dessine pendant 5 secondes, cela nous prendra 5 secondes de plus pour le traiter après qu'il ait relâché le bouton de la souris ! Ce que l'on voudrait, c'est ne récupérer qu'un événement de déplacement pour chaque événement que l'on traite. Pour cela, il faut préciser GDK_POINTER_MOTION_HINT_MASK.

Avec GDK_POINTER_MOTION_HINT_MASK, le serveur nous envoit un événement de déplacement la première fois que la pointeur se déplace après être entré dans la fenêtre, ou après un événement d'appui ou de relâchement d'un bouton. Les événements de déplacement suivants seront supprimés jusqu'à ce que l'on demande explicitement la position du pointeur en utilisant la fonction :

GdkWindow*    gdk_window_get_pointer     (GdkWindow       *window,
                                          gint            *x,
                                          gint            *y,
                                          GdkModifierType *mask);

(Il existe une autre fonction, gtk_widget_get_pointer() qui possède une interface simple, mais n'est pas très utile car elle ne fait que récupérer la position de la souris et ne se préoccupe pas de savoir si les boutons sont pressés).

Le code pour configurer les événements pour notre fenêtre ressemble alors à :

  gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
                      (GtkSignalFunc) expose_event, NULL);
  gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
                      (GtkSignalFunc) configure_event, NULL);
  gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
                      (GtkSignalFunc) motion_notify_event, NULL);
  gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
                      (GtkSignalFunc) button_press_event, NULL);

  gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
                         | GDK_LEAVE_NOTIFY_MASK
                         | GDK_BUTTON_PRESS_MASK
                         | GDK_POINTER_MOTION_MASK
                         | GDK_POINTER_MOTION_HINT_MASK);

Nous garderons les gestionnaires de "expose_event" et "configure_event" pour plus tard. Les gestionnaires de "motion_notify_event" et "button_press_event" sont très simples :

static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
  if (event->button == 1 && pixmap != NULL)
      draw_brush (widget, event->x, event->y);

  return TRUE;
}

static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
  int x, y;
  GdkModifierType state;

  if (event->is_hint)
    gdk_window_get_pointer (event->window, &x, &y, &state);
  else
    {
      x = event->x;
      y = event->y;
      state = event->state;
    }
    
  if (state & GDK_BUTTON1_MASK && pixmap != NULL)
    draw_brush (widget, x, y);
  
  return TRUE;
}

20.3 Le widget DrawingArea et le dessin

Revenons au processus de dessin sur l'écran. Le widget que l'on utilise pour ceci est le widget DrawingArea. Un tel widget est essentiellement une fenêtre X et rien de plus. Il s'agit d'une toile vide sur laquelle nous pouvons dessiner ce que nous voulons.

Une zone de dessin est créée avec l'appel :

GtkWidget* gtk_drawing_area_new        (void);

Une taille par défaut peut être donnée au widget par l'appel :

void       gtk_drawing_area_size       (GtkDrawingArea      *darea,
                                        gint                 width,
                                        gint                 height);

Cette taille par défaut peu être surchargée en appelant gtk_widget_set_usize() et celle-ci, à son tour, peut être surchargée si l'utilisateur modifie manuellement la taille de la fenêtre contenant la zone de dessin.

Il faut noter que lorsque l'on crée un widget DrawingArea, nous sommes complètement responsable du dessin du contenu. Si notre fenêtre est cachée puis redécouverte, nous recevrons un événement d'exposition et devrons redessiner ce qui a été caché auparavant.

Devoir se rappeler tout ce qui a été dessiné à l'écran pour pouvoir correctement le redessiner peut s'avérer, c'est le moins que l'on puisse dire, pénible. De plus, cela peut être visible si des portions de la fenêtre sont effacées puis redessinées étape par étape. La solution à ce problème est d'utiliser un pixmap d'arrière-plan qui n'est pas sur l'écran. Au lieu de dessiner directement à l'écran, on dessine sur une image stockée dans la mémoire du serveur et qui n'est pas affichée, puis, lorsque l'image change ou lorsque de nouvelles portions de l'image sont affichées, on copie les parties adéquates sur l'écran.

Pour créer un pixmap mémoire, on appelle la fonction :

GdkPixmap* gdk_pixmap_new               (GdkWindow  *window,
                                         gint        width,
                                         gint        height,
                                         gint        depth);

Le paramètre windows indique une fenêtre GTK de laquelle ce pixmap tire certaines de ses propriétés. width et height précisent la taille du pixmap. depth précise la profondeur de couleur, c'est-à-dire le nombre de bits par pixel, de la nouvelle fenêtre. Si cette profondeur vaut -1, elle correspondra à celle de window.

Nous créons le pixmap dans notre gestionnaire "configure_event". Cet événement est généré à chaque fois que la fenêtre change de taille, y compris lorsqu'elle initialement créée.

/* Pixmap d'arrière-plan pour la zone de dessin */
static GdkPixmap *pixmap = NULL;

/* Création d'un nouveau pixmap d'arrière-plan de la taille voulue */
static gint
configure_event (GtkWidget *widget, GdkEventConfigure *event)
{
  if (pixmap)
    {
      gdk_pixmap_destroy(pixmap);
    }
  pixmap = gdk_pixmap_new(widget->window,
                          widget->allocation.width,
                          widget->allocation.height,
                          -1);
  gdk_draw_rectangle (pixmap,
                      widget->style->white_gc,
                      TRUE,
                      0, 0,
                      widget->allocation.width,
                      widget->allocation.height);

  return TRUE;
}

L'appel à gdk_draw_rectangle() remet le pixmap à blanc. Nous en dirons un peu plus dans un moment.

Notre gestionnaire d'événement d'exposition copie alors simplement la partie concernées du pixmap sur l'écran (on détermine la zone qu'il faut redessiner en utilisant le champ event->area de l'événement d'exposition) :

/* Remplit l'écran à partir du pixmap d'arrière-plan */
static gint
expose_event (GtkWidget *widget, GdkEventExpose *event)
{
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
                  pixmap,
                  event->area.x, event->area.y,
                  event->area.x, event->area.y,
                  event->area.width, event->area.height);

  return FALSE;
}

Nous avons vu comment garder l'écran à jour avec notre pixmap, mais comment dessine-t-on réellement ce que l'on veut dans le pixmap ? Il existe un grand nombre d'appels dans la bibliothèque GDK de GTK pour dessiner sur des dessinables. Un dessinable est simplement quelque chose sur lequel on peut dessiner. Cela peut être une fenêtre, un pixmap, ou un bitmap (une image en noir et blanc). Nous avons déjà vu plus haut deux de ces appels, gdk_draw_rectangle() et gdk_draw_pixmap(). La liste complète est :

gdk_draw_line ()
gdk_draw_rectangle ()
gdk_draw_arc ()
gdk_draw_polygon ()
gdk_draw_string ()
gdk_draw_text ()
gdk_draw_pixmap ()
gdk_draw_bitmap ()
gdk_draw_image ()
gdk_draw_points ()
gdk_draw_segments ()

Consultez la documentation de référence ou le fichier en-tête <gdkgdk.h>/ pour plus de détails sur ces fonctions. Celles-ci partagent toutes les mêmes deux paramêtres. Le premier est le dessinable sur lequel dessiner, le second est un contexte graphique (GC).

Un contexte graphique encapsule l'information sur des choses comme les couleurs de premier et d'arrière plan et la largeur de ligne. GDK possède un ensemble complet de fonctions pour créer et manipuler les contextes graphiques, mais, pour simplifier, nous n'utiliserons que les contextes graphiques prédéfinis. Chaque widget a un style associé (qui peut être modifié dans un fichier gtkrc, voir la section sur les fichiers rc de GTK). Celui-ci, entre autres choses, stocke plusieurs contextes graphiques. Quelques exemples d'accès à ces contextes sont :

widget->style->white_gc
widget->style->black_gc
widget->style->fg_gc[GTK_STATE_NORMAL]
widget->style->bg_gc[GTK_WIDGET_STATE(widget)]

Les champs fg_gc, bg_gc, dark_gc et light_gc sont indexés par un paramètre de type GtkStateType qui peut prendre les valeurs :

GTK_STATE_NORMAL,
GTK_STATE_ACTIVE,
GTK_STATE_PRELIGHT,
GTK_STATE_SELECTED,
GTK_STATE_INSENSITIVE

Par exemple, pour GTK_STATE_SELECTED, la couleur de premier plan par défaut est blanc et la couleur d'arrière plan par défaut est bleu foncé.

Notre fonction draw_brush(), qui réalise le dessin à l'écran est alors :

/* Dessine un rectangle à l'écran */
static void
draw_brush (GtkWidget *widget, gdouble x, gdouble y)
{
  GdkRectangle update_rect;

  update_rect.x = x - 5;
  update_rect.y = y - 5;
  update_rect.width = 10;
  update_rect.height = 10;
  gdk_draw_rectangle (pixmap,
                      widget->style->black_gc,
                      TRUE,
                      update_rect.x, update_rect.y,
                      update_rect.width, update_rect.height);
  gtk_widget_draw (widget, &update_rect);
}

Après avoir dessiné le rectangle représentant la brosse sur le pixmap, nous appelons la fonction :

void       gtk_widget_draw                (GtkWidget           *widget,
                                           GdkRectangle        *area);

qui indique à X que la zone donnée par le paramètre area a besoin d'être mise à jour. X génèrera éventuellement un événement d'exposition (en combinant peut-être les zones passés dans plusieurs appels à gtk_widget_draw()) ce qui forcera notre gestionnaire d'événement d'exposition à copier les parties adéquates à l'écran.

Nous avons maintenant couvert entièrement le programme de dessin, sauf quelques détails banals comme la création de la fenêtre principale. Le code source complet est disponible à l'endroit où vous avez obtenu ce didacticiel.

20.4 Ajouter le support XInput

Il est maintenant possible d'acheter des périphériques bon marché, comme les tablettes graphiques qui permettent d'exprimer beaucoup plus facilement son talent qu'avec une souris. La façon la plus simple pour utiliser de tels périphériques est simplement de le faire comme un remplacement de la souris, mais cela ne tire pas partie des nombreux avantages de ces périphériques, comme :

Pour des informations sur l'extension XInput, voir XInput-HOWTO.

Si l'on examine la définition complète de, par exemple, la structure GdkEventMotion, on voit qu'elle possède des champs pour supporter des informations étendues sur les périphériques.

struct _GdkEventMotion
{
  GdkEventType type;
  GdkWindow *window;
  guint32 time;
  gdouble x;
  gdouble y;
  gdouble pressure;
  gdouble xtilt;
  gdouble ytilt;
  guint state;
  gint16 is_hint;
  GdkInputSource source;
  guint32 deviceid;
};

pressure indique la pression comme un nombre réel compris entre 0 et 1. xtilt et ytilt peuvent prendre des valeurs entre -1 et 1, correspondant au degré d'inclinaison dans chaque direction, source et deviceid précisent de deux façons différentes le périphérique pour lequel l'événement est survenus. source donne une simple information sur le type du périphérique. Il peut prendre l'une des valeurs suivantes :

GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR

deviceid précise un ID numérique unique pour le périphérique. Il peut être utilisé pour trouver des informations supplémentaires sur le périphérique en utilisant l'appel gdk_input_list_devices() (voir ci-dessous). La valeur spéciale GDK_CORE_POINTER sert à désigner le périphérique de pointage principal (habituellement la souris).

Valider l'information supplémentaire sur un périphérique

Pour indiquer à GTK que l'on désire obtenir des informations supplémentaires sur le périphérique, on a simplement besoin d'ajouter une ligne à nos programmes.

gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);

En donnant la valeur GDK_EXTENSION_EVENTS_CURSOR, on indique que nous désirons les événements d'extension, mais seulement si l'on ne doit pas dessiner notre propre curseur. Voir la section Sophistications supplémentaires ci-dessous pour des plus de détails sur le dessin du curseur. Nous pourrions aussi donner les valeurs GDK_EXTENSION_EVENTS_ALL si nous voulons dessiner notre propre curseur, ou GDK_EXTENSION_EVENTS_NONE pour revenir à la situation par défaut.

Toutefois, nous ne sommes pas complètement à la fin de l'histoire. Par défaut, aucun périphérique d'extension n'est autorisé. Nous avons besoin d'un mécanisme pour que les utilisateurs puissent autoriser et configurer leur extensions. GTK dispose du widget InputDialog pour automatiser cette tâche. La procédure suivante gère un widget InputDialog. Elle crée le dialogue s'il n'est pas présent et le place au premier plan sinon.

void
input_dialog_destroy (GtkWidget *w, gpointer data)
{
  *((GtkWidget **)data) = NULL;
}

void
create_input_dialog ()
{
  static GtkWidget *inputd = NULL;

  if (!inputd)
    {
      inputd = gtk_input_dialog_new();

      gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
                          (GtkSignalFunc)input_dialog_destroy, &inputd);
      gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
                                 "clicked",
                                 (GtkSignalFunc)gtk_widget_hide,
                                 GTK_OBJECT(inputd));
      gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);

      gtk_widget_show (inputd);
    }
  else
    {
      if (!GTK_WIDGET_MAPPED(inputd))
        gtk_widget_show(inputd);
      else
        gdk_window_raise(inputd->window);
    }
}

(vous pouvez remarquer la façon dont nous gérons ce dialogue. En le connectant au signal "destroy", nous nous assurons que nous ne garderons pas un pointeur sur le dialogue après l'avoir détruit -- cela pourrait provoquer une erreur de segmentation).

InputDialog a deux boutons "Close" et "Save", qui n'ont pas d'actions qui leur sont assignées par défaut. Dans la fonction ci-dessus, nous associons à "Close" le masquage du dialogue et nous cachons le bouton "Save" car nous n'implantons pas la sauvegarde des options XInput dans ce programme.

Utiliser l'information supplémentaire d'un périphérique

Lorsque l'on a validé le périphérique, on peut simplement utiliser l'information supplémentaire des champs des structures d'événement. En fait, il est toujours prident d'utiliser ces informations car ces champs auront des valeurs par défaut judicieuses même lorsque les événements supplémentaires ne sont pas autorisés.

La seule modification consiste à appeler gdk_input_window_get_pointer() au lieu de gdk_window_get_pointer. Cela est nécessaire car gdk_window_get_pointer ne retourne pas l'information supplémentaire.

void gdk_input_window_get_pointer     (GdkWindow       *window,
                                       guint32         deviceid,
                                       gdouble         *x,
                                       gdouble         *y,
                                       gdouble         *pressure,
                                       gdouble         *xtilt,
                                       gdouble         *ytilt,
                                       GdkModifierType *mask);

Lorsque l'on appelle cette fonction, on doit préciser l'ID du périphérique ainsi que la fenêtre. Habituellement, on aura obtenu cet ID par le champ deviceid d'une structure d'événement. Cette fonction retournera une valeur cohérente lorsque les événements ne sont pas autorisés (dans ce cas, event->deviceid aura la valeur GDK_CORE_POINTER).

La structure de base des gestionnaires d'événements de déplacement et de bouton pressé ne change donc pas trop -- il faut juste ajouter le code permettant de traiter l'information supplémentaire.

static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
  print_button_press (event->deviceid);
  
  if (event->button == 1 && pixmap != NULL)
    draw_brush (widget, event->source, event->x, event->y, event->pressure);

  return TRUE;
}

static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
  gdouble x, y;
  gdouble pressure;
  GdkModifierType state;

  if (event->is_hint)
    gdk_input_window_get_pointer (event->window, event->deviceid,
                                  &x, &y, &pressure, NULL, NULL, &state);
  else
    {
      x = event->x;
      y = event->y;
      pressure = event->pressure;
      state = event->state;
    }
    
  if (state & GDK_BUTTON1_MASK && pixmap != NULL)
    draw_brush (widget, event->source, x, y, pressure);
  
  return TRUE;
}

On doit aussi faire quelquechose de cette nouvelle information. Notre nouvelle fonction draw_brush() dessine avec une couleur différente pour chaque event->source et change la taille du pinceau selon la pression.

/* Dessine un rectangle à l'écran, la taille dépend de la pression,
   et la couleur dépend du type de périphérique */
static void
draw_brush (GtkWidget *widget, GdkInputSource source,
            gdouble x, gdouble y, gdouble pressure)
{
  GdkGC *gc;
  GdkRectangle update_rect;

  switch (source)
    {
    case GDK_SOURCE_MOUSE:
      gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
      break;
    case GDK_SOURCE_PEN:
      gc = widget->style->black_gc;
      break;
    case GDK_SOURCE_ERASER:
      gc = widget->style->white_gc;
      break;
    default:
      gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
    }

  update_rect.x = x - 10 * pressure;
  update_rect.y = y - 10 * pressure;
  update_rect.width = 20 * pressure;
  update_rect.height = 20 * pressure;
  gdk_draw_rectangle (pixmap, gc, TRUE,
                      update_rect.x, update_rect.y,
                      update_rect.width, update_rect.height);
  gtk_widget_draw (widget, &update_rect);
}

En savoir plus sur un périphérique

Notre programme affichera le nom du périphérique qui a généré chaque appui de bouton. Pour trouver le nom d'un périphérique, nous appelons la fonction :

GList *gdk_input_list_devices               (void);

qui retourne une GList (un type de liste chaînée de la bibliothèque glib) de structures GdkDeviceInfo. La structure GdkDeviceInfo est définie de la façon suivante :

struct _GdkDeviceInfo
{
  guint32 deviceid;
  gchar *name;
  GdkInputSource source;
  GdkInputMode mode;
  gint has_cursor;
  gint num_axes;
  GdkAxisUse *axes;
  gint num_keys;
  GdkDeviceKey *keys;
};

La plupart de ces champs sont des informations de configuration que l'on peut ignorer sauf si l'on implante la sauvegarde de la configuration XInput. Celui qui nous intéresse est name qui est, tout simplement, le nom que X donne au périphérique. L'autre champ, qui n'est pas une information de configuration, est has_cursor. Si has_cursor est faux, on doit dessiner notre propre curseur, mais puisque nous avons précisé GDK_EXTENSION_EVENTS_CURSOR, nous n'avons pas à nous en préoccuper.

Notre fonction print_button_press() ne fait parcourir la liste retournée jusqu'à trouver une correspondance, puis affiche le nom du périphérique.

static void
print_button_press (guint32 deviceid)
{
  GList *tmp_list;

  /* gdk_input_list_devices retourne une liste interne, nous ne devons donc
    pas la libérer après */
  tmp_list = gdk_input_list_devices();

  while (tmp_list)
    {
      GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;

      if (info->deviceid == deviceid)
        {
          printf("Bouton pressé sur le périphérique '%s'\n", info->name);
          return;
        }

      tmp_list = tmp_list->next;
    }
}

Ceci termine les modifications de notre programme « XInputize ». Comme pour la première version, le code complet est disponible à l'endroit où vous avez obtenu ce didacticiel.

Sophistications supplémentaires

Bien que notre programme supporte maintenant XInput, il y manque des caractéristiques que l'on souhaite trouver dans les applications complètes. D'abord, l'utilisateur ne veut probablement pas avoir à configurer ses périphériques à chaque fois qu'il lance le programme et nous devons donc lui permettre de sauvegarder la configuration du périphérique. Ceci est réalisé en parcourant ce que retourne gdk_input_list_devices() et en écrivant la configuration dans un fichier.

Pour restaurer un état au prochain démarrage du programme, GDK dispose de fonctions pour changer la configuration des périphériques :

gdk_input_set_extension_events()
gdk_input_set_source()
gdk_input_set_mode()
gdk_input_set_axes()
gdk_input_set_key()

(La liste retournée par gdk_input_list_devices() ne doit pas être modifiée directement). Un exemple est donné dans le programme de dessin gsumi (disponible à l'adresse http://www.msc.cornell.edu/~otaylor/gsumi/). De plus, ce serait pratique d'avoir une méthode standard pour faire cela pour toutes les applications. Ceci appartient probablement à un niveau légèrement supérieur à GTK, peut-être dans la bibliothèque GNOME.

Une autre grosse omission que nous avons mentionnée plus haut est l'absence de dessin du curseur. Les plates-formes autres qu'XFree86 n'autorisent pas encore l'utilisation simultanée d'un périphérique comme pointeur de base et comme pointeur d'une application. Lisez le XInput-HOWTO pour plus d'informations là-dessus. Ceci signifie que les applications qui veulent atteindre le plus de monde possible doivent dessiner leur propre curseur.

Une application qui dessine son propre curseur doit faire deux choses : déterminer si le périphérique courant a besoin ou non d'un curseur dessiné et déterminer si le périphérique courant est à proximité (si celui-ci est une tablette de dessin, il est pratique de faire disparaître le curseur lorsque le stylet est en dehors de la tablette. Lorsque le périphérique voit le stylet, on dit qu'il est « à proximité »). La première vérification est faite en recherchant dans la liste des périphériques, comme nous l'avons fait pour obtenir le nom du périphérique. La deuxième est réalisée en choisissant des événements "proximity_out". Une exemple de dessin d'un curseur personnel est donné dans le programme testinput de la distribution GTK.


Page suivante Page précédente Table des matières