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 windowspecifica 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:

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