Página siguiente Página anterior Índice general

25. Scribble, un sencillo programa de dibujo de ejemplo

25.1 Objetivos

En esta sección, vamos a crear un sencillo programa de dibujo. En el proceso, vamos a examinar como se manejan los eventos de ratón, como dibujar en una ventana, y como mejorar el dibujado utilizando un pixmap intermedio. Después de crear el programa de dibujo, lo ampliaremos añadiendole la posibilidad de utilizar dispositivos XInput, como tabletas digitalizadoras. GTK proporciona las rutinas que nos darán la posibilidad de obtener información extra, como la presión y la inclinación, de todo tipo de dispositivos de una forma sencilla.

25.2 Manejo de eventos

Las señales GTK sobre las que ya hemos discutido son para las acciones de alto nivel, como cuando se selecciona un elemento de un menú. Sin embargo a veces es útil tratar con los acontecimientos a bajo nivel, como cuando se mueve el ratón, o cuando se está presionando una tecla. También hay señales GTK relacionadas con estos eventos de bajo nivel. Los manejadores de estas señales tienen un parámetro extra que es un puntero a una estructura conteniendo información sobre el evento. Por ejemplo, a los manejadores de los eventos de movimiento se les pasa una estructura GdkEventMotion que es (en parte) así:

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

type adquirirá su valor adecuado dependiendo del tipo de evento, en nuestro caso GDK_MOTION_NOTIFY, window es la ventana en la que ocurre el evento. x e y dan las coordenadas del evento, y state especifica cual es la modificación que ha habido cuando ocurrió el evento (esto es, especifica que teclas han cambiado su estado y que botones del ratón se han presionado.) Es la operación OR (O) de algunos de los siguientes valores:

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

Como con las otras señales, para especificar que es lo que pasa cuando ocurre un evento, llamaremos a gtk_signal_connect(). Pero también necesitamos decirle a GTK sobre que eventos queremos ser informados. Para ello, llamaremos a la función:

void gtk_widget_set_events (GtkWidget *widget,
                            gint      events);

El segundo campo especifica los eventos en los que estamos interesados. Es el OR (O) de las constantes que especifican los diferentes tipos de eventos. Por las referencias futuras que podamos hacer, presentamos aquí los tipos de eventos que hay disponibles:

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     

Hay unos cuantas sutilezas que debemos respetar cuando llamamos a gtk_widget_set_events(). Primero, debemos llamar a esta función antes de que se cree la ventana X para el widget GTK. En términos prácticos, significa que debemos llamarla inmediatamente después de crear el widget. Segundo, el widget debe tener una ventana X asociado. Por motivos de eficiencia, hay muchos widgets que no tienen su propia ventana, sino que dibujan en la de su padre. Estos widgets son:

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

Para capturar eventos para estos widgets, necesita utilizar un widget EventBox. Vea la sección El widget EventBox para más detalles.

Para nuestro programa de dibujo, queremos saber cuando se presiona el botón del ratón y cuando se mueve, por lo que debemos especificar los eventos GDK_POINTER_MOTION_MASK y GDK_BUTTON_PRESS_MASK. También queremos saber cuando necesitamos redibujar nuestra ventana, por lo que especificaremos el evento GDK_EXPOSURE_MASK. Aunque queremos estar informados mediante un evento Configure cuando cambie el tamaño de nuestra ventana, no tenemos que especificar la correspondiente GDK_STRUCTURE_MASK, porque ya está activada por defecto para todas las ventanas.

Tenemos un problema con lo que acabamos de hacer, y tiene que ver con la utilización de GDK_POINTER_MOTION_MASK. Si especificamos este evento, el servidor añadirá un evento de movimiento a la cola de eventos cada vez que el usuario mueva el ratón. Imagine que nos cuesta 0'1 segundo tratar el evento de movimiento, pero que el servidor X añade a la cola un nuevo evento de moviento cada 0'05 segundos. Pronto nos iremos quedando retrasados con respecto al resto de los eventos. Si el usuario dibuja durante 5 segundos, ¡nos llevará otros 5 segundos el cazarle después de que hay levantado el botón del ratón! Lo que queremos es sólo un evento de movimiento por cada evento que procesemos. La manera de hacerlo es especificando GDK_POINTER_MOTION_HINT_MASK.

Cuando especificamos GDK_POINTER_MOTION_HINT_MASK, el servidor nos envia un evento de movimiento la primera ver que el puntero se mueve depués de entrar en nuestra ventana, o después de que se apriete o se suelte un botón (y se reciba el evento correspondiente). Los eventos de movimiento restantes se eliminarán a no ser que preguntemos especificamente por la posición del puntero utilizando la función:

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

(Hay otra función, gtk_widget_get_pointer() que tiene una interfaz más sencillo, pero esta simplificación le resta utilidad, ya que sólo devuelve la posición del ratón, y no si alguno de sus botones está presionado.)

El código para establecer los eventos para nuestra ventana es el siguiente:

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

Vamos a dejar los manejadores de los eventos expose_event y configure_event para después. Los manejadores de motion_notify_event y de button_press_event son bastante 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;
}

25.3 El widget DrawingArea, y dibujando

Vamos a pasar al proceso de dibujar en la pantalla. El widget que utilizaremos será el DrawingArea. Un widget DrawingArea es esencialmente una ventana X y nada más. Es un lienzo en blanco en el que podemos dibujar lo que queramos. Crearemos un área de dibujo utilizando la llamada:

GtkWidget* gtk_drawing_area_new        (void);

Se puede especificar un tamaño por defecto para el widget llamando a:

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

Se puede cambiar el tamaño por defecto, como para todos los widgets, llamando a gtk_widget_set_usize(), y esto, además, puede cambiarse si el usuario cambia manualmente el tamaño de la ventana que contiene el área de dibujo.

Debemos hacer notar que cuando creamos un widget DrawingArea, seremos completamente responsables de dibujar su contenido. Si nuestra ventana se tapa y se vuelve a poner al descubierto, obtendremos un evento de exposición y deberemos redibujar lo que se había tapado.

Tener que recordar todo lo que se dibujó en la pantalla para que podamos redibujarla convenientemente es, por decirlo de alguna manera suave, una locura. Además puede quedar mal si hay que borrar partes de la pantalla y hay que redibujarlas paso a paso. La solución a este problema es utilizar un pixmap intermedio. En lugar de dibujar directamente en la pantalla, dibujaremos en una imagen que estará almacenada en la memoria del servidor, pero que no se mostrará, y cuando cambie la imagen o se muestren nuevas partes de la misma, copiaremos las porciones relevantes en la pantalla.

Para crear un pixmap intermedio, llamaremos a la función:

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

El parámetro widget especifica una ventana GDK de las que este pixmap tomará algunas propiedades. width y height especifican el tamaño del pixmap. depth especifica la profundidad del color, que es el número de bits por pixel de la nueva ventana. Si la profundidad que se especifica es -1, se utilizará la misma profundidad de color que tenga la ventana.

Creamos nuestro pixmap en nuestro manejador del evento configure_event. Este evento se genera cada vez que cambia el tamaño de la ventana, incluyendo cuando ésta se crea.

/* Backing pixmap for drawing area */
static GdkPixmap *pixmap = NULL;

/* Create a new backing pixmap of the appropriate size */
static gint
configure_event (GtkWidget *widget, GdkEventConfigure *event)
{
  if (pixmap)
    gdk_pixmap_unref(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;
}

La llamada a gdk_draw_rectangle() rellena todo el pixmap de blanco. Hablaremos más de todo esto en un momento.

Nuestro manejador del evento de exposición simplemente copia la porción relevante del pixmap en la pantalla (determinaremos la zona a redibujar utilizando el campo event->area del evento de exposición):

/* Redraw the screen from the backing pixmap */
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;
}

Ahora ya sabemos como mantener la pantalla actualizada con el contenido de nuestro pixmap, pero ¿cómo podemos dibujar algo interesante en nuestro pixmap? Hay un gran número de llamadas en la biblioteca GDK para dibujar en los dibujables. Un dibujable es simplemente algo sobre lo que se puede dibujar. Puede ser una ventana, un pixmap, un bitmap (una imagen en blanco y negro), etc. Ya hemos visto arriba dos de estas llamadas, gdk_draw_rectangle() y gdk_draw_pixmap(). La lista completa de funciones para dibujar es:

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 ()

Ver la documentación de estas funciones o el fichero de cabecera <gdk/gdk.h> para obtener más detalles sobre estas funciones. Todas comparten los dos primeros argumentos. El primero es el dibujable en el que se dibujará, y el segundo argumento es un contexto gráfico (GC).

Un contexto gráfico reúne la información sobre cosas como el color de fondo y del color de lo que se dibuja, el ancho de la línea, etc... GDK tiene un conjunto completo de funciones para crear y modificar los contextos gráficos. Cada widget tiene un GC asociado. (Que puede modificarse en un fichero gtkrc, ver la sección ``Ficheros rc de GTK''.) Estos, junto con otras cosas, almacenan GC's. Algunos ejemplos de como acceder a estos GC's son:

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

Los campos fg_gc, bg_gc, dark_gc, y light_gc se indexan con un parámetro del tipo GtkStateType que puede tomar uno de los valores:

GTK_STATE_NORMAL,
GTK_STATE_ACTIVE,
GTK_STATE_PRELIGHT,
GTK_STATE_SELECTED,
GTK_STATE_INSENSITIVE

Por ejemplo, para el GTK_STATE_SELECTED, el color que se utiliza para pintar por defecto es el blanco y el color del fondo por defecto, es el azul oscuro.

Nuestra función draw_brush(), que es la que dibuja en la pantalla, será la siguiente:

/* Draw a rectangle on the screen */
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);
}

Después de que dibujemos el rectángulo representando la brocha en el pixmap llamaremos a la función:

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

que le informa a X de que la zona dada por el parámetro area necesita actualizarse. X generará un evento de exposición (combinando posiblemente distintas zonas pasadas mediante distintas llamadas a gtk_widget_draw()) que hará que nuestro manejador de eventos de exposición copie las porciones relevantes en la pantalla.

Ya hemos cubierto el programa de dibujo completo, excepto unos cuantos detalles mundanos como crear la ventana principal. El código completo está disponible en el mismo lugar en el que consiguió este tutorial, o en:

http://www.gtk.org/~otaylor/gtk/tutorial/

25.4 Añadiendo la capacidad de utilizar XInput

Ahora es posible comprar dispositos de entrada bastante baratos, como tabletas digitalizadoras, que permiten dibujar de forma artística mucho más fácilmente de cómo lo haríamos con un ratón. La forma más sencilla de utilizar estos dispositivos es simplemente reemplazando a los ratones, pero así perdemos muchas de las ventajas de este tipo de dispositivos, como por ejemplo:

Para información sobre la extensión XInput, ver el XInput-HOWTO.

Si examinamos la definición completa de, por ejemplo, la estructura GdkEventMotion, veremos que tiene campos para almacenar la información de los dispositivos extendidos.

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 da la presión como un número de coma flotante entre 0 y 1. xtilt e ytilt pueden tomar valores entre -1 y 1, correspondiendo al grado de inclinación en cada dirección. source y deviceid especifican el dispositivo para el que ocurre el evento de dos maneras diferentes. source da alguna información simple sobre el tipo de dispositivo. Puede tomar los valores de la enumeración siguiente:

GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR

deviceid especifica un número único ID para el dispositivo. Puede utilizarse para obtener más información sobre el dispositivo utilizando la función gdk_input_list_devices() (ver abajo). El valor especial GDK_CORE_POINTER se utiliza para el núcleo del dispositivo apuntador. (Normalmente el ratón.)

Activando la información del dispositivo extendido

Para informar a GTK de nuestro interés en la información sobre los dispositivos extendidos, sólo tenemos que añadirle una línea a nuestro programa:

gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);

Dando el valor GDK_EXTENSION_EVENTS_CURSOR decimos que estamos interesados en los eventos de extensión, pero sólo si no tenemos que dibujar nuestro propio cursor. Ver la sección Sofisticaciones adicionales más abajo para obtener más información sobre el dibujado del cursor. También podríamos dar los valores GDK_EXTENSION_EVENTS_ALL si queremos dibujar nuestro propio cursor, o GDK_EXTENSION_EVENTS_NONE para volver al estado inicial.

Todavía no hemos llegado al final de la historia. Por defecto, no hay ningún dispositivo extra activado. Necesitamos un mecanismo que permita a los usuarios activar y configurar sus dispositivos extra. GTK proporciona el widget InputDialog para automatizar el proceso. El siguiente procedimiento utiliza el widget InputDialog. Crea el cuadro de diálogo si no ha sido ya creado, y lo pone en primer plano en caso contrario.

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);
    }
}

(Tome nota de la manera en que hemos manejado el cuadro de diálogo. Conectando la señal destroy, nos aseguramos de que no tendremos un puntero al cuadro de diálogo después de que haya sido destruido, lo que nos podría llevar a un segfault.)

El InputDialog tiene dos botones ``Cerrar'' y ``Guardar'', que por defecto no tienen ninguna acción asignada. En la función anterior hemos hecho que ``Cerrar'' oculte el cuadro de diálogo, ocultando el botón ``Guardar'', ya que no implementaremos en este programa la acción de guardar las opciones de XInput.

Utilizando la información de los dispositivos extras

Una vez hemos activado el dispositivo, podemos utilizar la información que hay respecto a los dispositivos extendidos en los campos extras de las estructuras de los eventos. De hecho, es bueno utilizar esa información ya que esos campos tienen unos valores por defecto razonables aún cuando no se activen los eventos extendidos.

Un cambio que tenemos que hacer es llamar a gdk_input_window_get_pointer() en vez de a gdk_window_get_pointer. Esto es necesario porque gdk_window_get_pointer no devuelve la información de los dispositivos extra.

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

Cuando llamamos a esta función, necesitamos especificar tanto el ID del dispositivo como la ventana. Normalmente, obtendremos el ID del dispositivo del campo deviceid de una estructura de evento. De nuevo, esta función devolverá valores razonables cuando no estén activados los eventos extendidos. (En ese caso, event->deviceid tendrá el valor GDK_CORE_POINTER).

Por tanto la estructura básica de nuestros manejadores de los eventos de movimiento y de pulsación del botón del ratón no cambiarán mucho - sólo tenemos que añadir código para manejar la información extra.

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;
}

También tenemos que hacer algo con la nueva información. Nuestra nueva función draw_brush() dibuja con un color diferente dependiendo de event->source y cambia el tamaño de la brocha dependiendo de la presión.

/* Draw a rectangle on the screen, size depending on pressure,
   and color on the type of device */
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);
}

Obteniendo más información de un dispositivo

Como ejemplo de como podemos obtener más información de un dispositivo, nuestro programa imprimirá el nombre del dispositivo que genera cada pulsación de botón. Para encontrar el nombre de un dispositivo, llamaremos a la función:

GList *gdk_input_list_devices               (void);

que devuelve una GList (una lista enlazada de la biblioteca glib) de estructuras GdkDeviceInfo. La estructura GdkDeviceInfo se define como:

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

Muchos de estos campos son información de configuración que puede ignorar, a menos que quiera permitir la opción de grabar la configuración de XInput. El campo que nos interesa ahora es name que es simplemente el nombre que X le asigna al dispositivo. El otro campo que no tiene información sobre la configuración es has_cursor. Si has_cursor es falso, tendremos que dibujar nuestro propio cursor. Pero como hemos especificado GDK_EXTENSION_EVENTS_CURSOR, no tendremos que preocuparnos por esto.

Nuestra función print_button_press() simplemente recorre la lista devuelta hasta que encuentra una coincidencia, y entonces imprime el nombre del dispositivo.

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

  /* gdk_input_list_devices returns an internal list, so we shouldn't
     free it afterwards */
  tmp_list = gdk_input_list_devices();

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

      if (info->deviceid == deviceid)
        {
          printf("Button press on device '%s'\n", info->name);
          return;
        }

      tmp_list = tmp_list->next;
    }
}

Con esto hemos completado los cambios para `XInputizar' nuestro programa. Como ocurria con la primera versión, el código completo se encuentra disponible en el mismo sitio donde obtuvo éste tutorial, o desde:

http://www.gtk.org/~otaylor/gtk/tutorial/

Sofisticaciones adicionales

Aunque ahora nuestro programa admite XInput bastante bien, todavía falla en algunas características que deberían estar disponibles en una aplicación bien hecha. Primero, el usuario no debería tener que configurar su dispositivo cada vez que ejecute el programa, por lo que debería estar disponible la opción de guardar la configuración del dispositivo. Esto se hace recorriendo el resultado de gdk_input_list_devices() y escribiendo la configuración en un fichero.

Para cargar la configuración del dispositivo cuando se vuelva a ejecutar el programa, puede utilizar las funciones que proporciona GDK para cambiar la configuración de los dispositivos:

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

(La lista devuelta por gdk_input_list_devices() no debería modificarse directamente.) Podemos encontrar un ejemplo de como debe utilizarse en el programa de dibujo gsumi. (Disponible en http://www.msc.cornell.edu/~otaylor/gsumi/) Estaría bien tener un procedimiento estándar para poder hacer todo esto en cualquier aplicaciones. Probablemente se llegue a esto en una capa superior a GTK, quizás en la biblioteca GNOME.

El programa tiene otra carencia importante que ya hemos mencionado más arriba, y es la falta del cursor. Ninguna plataforma distinta de XFree86 permite utilizar simultaneamente un dispositivo como puntero núcleo y como dispositivo directamente utilizable por una aplicación. Ver el XInput-HOWTO para más información sobre esto. Con esto queremos decir que si quiere tener la máxima audiencia necesita dibujar su propio cursor.

Una aplicación que dibuja su propio cursor necesita hacer dos cosas: determinar si el dispositivo actual necesita que se dibuje un cursor o no, y determinar si el dispositivo está ``próximo''. (Si el dispositivo es una tableta digitalizadora, queda muy bonito que el cursor desaparezca cuando el lápiz se separa de la tableta. Cuando el lápiz está tocando la tableta, se dice que el dispositivo está ``próximo.'') Lo primero se hace buscando la lista de dispositivos, tal y como hicimos para encontrar el nombre del dispositivo. Lo segundo se consigue seleccionando los eventos proximity_out. Podemos encontrar un ejemplo de como dibujar nuestro propio cursor en el programa `testinput' que viene con la distribución de GTK.


Página siguiente Página anterior Índice general