In questa sezione, creeremo un semplice programma di disegno. Durante questo processo, esamineremo come gestire gli eventi generati dal mouse, come disegnare all'interno di una finestra e come disegnare in modo migliore usando una pixmap di supporto. Dopo averlo creato, lo amplieremo aggiungendo il supporto per i dispositivi XInput, per esempio le tavolette grafiche. Il GTK fornisce delle routine di supporto grazie alle quali risulta piuttosto semplice ottenere informazioni estese, come la pressione o l'inclinazione.
I segnali di GTK che abbiamo discusso finora si riferivano ad azioni di alto livello, ad esempio la selezione di un elemento di un menù. Però, a volte è utile sapere qualcosa su cose che si svolgono a livello più basso livello, come possono essere il movimento del mouse o la pressione di un tasto. Ci sono segnali di GTK anche per questi eventi di basso livello. I gestori di questo tipo di segnali hanno un parametro caratteristico in più, che è il puntatore ad una struttura che contiene informazioni riguardo all'evento. Per esempio, ai gestori di eventi che riguardano dei movimenti, si passa un puntatore ad una struttura GdkEventMotion, che è fatta (in parte) così:
struct _GdkEventMotion
{
GdkEventType type;
GdkWindow *window;
guint32 time;
gdouble x;
gdouble y;
...
guint state;
...
};
type
avrà il valore del tipo di evento, in questo caso
GDK_MOTION_NOTIFY
, window
rappresenta la finestra in cui l'evento
si è verificato. x
e y
forniscono le coordinate dell'evento e
state
specifica lo stato dei modificatori nel momento in cui l'evento
si è verificato (cioè, specifica quali tasti modificatori e tasti del mouse
erano premuti in quel momento). E' un OR bit per bit dei seguenti valori:
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
Come succede per gli altri segnali, per determinare cosa deve accadere in
corrispondenza di un evento, si chiama gtk_signal_connect()
. Ma
è anche necessario far sì che GTK sappia di quali eventi vogliamo essere
informati. A questo fine, chiamiamo la funzione:
void gtk_widget_set_events (GtkWidget *widget, gint events);
Il secondo campo specifica gli eventi che ci interessano. Si tratta dell'OR bit per bit delle costanti che identificano i diversi tipi di eventi. La lista dei tipi di eventi è la seguente:
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
Per chiamare gtk_widget_set_events()
, si devono fare alcune osservazioni
sottili. In primo luogo, la si deve chiamare prima che sia stata creata la
finestra X per il widget GTK. In pratica, ciò significa che la si deve
chiamare subito dopo aver creato il widget. In secondo luogo, il widget
deve avere una finestra X associata. Molti widget, per ragioni di
efficienza, non hanno una propria finetra, e vengono mostrati nella
finestra madre. Questi widget sono:
GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkAspectFrame
GtkFrame
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator
Per catturare degli eventi per questo tipo di widget, si deve fare uso del widget EventBox. Si veda a questo proposito la sezione su The EventBox Widget.
Per il nostro programma di disegno, vogliamo sapere quando il pulsante del
mouse è premuto e quando viene mosso, quindi specificheremo
GDK_POINTER_MOTION_MASK
e GDK_BUTTON_PRESS_MASK
. Vogliamo anche
essere informati su quando è necessario ridisegnare la nostra finestra,
quindi specifichiamo GDK_EXPOSURE_MASK
. Anche se vogliamo essere
avvertiti con un evento ``Configure'' se la dimensione della nostra finestra
cambia, non è necessario specificare il flag GDK_STRUCTURE_MASK
, dal
momento che questo viene specificato automaticamente per tutte le finestre.
Risulta, conunque, che specificando semplicemente GDK_POINTER_MOTION_MASK
si crea un problema. Ciò infatti fa sì che il server aggiunga nella coda un
un nuovo evento di movimento ogni volta che l'utente muovoe il mouse. Immaginate
che ci vogliano 0.1 secondi per gestire uno di questi eventi, e che il server
X metta in coda un nuovo evento ogni 0.05 secondi. Rimarremo ben presto indietro
rispetto al disegno dell'utente. Se l'utente disegna per 5 secondi, ci metteremmo
altri 5 secondi prima di finire dopo che l'utente ha rilasciato il pulsante del
mouse! Vorremmo quindi che venga notificato un solo evento di movimento per
ogni evento che processiamo. Il modo per farlo è di specificare
GDK_POINTER_MOTION_HINT_MASK
.
Quando specifichiamo GDK_POINTER_MOTION_HINT_MASK
, il server ci notifica
un evento di movimento la prima volta che il puntatore si muove dopo essere
entrato nella nostra finestra, oppure dopo ogni rilascio di un pulsante del
mouse. Gli altri eventi di movimento verranno soppressi finché non richiediamo
esplicitamente la posizione del puntatore con la funzione:
GdkWindow* gdk_window_get_pointer (GdkWindow *window,
gint *x,
gint *y,
GdkModifierType *mask);
(c'è anche un'altra funzione, gtk_widget_get_pointer()
, che ha
un'interfaccia più semplice, ma che non risulta molto utile dal momento
che restituisce solo la posizione del puntatore, senza dettagli sullo
sato dei pulsanti.)
Quindi, il codice per assegnare gli eventi per la nostra finestra, avrà l'aspetto:
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);
Teniamo per dopo i gestori di ``expose_event'' e ``configure_event''. Quelli di ``motion_notify_event'' e ``button_press_event'' sono piuttosto semplici:
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;
}
Vediamo ora il procedimento per disegnare sullo schermo. Il widget da usare è l'Area di Disegno (DrawingArea). Essenzialmente si tratta di una finestra X e nient'altro. E' una tela bianca su cui possimo disegnare tutto quello che vogliamo. Per crearne una usiamo la chiamata:
GtkWidget* gtk_drawing_area_new (void);
Per specificare una dimensione predefinita, si puo fare:
void gtk_drawing_area_size (GtkDrawingArea *darea,
gint width,
gint height);
Come è vero per tutti i widget, si può modificare questa dimensione
predefinita, tramite la chamata a gtk_widget_set_usize()
, e
questa a sua volta può essere modificata dall'utente ridimensionando
manualmente la finestra che contiene l'area di disegno.
Si deve notare che nel momento in cui creiamo un widget DrawingArea, siamo completamente responsabili di disegnarne il contenuto. Se ad esempio la nostra finestra viene prima nascosta e poi dinuovo portata in primo piano, otteniamo un evento di ``esposizione'' e doppiamo ridisegnare ciò che era stato precedente nascosto.
Dover ricordare tutto quello che era disegnato sulla finestra in modo da poterlo ridisegnare successivamente, può essere, come minimo, noioso. In più, può essere spiacevole dal punto di vista visivo, se delle porzioni dello schermo vengono prima cancellate e poi ridisegnate passo per passo. La soluzione per questo problema è di usare una pixmap di supporto. Invece di disegnare direttamente sullo schermo, disegnamo su un'iimagine conservata nella memoria del server ma che non viene mostrata; quindi, quando l'immagine cambia o ne vengono mostrate nuove porzioni, copiamo sullo schermo le parti corrispondenti.
Per creare una ppixmap fuori dallo schermo, usiamo la funzione:
GdkPixmap* gdk_pixmap_new (GdkWindow *window,
gint width,
gint height,
gint depth);
Il parametro window
specifica una finestra GDK dalla quale questa
pixmap prende alcune delle sue proprietà. width
e height
specificano le dimensioni della pixmap. depth
specifica la
profondità di colore, cioè il numero di bit per ogni pixel, per
la nuova pixmap. Se alla profondità è assegnato il valore -1
, questa
verrà posta identica a quella di window
.
Creiamo la pixmap all'interno del gestore di ``configure_event''. Questo evento è generato ogni volta che la finestra cambia di dimensione, compreso il momento in cui viene creata per la prima volta.
/* Pixmap di supporto per l'area di disegno */
static GdkPixmap *pixmap = NULL;
/* Creare una pixmap della dimensione appropriata */
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;
}
La chiamata a gdk_draw_rectangle()
inizialmente rende bianca l'intera
pixmap. Fra un momento ne riparleremo.
Il gestore dell'evento ``esposizione'', copia quindi la porzione appropriata della pixmap sullo schermo (determiniamo qual è l'area da ridisegnare usando il campo event->area dell'evento di esposizione):
/* Ridisegna sullo schermo a partire dalla pixmap di supporto */
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;
}
Abbiamo quindi visto come tenete aggiornato lo schermo con la nostra
pixmap, ma come facciamo per disegnare delle cose interessanti sulla
pixmap? Ci sono un bel po' di funzioni nella libreria GDK di GTK che
servono per disegnare su superfici disegnabili. Una superficie
disegnabile è semplicemente qualcosa su cui si può disegnare un'immagine.
Può essere una finestra, una pixmap o una bitmap (un'immagine in bianco e
nero). Abbiamo già visto sopra due di chiamate,
gdk_draw_rectangle()
and gdk_draw_pixmap()
. La lista
completa è la seguente:
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 ()
Per ulteriori dettagli su queste funzioni, vedete la documentazione di
riferimento nei file header <gdk/gdk.h>
.
Tutte queste funzioni hanno i medesimi primi due argomenti. Il primo
è la superficie disegnabili su cui disegnare, il secondo è un
contesto grafico (GC).
Un contesto grafico incapsula delle informazioni riguardo a cose come il colore di sfondo e di primo piano e lo spessore della linea. GDK ha un ampio insieme di funzioni per crare e modificare contesti grafici, ma per tenere le cose semplici useremo solo dei contesti grafici predefiniti. Ogni widget ha uno stile associato (che può essere modificato agendo su un file gtkrc). Questo, fra le altre cose, contiene un certo numero di contesti grafici. Alcuni esempi di come accedere a questi contesti grafici sono i seguenti:
widget->style->white_gc
widget->style->black_gc
widget->style->fg_gc[GTK_STATE_NORMAL]
widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
I campi fg_gc
, bg_gc
, dark_gc
, e
light_gc
sono indicizzati tramite un parametri di tipo
GtkStateType
, che può assumere i valori:
GTK_STATE_NORMAL,
GTK_STATE_ACTIVE,
GTK_STATE_PRELIGHT,
GTK_STATE_SELECTED,
GTK_STATE_INSENSITIVE
Per esempio, per GTK_STATE_SELECTED
il colore di sfondo predefinito
è blu scuro e quello di primo piano bianco.
La nostra funzione draw_brush()
, che efettivamente disegna sullo
schermo, diventa quindi:
/* Disegna un rettangolo sullo schermo */
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);
}
Dopo aver disegnato il rettangolo sulla pixmap, chiamiamo la funzione:
void gtk_widget_draw (GtkWidget *widget,
GdkRectangle *area);
che notifica a X che l'area data dal parametro area
deve essere
aggiornata. X poi genererà un evento di esposizione (che può essere combinato
con le aree passate da diverse chiamate a gtk_widget_draw()
) che
farà sì che il nostro gestore dell'evento di esposizione, copi le porzioni
rilevanti sullo schermo.
Abbiamo a questo punto creato tutto il programma di disegno, tranne che per qualche dettaglio irrilevante come la creazione della finestra principale. Il codice sorgente completo è reperibile dove avete ottenuto questo tutorial, oppure da:
http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial
Al giorno d'oggi è possibile acquistare dei dispositivi abbastanza a buon mercato, come tavolette grafice, che permettono di disegnare con una espressività artistica molto semplificata rispetto ad un mouse. Il modo più semplice per usare questi dispositivi è di sostituirli semplicemente al mouse, ma in questo modo si perdono molti dei loro vantaggi, come:
Per ulteriori informazioni sulle estensioni XInput, vedere l' XInput-HOWTO.
Se esaminiamo, per esempio, la definizione completa della struttura GdkEventMotion, possiamo vedere che contiene dei campi per il supporto delle informazioni estese dai dispositivi.
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
fornisce la pressione sotto forma di un numero decimale
compreso fra 0 e 1. xtilt
e ytilt
possono assumere valori
compresi fra -1 e 1, corrispondenti al grado di inclinazione in ciascuna
direzione. source
e deviceid
specificano il dispositivo per il
quale si è verificato l'evento in due modi distinti. source
da alcune
semplici informazioni sul tipo di dispositivo, e può assumere i valori:
GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR
deviceid
specifica invece un identificativo numerico univoco per il
dispositivo. Questo può essere a sua volta utilizzato per avere ulteriori
informazioni sul dispositivo tramite la chiamata a gdk_input_list_devices()
(vedi sotto). Il valore speciale GDK_CORE_POINTER
viene usato per identificare
il dispositivo di puntamento principale (di solito il mouse).
Per far sì che GTK sappia che ci interessano le informazioni estese dai dispositivi, basta aggiungere un'unica linea al nostro programma:
gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
Dando il valore GDK_EXTENSION_EVENTS_CURSOR
, diciamo che ci interessano
gli eventi relativi alle estensioni, ma solo se non dobbiamo disegnare da noi
il nostro cursore. Si veda più sotto alla sezione
Ulteriori Sofisticazioni per ulteriori
informazioni sul modo si disegnare i cursori. Potremmo anche dare i valori
GDK_EXTENSION_EVENTS_ALL
se vogliamo disegnare il nostro cursore o
GDK_EXTENSION_EVENTS_NONE
se vogliamo tornare alle condizioni predefinite.
Comunque, non finisce tutto qui. Non ci sono estensioni abilitate per difetto. Abbiamo bisogno di un meccanismo per permettere agli utenti l'abilitazione e la configurazione delle estensioni dei loro dispositivi, GTK fornisce il widget InputDialog per automatizzare questo processo. La seguente procedura mostra come gestire un widget InputDialog. Crea la finestra di dialogo nel caso non sia presente, mentre la porta in primo piano in 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);
}
}
(Notate come gestiamo questo dialogo. Con la connessione del segnale ``destroy'' ci assicuriamo di non tenerci in giro il puntatore al dialogo dopo che lo abbiamo distrutto, cosa che potrebbe portare ad un errore di segmentazione.)
L'InputDialog ha due pulsanti, ``Close'' e ``Save'', i quali non hanno alcuna azione predefinita assegnata ad essi. Nella funzione precedente, abbiamo fatto in modo che ``Close'' nasconda la finestra di dialogo, e abbiamo nascosto il pulsante ``Save'' dal momento che in questo programma non implementiamo il salvataggio delle opzioni di XInput.
Una volta abilitato il dipositivo, possiamo usare le informazioni estese che si trovano nei corrispondenti campi delle strutture che descrivono gli eventi. A dire il vero, l'utilizzo di questi campi è sempre sicuro, perché sono tutti posti per difetto a valori ragionevoli ancje quando la gestione degli eventi estesi non è abilitata.
Un cambiamento che dobbiamo fare è di chiamare gdk_input_window_get_pointer()
invece di gdk_window_get_pointer
. Ciò si rende necessario perché
gdk_window_get_pointer
non restituisce le informazioni esetese.
void gdk_input_window_get_pointer (GdkWindow *window,
guint32 deviceid,
gdouble *x,
gdouble *y,
gdouble *pressure,
gdouble *xtilt,
gdouble *ytilt,
GdkModifierType *mask);
Quando chiamiamo questa funzione, dobbiamo specificare l'identificativo
del dispositivo e la finestra. Normalmente questo identificativo lo si
ottiene dal campo deviceid
della struttura dell'evento.
Questa funzione restituirà valori ragionevoli nel caso che la gestione
degli eventi estesi non sia attivata (in questo caso, event->deviceid
avrà il valore GDK_CORE_POINTER
).
Quindi, la struttura di base dei gestori degli eventi relativi alla pressione di bottoni e ai movomenti non cambia molto - abbiamo solo bisogno di aggiungere il codice necessario per tenere conto delle informazioni estese.
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;
}
Avremo anche bisogno di fare qualcosa con queste nuove informazioni. La
nostra nuova funzione draw_brush
disegna con un colore diverso per
ogni event->source
e cambia la dimensione della linea in funzione
della pressione.
/* Disegna un rettangolo sullo schermo, con la dimensione dipendente
dalla pressione e il colore dipendente dal tipo di dispositivo */
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);
}
Come esempio del modo di trovare altre informazioni su di un dispositivo, il nostro programma stamperà il nome di ogni dispositivo che genera un evento di pressione di un pulsante. Per avere il nome di un dispositivo, chiamiamo la funzione
GList *gdk_input_list_devices (void);
che restituisce una GList (un tipo di lista collegata che si trova nella libreria glib) di strutture di tipo GdkDeviceInfo. La definizione di GdkDeviceInfo è la seguente:
struct _GdkDeviceInfo
{
guint32 deviceid;
gchar *name;
GdkInputSource source;
GdkInputMode mode;
gint has_cursor;
gint num_axes;
GdkAxisUse *axes;
gint num_keys;
GdkDeviceKey *keys;
};
La maggior parte di questi campi rappresentano informazioni di configurazione
che potete ignorare a meno che non implementiate il salvataggio della
configurazione di un XInput. Quelle che ci interessano sono name
, che
è semplicemente il nome che X assegna al dispositivo, e has_cursor
. Anche
has_cursor
non è informazione di configurazione, e indica, nel caso
abbia valore ``falso'', che dobbiamo disegnare da soli il nostro cursore. Ma
dal momento che abbiamo specificato GDK_EXTENSION_EVENTS_CURSOR
,
possiamo anche non preoccuparcene.
La nostra funzione print_button_press()
scorre semplicemente la lista
che è stata restituita finché non trova il valore corretto, e poi stampa
il nome del dispositivo.
static void
print_button_press (guint32 deviceid)
{
GList *tmp_list;
/* gdk_input_list_devices restituisce una lista interna, così poi
non dobbiamo liberarla */
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;
}
}
Questo completa i cambiamenti necessari per usare gli XInput nel nostro
programma. Come per la prima versione, i sorgenti completi sono prelevabili
da dove avete prelevato questo tutorial, oppure da:
http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial
Anche se ora il nostro programma supporta XInput pittosto bene, gli mancano
alcune caratteristiche che probabilmente vorremmo mettere in una applicazione
completa. In primo luogo, probabilmente all'utente non farà piacere dover
configurare i propri dispositivi ogni volta che lanciano il programma, per
cui dovremmo dare la possibilità di salvare la configurazione dei dispositivi.
Ciò può essere fatto scorrendo la lista restituita da gdk_input_list_devices()
e scrivendo la configurazione su di un file.
Per tornare allo stato salvato la prossima volta che il programma viene eseguito, GDK mette a disposizione delle funzioni per cambiare la configurazione dei dispositivi:
gdk_input_set_extension_events()
gdk_input_set_source()
gdk_input_set_mode()
gdk_input_set_axes()
gdk_input_set_key()
(La lista restituita da gdk_input_list_devices()
non dovrebbe
essere modificata direttamente.) Un esempio di come fare può essere
trovato nel programma di disegno gsumi (disponibile da
http://www.msc.cornell.edu/~otaylor/gsumi/). Sarebbe bello
avere alla fine un modo standard di recuperare le informazioni per tutte
le applicazioni. Questo probabilmente appartiene ad un livello un po'
più elevato ripetto a GTK, forse alla libreria GNOME.
Un'altra notevole omissione a cui abbiamo accennato precedentemente è il fatto di non disegnare il cursore direttamente. Piattaforme diverse da XFree86 non permettono in questo momento di usare contemporaneamente un dispositivo sia come puntatore principale sia direttamente da una applicazione. Vedere XInput-HOWTO per ulteriori informazioni. Ciò significa che le applicazioni che vogliono rivolgersi al pubblico più ampio dovranno prevedere di disegnare esse stesse il proprio cursore.
Un'applicazione che voglia disegnare il proprio cursore dovrà fare due cose: determinare se il dispositivo corrente necessita che venga disegnato un cursore, e determinare se il dispositivo corrente è in prossimità. (Se il dispositivo è una tavoletta grafica, un tocco di finezza è fare sparire il puntatore quando lo stilo viene sollevato dalla tavoletta. Quando c'è contatto fra lo stilo e la tavoletta, si dice che il dispositivo è ``in prossimità".) La prima cosa viene fatta scorrendo la lista dei dispositivi, come abbiamo fatto per trovare il nome del dispositivo. La seconda cosa viene ottenuta selezionando gli eventi ``proximity_out''. Un esempio di disegno del proprio cursore si trova nel programma 'testinput' incluso nella distribuzione di GTK.