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