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:
http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial
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 è:
struct _GtkButtonClass
{
GtkContainerClass parent_class;
void (* pressed) (GtkButton *button);
void (* released) (GtkButton *button);
void (* clicked) (GtkButton *button);
void (* enter) (GtkButton *button);
void (* leave) (GtkButton *button);
};
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:
struct _GtkButton
{
GtkContainer container;
GtkWidget *child;
guint in_button : 1;
guint button_down : 1;
};
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.
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.
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:
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
.
.
.
#endif /* __TICTACTOE_H__ */
E per far felici i programmi in C++ che includono il nostro file header, in:
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
.
.
.
#ifdef __cplusplus
}
#endif /* __cplusplus */
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.
Qui vi è il file header completo:
/* tictactoe.h */
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
#include <gdk/gdk.h>
#include <gtk/gtkvbox.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
typedef struct _Tictactoe Tictactoe;
typedef struct _TictactoeClass TictactoeClass;
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *buttons[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
void (* tictactoe) (Tictactoe *ttt);
};
guint tictactoe_get_type (void);
GtkWidget* tictactoe_new (void);
void tictactoe_clear (Tictactoe *ttt);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TICTACTOE_H__ */
_get_type()
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.
guint
tictactoe_get_type ()
{
static guint ttt_type = 0;
if (!ttt_type)
{
GtkTypeInfo ttt_info =
{
"Tictactoe",
sizeof (Tictactoe),
sizeof (TictactoeClass),
(GtkClassInitFunc) tictactoe_class_init,
(GtkObjectInitFunc) tictactoe_init,
(GtkArgSetFunc) NULL,
(GtkArgGetFunc) NULL
};
ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
}
return ttt_type;
}
La struttura GtkTypeInfo ha la seguente definizione:
struct _GtkTypeInfo
{
gchar *type_name;
guint object_size;
guint class_size;
GtkClassInitFunc class_init_func;
GtkObjectInitFunc object_init_func;
GtkArgSetFunc arg_set_func;
GtkArgGetFunc arg_get_func;
};
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.
_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:
enum {
TICTACTOE_SIGNAL,
LAST_SIGNAL
};
static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
static void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
class->tictactoe = NULL;
}
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.
La funzione:
gint gtk_signal_new (const gchar *name,
GtkSignalRunType run_type,
GtkType object_type,
gint function_offset,
GtkSignalMarshaller marshaller,
GtkType return_val,
guint nparams,
...);
crea un nuovo segnale. I parametri sono:
name
: Il nome del segnale.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 parametriQuando 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.
_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.
static void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint i,j;
table = gtk_table_new (3, 3, TRUE);
gtk_container_add (GTK_CONTAINER(ttt), table);
gtk_widget_show (table);
for (i=0;i<3; i++)
for (j=0;j<3; j++)
{
ttt->buttons[i][j] = gtk_toggle_button_new ();
gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
i, i+1, j, j+1);
gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
gtk_widget_show (ttt->buttons[i][j]);
}
}
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''.
GtkWidget*
tictactoe_new ()
{
return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
}
void
tictactoe_clear (Tictactoe *ttt)
{
int i,j;
for (i=0;i<3;i++)
for (j=0;j<3;j++)
{
gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
FALSE);
gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
}
}
static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
int i,k;
static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 1, 2 }, { 0, 1, 2 } };
static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 2, 1, 0 } };
int success, found;
for (k=0; k<8; k++)
{
success = TRUE;
found = FALSE;
for (i=0;i<3;i++)
{
success = success &&
GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
found = found ||
ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
}
if (success && found)
{
gtk_signal_emit (GTK_OBJECT (ttt),
tictactoe_signals[TICTACTOE_SIGNAL]);
break;
}
}
}
E finalmente un programma di esempio che usa il nostro widget Tictactoe:
#include <gtk/gtk.h>
#include "tictactoe.h"
/* Invocato quando una riga, colonna o diagonale e' completata. */
void
win (GtkWidget *widget, gpointer data)
{
g_print ("Yay!\n");
tictactoe_clear (TICTACTOE (widget));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *ttt;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Crea un nuovo widget Tictactoe. */
ttt = tictactoe_new ();
gtk_container_add (GTK_CONTAINER (window), ttt);
gtk_widget_show (ttt);
/* E gli aggancia il segnale "tictactoe" */
gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
GTK_SIGNAL_FUNC (win), NULL);
gtk_widget_show (window);
gtk_main ();
return 0;
}
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.
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()
.
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.
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.
#include <math.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include "gtkdial.h"
#define SCROLL_DELAY_LENGTH 300
#define DIAL_DEFAULT_SIZE 100
/* Dichiarazioni di funzioni successive */
[ omesse per salvare spazio ]
/* variabili locali. */
static GtkWidgetClass *parent_class = NULL;
guint
gtk_dial_get_type ()
{
static guint dial_type = 0;
if (!dial_type)
{
GtkTypeInfo dial_info =
{
"GtkDial",
sizeof (GtkDial),
sizeof (GtkDialClass),
(GtkClassInitFunc) gtk_dial_class_init,
(GtkObjectInitFunc) gtk_dial_init,
(GtkArgSetFunc) NULL,
(GtkArgGetFunc) NULL,
};
dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
}
return dial_type;
}
static void
gtk_dial_class_init (GtkDialClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = (GtkObjectClass*) class;
widget_class = (GtkWidgetClass*) class;
parent_class = gtk_type_class (gtk_widget_get_type ());
object_class->destroy = gtk_dial_destroy;
widget_class->realize = gtk_dial_realize;
widget_class->expose_event = gtk_dial_expose;
widget_class->size_request = gtk_dial_size_request;
widget_class->size_allocate = gtk_dial_size_allocate;
widget_class->button_press_event = gtk_dial_button_press;
widget_class->button_release_event = gtk_dial_button_release;
widget_class->motion_notify_event = gtk_dial_motion_notify;
}
static void
gtk_dial_init (GtkDial *dial)
{
dial->button = 0;
dial->policy = GTK_UPDATE_CONTINUOUS;
dial->timer = 0;
dial->radius = 0;
dial->pointer_width = 0;
dial->angle = 0.0;
dial->old_value = 0.0;
dial->old_lower = 0.0;
dial->old_upper = 0.0;
dial->adjustment = NULL;
}
GtkWidget*
gtk_dial_new (GtkAdjustment *adjustment)
{
GtkDial *dial;
dial = gtk_type_new (gtk_dial_get_type ());
if (!adjustment)
adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
gtk_dial_set_adjustment (dial, adjustment);
return GTK_WIDGET (dial);
}
static void
gtk_dial_destroy (GtkObject *object)
{
GtkDial *dial;
g_return_if_fail (object != NULL);
g_return_if_fail (GTK_IS_DIAL (object));
dial = GTK_DIAL (object);
if (dial->adjustment)
gtk_object_unref (GTK_OBJECT (dial->adjustment));
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
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:
GtkAdjustment*
gtk_dial_get_adjustment (GtkDial *dial)
{
g_return_val_if_fail (dial != NULL, NULL);
g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
return dial->adjustment;
}
void
gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy)
{
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
dial->policy = policy;
}
void
gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment)
{
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
if (dial->adjustment)
{
gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
gtk_object_unref (GTK_OBJECT (dial->adjustment));
}
dial->adjustment = adjustment;
gtk_object_ref (GTK_OBJECT (dial->adjustment));
gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
(GtkSignalFunc) gtk_dial_adjustment_changed,
(gpointer) dial);
gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
(GtkSignalFunc) gtk_dial_adjustment_value_changed,
(gpointer) dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
gtk_dial_update (dial);
}
gtk_dial_realize()
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.
static void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
dial = GTK_DIAL (widget);
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = widget->allocation.width;
attributes.height = widget->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
widget->style = gtk_style_attach (widget->style, widget->window);
gdk_window_set_user_data (widget->window, widget);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
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.
static void
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}
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.
static void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
g_return_if_fail (allocation != NULL);
widget->allocation = *allocation;
if (GTK_WIDGET_REALIZED (widget))
{
dial = GTK_DIAL (widget);
gdk_window_move_resize (widget->window,
allocation->x, allocation->y,
allocation->width, allocation->height);
dial->radius = MAX(allocation->width,allocation->height) * 0.45;
dial->pointer_width = dial->radius / 5;
}
}
.
gtk_dial_expose()
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.
static gint
gtk_dial_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GtkDial *dial;
GdkPoint points[3];
gdouble s,c;
gdouble theta;
gint xc, yc;
gint tick_length;
gint i;
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);
if (event->count > 0)
return FALSE;
dial = GTK_DIAL (widget);
gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
xc = widget->allocation.width/2;
yc = widget->allocation.height/2;
/* Draw ticks */
for (i=0; i<25; i++)
{
theta = (i*M_PI/18. - M_PI/6.);
s = sin(theta);
c = cos(theta);
tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
gdk_draw_line (widget->window,
widget->style->fg_gc[widget->state],
xc + c*(dial->radius - tick_length),
yc - s*(dial->radius - tick_length),
xc + c*dial->radius,
yc - s*dial->radius);
}
/* Draw pointer */
s = sin(dial->angle);
c = cos(dial->angle);
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
gtk_draw_polygon (widget->style,
widget->window,
GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
points, 3,
TRUE);
return FALSE;
}
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()
).
static void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
new_value = dial->adjustment->value;
if (new_value < dial->adjustment->lower)
new_value = dial->adjustment->lower;
if (new_value > dial->adjustment->upper)
new_value = dial->adjustment->upper;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
(dial->adjustment->upper - dial->adjustment->lower);
gtk_widget_draw (GTK_WIDGET(dial), NULL);
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if ((dial->old_value != adjustment->value) ||
(dial->old_lower != adjustment->lower) ||
(dial->old_upper != adjustment->upper))
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
}
}
static void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
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:
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.