Anche se la distribuzione GTK contiene molto tipi di widget che possono
coprire molte necessità basilari, può essere necessario costruirsi
un proprio widget. GTK usa molto l'ereditarietà tra i vari
widget e, di solito, vi è un widget che si avvicina a quello che ti
servirebbe, ed è spesso possibile creare un nuovo widget con poche linee
di codice. Ma prima di iniziare il lavoro su un nuovo widget, vediamo
se qualcuno non lo ha già creato. Questo eviterà un duplicazione
di lavoro e farà sì che i widget non-GTK puri siano minimi, così da
aiutare sia chi crea il codice che chi l'interfaccia per applicazioni GTK
molto grosse. D'altra parte, quando hai finito di scrivere un widget,
annuncialo a tutto il mondo così che le altre persone ne possano
beneficiare. Il miglioro modo dove farlo è la gtk-list.
I sorgenti completi per i widget di esempio possono essere presi dallo stesso
sito da cui avete scaricato questo tutorial, oppure da:
Per creare un nuovo widget è importante aver capito come gli ogetti
di GTK lavorano. Questa sezione è solo una breve spiegazione. Guarda la
documentazione di riferimento per maggiori dettagli.
I widget GTK sono implementati in un modo orientato agli oggetti,
anche se usando il C standard. Questo aumenta notevolmente la portabilità
e la stabilità, specialmente per le correnti generazioni di compilatori C++;
comunque questo significa che chi scrive un widget deve fare attenzione
ad alcuni dettagli di implementazione. L'informazione comune a tutte le
istanze di una classe di widget (ad esempio: a tutti i bottoni) è memorizzata
class structure. C'e' solamente una copia di questo in cui
sono memorizzate le informazioni riguardanti i segnali della classe
(assomiglia ad una funzione virtuale in C). Per supportare l'ereditarietà
il primo campo della struttura di una classe deve essere una copia della
struttura della classe genitore. La dichiarazione della struttura della
classe GtkButton è:
Quando un bottone viene trattato come un contenitore (ad esempio quando viene
ridimensionato) si può fare il cast della struttura della sua classe con la
GtkContainerClass, e usare i campi rilevanti per gestire i segnali.
C'è anche una struttura per ogni widget che viene creata
ad ogni istanza. Questa struttura ha campi per
memorizzare le informazioni che sono differenti per ogni volta che il widget
viene istanziato. Chiameremo questa struttura la struttura
oggetto. Per la classe Bottone, questa ha l'aspetto:
Si noti che, similmente alla struttura della classe, il primo campo
è la struttura dell'oggetto della classe madre, così che, se necessario, si può fare il
cast di questa struttura con quella dell'oggetto della classe madre.
Un tipo di widget a cui potreste essere interessati è un widget che
è semplicemnte un aggregato di altri widget GTK. Questo tipo di
widget non fa nulla che non possa essere fatto creando un nuovo
widget, ma fornisce un modo conveniente per inscatolare elementi
dell'interfaccia utente per poi riutilizzarli.
I widget FileSelection e ColorSelection della ditribuzione standard
sono esempi di questo tipo di widget.
Il widget di esempio che creeremo in questo capitolo è il
Tictactoe, un vettore 3x3 di bottoni a commutazione il quale emette
un segnale quando tutti e 3 i bottoni di una riga, colonna o di una
diagonale sono premuti.
Scegliere la classe madre
La classe madre per un widget composto e' tipicamente la classe
contenitrice che racchiude tutti gli elementi del widget composto.
Per esempio, la classe madre del widget FileSelection è la classe
Dialog. Visto che i nostri bottoni sono inseriti in una tabella, è
naturale pensare che la nostra classe madre possa essere la GtkTable.
Sfortunatamente, così non è. La creazione di un widget è diviso
tra 2 funzioni : la funzione WIDGETNAME_new() che viene invocata
dall'utente, e la funzione WIDGETNAME_init() che ha il compito
principale di inizializzare il widget che è indipendente dai valori
passati alla funzione _new(). Widget figli o discendenti possono
chiamare, solamente, la funzione del loro widget genitore.
Ma questa divisione del lavoro non funziona bene per la tabella, la
quale, quando creata, necessita di conoscere il numero di righe e
colonne che la comporrà. A meno che non vogliamo duplicare molte delle
fuinzionalità della gtk_table_new() nel nostro widget
Tictactoe, faremmo meglio a evitare di derivarlo dalla GtkTable. Per questa
ragione lo deriviamo invece da GtkVBox, e uniamo la nostra tabella
dentro il VBox.
Il File Header
Ogni classe di widget ha un file header il quale dichiara l'oggetto e la
struttura della classe del widget, comprese le funzioni pubbliche.
Per prevenire duplicati di definizioni, noi includiamo l'intero file header fra:
Insieme alle funzioni e alle strutture, dichiariamo tre macro
standard nel nostro file header, TICTACTOE(obj),
TICTACTOE_CLASS(klass), e IS_TICTACTOE(obj), i quali rispettivamente
fanno il cast di un puntatore ad un puntatore ad un ogetto od ad una struttura
di classe, e guarda se un oggetto è un widget Tictactoe.
Continuiamo ora con l'implementazione del nostro widget. Una funzione
basilare di ogni widget è la funzione WIDGETNAME_get_type().
Questa funzione, quando chiamata la prima volta, comunica a GTK la classe
del widget, e ottiene un identificativo univoco per la classe del
widget. Chiamate successive restituiscono semplicemente l'identificativo.
I campi di questa struttura sono abbastanza auto-esplicativi.
Ignoreremo, per ora, i campi arg_set_func e arg_get_func:
hanno un ruolo importante, ma ancora largamente non
implementato, nel permettere ai linguaggi interpretati
di settare convenientemente le opzioni del widget.
Una volta che il GTK ha completato correttamente una copia di questa
struttura, sa come creare un oggetto di un particolare widget.
La funzione _class_init()
La funzione WIDGETNAME_class_init() inizialiazza i campi della
struttura della classe del widget, e setta ogni segnale della classe.
Per il nostro widget Tictactoe ha il seguente aspetto:
Il nostro widget ha semplicemente il segnale ``tictactoe'' che è
invocato quando una riga, colonna o diagonale è completamente premuta.
Non tutti i widget composti necessitano di segnali, quindi se stai
leggendo questo per la prima volta, puoi anche saltare alla prossima sezione,
dal momento che a questo punto le cose diventano un po' complicate.
run_type: Se il segstore predefinito viene eseguito prima o dopo
di quello dell'utente. Di norma questo sarà GTK_RUN_FIRST, o GTK_RUN_LAST,
anche se ci sono altre possibilità.
object_type: l'identificativo dell'oggetto a cui questo segnale si
riferisce. Esso sarà anche applicato agli oggetti discendenti.
function_offset: L'offset nella struttura della classe di un
puntatore al gestore predefinito.
marshaller: una funzione che è usata per invocare il gestore
del segnale. Per gestori di segnali che non hanno argomenti oltre
all'oggetto che emette il segnale e i dati dell'utente, possiamo usare
la funzione predefinita gtk_signal_default_marshaller
return_val: Il tipo del valore di ritorno.
nparams: Il numero di parametri del gestore di segnali (oltre
ai due predefiniti menzionati sopra)
...: i tipi dei parametri
Quando si specificano i tipi, si usa l'enumerazione GtkType:
typedef enum
{
GTK_TYPE_INVALID,
GTK_TYPE_NONE,
GTK_TYPE_CHAR,
GTK_TYPE_BOOL,
GTK_TYPE_INT,
GTK_TYPE_UINT,
GTK_TYPE_LONG,
GTK_TYPE_ULONG,
GTK_TYPE_FLOAT,
GTK_TYPE_DOUBLE,
GTK_TYPE_STRING,
GTK_TYPE_ENUM,
GTK_TYPE_FLAGS,
GTK_TYPE_BOXED,
GTK_TYPE_FOREIGN,
GTK_TYPE_CALLBACK,
GTK_TYPE_ARGS,
GTK_TYPE_POINTER,
/* sarebbe bello poter togliere alla fine i prossimi due */
GTK_TYPE_SIGNAL,
GTK_TYPE_C_CALLBACK,
GTK_TYPE_OBJECT
} GtkFundamentalType;
gtk_signal_new() restituisce un identificatore unico intero per il segnale,
che memorizziamo nel vettore tictactoe_signals, che
indicizzeremo usando una enumerazione. (Convenzionalmente, gli elementi dell'enumerazione
sono i nomi dei segnali, in maiuscolo,
ma qui ci potrebbe essere un conflitto con la macro TICTACTOE(),
quindi l'abbiamo chiamato TICTACTOE_SIGNAL
Dopo aver creato un nostro segnale, abbiamo bisogno di dire a GTK
di associare il nostro segnale alla classe Tictactoe. Lo facciamo
invocando gtk_object_class_add_signals(). Settiamo quindi a NULL
il puntatore che punta al gestore predefinito per il segnale
``tictactoe'' a NULL, indicando che non ci sono azioni predefinite.
La funzione _init()
Ogni classe di Widget necessita anche di una funzione per inizializzare
la struttura dell'oggetto. Usualmente questa funzione ha il ruolo abbastanza
limitato di assegnare ai campi della struttura i valori predefiniti.
Per widget composti, comunque, questa funzione crea, anche,
i widget componenti del widget composto.
C'è un'altra funzione che ogni widget (eccetto i Widget di base come
GtkBin che non possono essere instanziati) deve avere : la funzione
che l'utente invoca per creare un oggetto di quel tipo. Questa è
convenzionalmente chiamata WIDGETNAME_new(). In alcuni widget,
non nel caso del nostro Tictactoe, questa funzione richiede degli
argomenti, e fa alcune operazioni basandosi su di essi. Le altre
due funzioni sono specifiche del widget Tictactoe.
tictactoe_clear() è una funzione pubblica che resetta tutti i
bottoni, nel widget, allo stato iniziale (non premuto). Notate l'uso
di gtk_signal_handler_block_by_data() per impedire che il nostro
gestore dei segnali venga attivato quando non ce n'è bisogno.
tictactoe_toggle() è il gestore del segnale che viene invocato
quando l'utente preme il bottone. Esso guarda se vi è
qualche combinazione vincente che coinvolge i bottoni premuti, e nel
caso ci fosse, emette il segnale ``tictactoe''.
In questa sezione impareremo meglio come i widget si mostrano sullo schermo
e interagiscono con gli eventi. Come esempio, creeremo
un widget di quadrante analogico con un puntatore che l'utente
può trascinare per assegnare il valore.
Mostrare un widget sullo schermo
Ci sono alcuni passi che sono necessari nella visualizzazione sullo
schermo. Dopo che il widget è stato creato con una chiamata a
WIDGETNAME_new(), sono necessarie alcune altre funzioni:
WIDGETNAME_realize() è responsabile della creazione di
una finestra X per il widget se ne ha una.
WIDGETNAME_map() è invocata dopo che l'utente ha
chiamato gtk_widget_show(). E' responsabile di vedere se il
widget è attualmente disegnato sullo schermo (mappato). Per
una classe contenitore, essa deve anche creare chiamate alle
funzioni map()> per ogni widget figlio.
WIDGETNAME_draw() è invocata quando
gtk_widget_draw() viene chiamata per il widget o per uno dei suoi
predecessori. Esso fa sì che l'attuale chiamata alla
funzione di disegno del widget disegni il widget sullo schermo.
Per la classe contenitore, questa funzione deve eseguire le
chiamate alla funzioni gtk_widget_draw() di ogni suo widget
figlio.
WIDGETNAME_expose() è un gestore per l'evento di esposizione
per il widget. Esso crea le chiamate necessarie alle funzioni di disegno
per disegnare la porzione che si è resa visibile. Per le classi
contenitore, questa funzione deve generare gli eventi di ``expose'' per
tutti i widget figli che non hanno una propria finestra (se essi hanno
una loro finestra, sarà X che genererà i necessari eventi di expose).
Potete notare che le ultime due funzioni sono molto simili, ognuna è
responsabile per il disegno del widget sullo schermo. Infatti molti
tipi di widget non sanno relamente la differenza tra le due.
La funzione di predefinita draw() nella classe widget, semplicemente
genera un sintetico evento di ``expose'' per l'area da ridisegnare.
Comunque, alcuni tipi di widget possono risparmiare tempo distinguendo
le due funzioni. Per esempio, se un widget ha piu' finestre X, allora
visto che l'evento ``expose'' identifica solo la finestra esposta,
esso può ridisegnare solo la finestra interessata, cosa che non è
possibile per chiamate a draw().
I widget contenitori, anche se essi non farebbero differenze,
non possono semplicemente usare la funzione draw() perchè per i
loro widget figli la differenza potrebbere essere importante. Comunque,
sarebbe uno spreco duplicare il codice di disegno nelle due
funzioni. La convenzione è che questi widget abbiano una funzione
chiamata WIDGETNAME_paint() che disegna il widget, che è poi
chiamata dalle funzioni draw() e expose()
Nell'approccio del nostro esempio, visto che il widget, ha
una sola finestra, possiamo utilizzare il modo piu' semplice
ed usare la funzione predefinita draw() e implementare
solamente la funzione expose().
Le origini del widget Dial
Come tutti gli animali terresti sono semplicemente varianti del primo
amfibio, i widget Gtk tendono ad essere varianti di altri widget, precedentemente
scritti. Così, anche se questa sezione è intitolata ``Creare
un widget a partire da zero", il nostro widget inizia in realtà con il codice
sorgente del widget Range. Questo è stato preso come punto d'inizio
perche' sarebbe carino se il nostro widget avesse la
stessa interfaccia del widget Scale il quale è semplicemente una
specializzazione del widget Range. Così, sebbene il codice sorgente e'
presentato sotto in forma definitiva, non si deve pensare che sia stato
scritto deus ex machina in questo modo. Se poi non avete familiarità
con il funzionamento del widget Scale dal punto di vista di chi scrive
un'applicazione, potrebbe essere una buona idea guardare indietro prima
di continuare.
Le basi
Una parte del nostro widget potrebbe essere simile
al widget Tictactoe. In primo luogo, abbiamo il file header:
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __GTK_DIAL_H__
#define __GTK_DIAL_H__
#include <gdk/gdk.h>
#include <gtk/gtkadjustment.h>
#include <gtk/gtkwidget.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
typedef struct _GtkDial GtkDial;
typedef struct _GtkDialClass GtkDialClass;
struct _GtkDial
{
GtkWidget widget;
/* Politica di update (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* Bottone correntemente premuto o 0 altrimenti */
guint8 button;
/* Dimensione della componente Dial. */
gint radius;
gint pointer_width;
/* ID del timer di update, o 0 altrimenti */
guint32 timer;
/* Angolo corrente. */
gfloat angle;
/* Vecchi valori dell'aggiustamento così sappiamo quando
* qualcosa cambia */
gfloat old_value;
gfloat old_lower;
gfloat old_upper;
/* L'oggetto adjustament che memorizza i dati per questo dial */
GtkAdjustment *adjustment;
};
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
guint gtk_dial_get_type (void);
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
void gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy);
void gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __GTK_DIAL_H__ */
Essendoci più cose da fare con questo widget, rispetto al precedente,
abbiamo più cambi nella struttura dati, ma le altre cose sono
abbastamza simili.
Dopo aver incluso i file di header e aver dichiarato alcune costanti,
dobbiamo fornire alcune funzioni circa il widget e la sua
inizializzazione.
Notate che questa funzione init() fa meno rispetto all'analoga del
widget Tictactoe, essendo questo un widget non composto, e la
funzione new() fa di più, essendoci un argomento. Inoltre,
notate che quando memorizziamo un puntatore all'oggetto Adjustment,
incrementiamo il conteggio dei suoi riferimenti(e corrispondentemente
lo decrementato quando non lo usiamo più) così che GTK può tener traccia di
quando è possibile distruggerlo senza causare guai.
Inoltre, ci sono alcune funzioni per manipolare le opzioni del widget:
Abbiamo ora raggiunto alcuni nuovi tipi di funzione. In primo luogo,
abbiamo una funzione che crea la finestra di X. Noterete che viene
passata alla funzione gdk_window_new() una maschera che
specifica quali campi della struttura GdkWindowAttr non sono vuoti
(ai rimanenti campi può essere dato il valore predefinito). Anche
il modo con cui la maschera degli eventi del widget creata non è
complicato. Chiameremo gtk_widget_get_events() per sapere la
maschera degli eventi che l'utente ha specificato per questo widget
(con gtk_widget_set_events()) e aggiungeremo gli eventi che ci possono
interessare.
Dopo aver creato la finestra, settiamo lo stile e lo sfondo,
e creiamo un puntatore al widget nel campo dei dati utente (user data)
del GdkWindow. Quest'ultimo passo permette a GTK di mandare gli
eventi della finestra al widget corretto.
Prima di visualizzare per la prima volta la finestra, e se il
layout della finestra cambia, GTK chiede ad ogni widget, incluso nella
finestra, la propria dimensione. Questa richiesta è fatta dalla
funzione gtk_dial_size_request(). Non essendo il nostro widget
un contenitore, e non avendo dei veri limiti per la propria
dimensione, restituiamo semplicemnte un valore ragionevole.
Dopo che tutti i widget hanno restituito una dimensione ideale, viene
calcolata la disposizione della finestra e ad ogni widget figlio è
notificata la propria dimensione attuale . Usualmente, questo sarà
almeno quanto richiesto, ma occasionalmente può essere più piccolo.
La notifica della dimensione viene fatta dalla funzione
gtk_dial_size_allocate(). Notate che questa funzione è utilizzata
anche quando la finestra X del widget è spostata o modificata come
dimensione.
Come menzionato sopra, tutto il lavoro di questo widget viene fatto nella
gestione dell'evento ``expose''. Non c'è molto da notare su questo eccetto
l'uso della funzione gtk_draw_polygon per disegnare il
puntatore con un'ombreggiatura a tre dimensioni in accordo con il colore
memorizzato nello stile del wiget.
Il resto del codice del widget manipola vari tipi di eventi, e non
è differente da quello che può essere trovato in molte applicazione
GTK. Due tipi di eventi possono verificarsi: l'utente può
clickare sul widget con il mouse e trascinare per muovere il puntatore,
o il valore dell'oggetto Adjustmente può cambiare a causa di alcune
circostanze esterne.
Quando l'utente clicka sul widget, noi vediamo se la pressione
era veramente vicina al puntatore, e se così, memorizziamo il bottone
premuto dall'utente con il campo button della struttura del
widget, e prendiamo tutti gli eventi del mouse con una chiamata alla
funzione gtk_grab_add(). Successivi movimenti del mouse causano il
ricalcolo dei valori di controllo (fatto dalla funzione
gtk_dial_update_mouse). Dipendentemente dalla politica che abbiamo
stabilito, gli eventi ``value_changed'' possono essere generati
istantaneamente (GTK_UPDATE_CONTINUOUS), dopo un certo tempo aggiunto
con la funzione gtk_timeout_add() (GTK_UPDATE_DELAYED), o
solamente quando il bottone del mouse e' rilasciato
(GTK_UPDATE_DISCONTINUOUS).
static gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
/* Determina se il bottone premuto era dentro la regione del puntatore:
lo facciamo calcolando la distanza parallela e
perpendicolare dal punto dove il bottone del mouse e' stato premuto
alla linea passante per il puntatore. */
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
d_parallel = s*dy + c*dx;
d_perpendicular = fabs(s*dx - c*dy);
if (!dial->button &&
(d_perpendicular < dial->pointer_width/2) &&
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
gtk_dial_update_mouse (dial, event->x, event->y);
}
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
(dial->old_value != dial->adjustment->value))
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button != 0)
{
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer (widget->window, &x, &y, &mods);
switch (dial->button)
{
case 1:
mask = GDK_BUTTON1_MASK;
break;
case 2:
mask = GDK_BUTTON2_MASK;
break;
case 3:
mask = GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods & mask)
gtk_dial_update_mouse (dial, x,y);
}
return FALSE;
}
static gint
gtk_dial_timer (GtkDial *dial)
{
g_return_val_if_fail (dial != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
return FALSE;
}
static void
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
{
gint xc, yc;
gfloat old_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
xc = GTK_WIDGET(dial)->allocation.width / 2;
yc = GTK_WIDGET(dial)->allocation.height / 2;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
if (dial->angle < -M_PI/2.)
dial->angle += 2*M_PI;
if (dial->angle < -M_PI/6)
dial->angle = -M_PI/6;
if (dial->angle > 7.*M_PI/6.)
dial->angle = 7.*M_PI/6.;
dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
(dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
if (dial->adjustment->value != old_value)
{
if (dial->policy == GTK_UPDATE_CONTINUOUS)
{
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
else
{
gtk_widget_draw (GTK_WIDGET(dial), NULL);
if (dial->policy == GTK_UPDATE_DELAYED)
{
if (dial->timer)
gtk_timeout_remove (dial->timer);
dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
(GtkFunction) gtk_dial_timer,
(gpointer) dial);
}
}
}
}
Cambiamenti esterni all'Adjustment sono comunicati al nostro widget
dai segnali ``changed'' e ``value_changed''. Il gestore per
queste funzioni chiama gtk_dial_update() per validare gli
argomenti, calcolare il nuovo angolo del puntatore e ridisegnare il
widget (chiamando gtk_widget_draw()).
Il widget Dial, da come l'abbiamo costruito, è lungo circa 670 linee
di codice C. Anche se questo potrebbe sembrare un po' troppo, abbiamo
realmente fatto un bel po' con quel tanto di codice, specialmente
considerando che molta della lunghezza è costituita da file header e
commmenti. Comunque ci sono alcuni miglioramenti che potrebbero essere
fatti a questo widget:
Se tu provate questo widget, troverete che ci sono alcuni lampeggiamenti
quando il puntatore viene trascinato in giro. Questo
perchè l'intero widget è cancellato ogni volta che il
puntatore viene mosso, prima di essere ridisegnato. Spesso, il modo migliore
per gestire questo tipo di problema è il disegnare il tutto su una
pixmap non visibile, poi copiare il risultato finale sullo schermo
in una passata sola (il widget ProgressBar viene disegnato in questo
modo).
L'utente potrebbe essere abilitato ad usare le frecce su e giu per
incrementare e diminuire il valore.
Potrebbe essere carino se il widget avesse i bottoni per
incrementare e decrementare il valore di step. Anche se potrebbe essere
possibile usare dei widget Bottone incorporati per questo, possiamo anche
far sì che il bottone sia auto-ripentente quando premuto, come le frecce
in una barra di scorrimento. Molto del codice per implementare questo tipo di
comportamento può essere trovato nel widget GtkRange.
il widget Dial potrebbe essere fatto/creato dentro un widget
contenitore con un singolo widget figlio posizionato all'inizio tra i
2 bottoni menzionati prima. L'utente potrebbe poi aggiungere o una etichetta
o un widget ``entry'' per mostrare il valore corrente del dial.
Fin qui abbiamo esposto solo una piccola parte di tutto quello che serve
per creare un widget. Se volete davvero scrivere un vostro widget, la
miglior risorsa di esempi è lo stesso codice sorgente GTK. Chiedete a voi
stessi alcune cose su come deve essere il widget che volete scrivere: è
un widget contenitore? dovrà avere una propria finestra? è una modifica di
un widget precedente? Trovate poi un widget simile e iniziate a fargli
delle modifiche.
Buone Fortuna.