GTK Tutorial: Scribble, Un semplice esempio di Programma di Disegno

Avanti Indietro Indice

## 20. Scribble, Un semplice esempio di Programma di Disegno

## 20.1 Panoramica

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.

## 20.2 Gestione degli Eventi

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

> `

## 20.3 Il widget Area di Disegno (DrawingArea) e il procedimento per Disegnare

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

## 20.4 Aggiungere il supporto per XInput

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:

- Sensibilità alla pressione
- Sensibilità all'inclinazione
- Posizionamento infra-pixel
- Ingressi multipli (per esempio, uno stilo che contiene sia una ``matita'' sia una ``gomma'')

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

### Abilitare le informazioni estese

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.

### Usare le informazioni estese

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

> `

### Trovare ulteriori informazioni su di un dispositivo

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

### Ulteriori sofisticazioni

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.

Avanti Indietro Indice