INTRODUZIONE
Per prima cosa voglio ringraziare Tristan per avermi fatto entrare in questo mondo con la sua guida: http://blogs.gnome.org/tvb/2013/04/09/announcing-composite-widget-templates/.
Nonostante sia tuttora anche io poco esperto, so per esperienza che se siete alle prime armi con Gtk+3 e Glade, e vi trovate nella necessità di creare un vostro widget personale, di sicuro la guida che sto per presentarvi può esservi d'aiuto.
Essendo la programmazione tramite Gtk+ orientata agli oggetti, quello che ci basterà fare per risolvere il problema sarà crearci un oggetto
con le caratteristiche desiderate. Per farlo, ovviamente, dovremo definire la classe corrispondente.
A questo punto basterà allocare un oggetto
della nuova classe ed il gioco è fatto.
L'utilità della definizione di un nuovo widget sta soprattutto nel fatto di poter ripetere lo stesso oggetto nella nostra interfaccia avendolo definito una sola volta. In effetti con Glade tutto questo è un gioco da ragazzi, ma non lo è altrettanto definire il nuovo oggetto come tipo.
Fortunatamente Gtk+3 mette a disposizione alcune funzioni che diminuiscono notevolmente il lavoro.
Nella guida utilizzarò un banalissimo esempio: nella finestra principale è presente un pulsante che permette la creazione del nuovo oggetto definito. Ogni oggetto avrà il proprio numero identificativo, e questo permetterà di verificare che in effetti si tratta di diverse istanze della stessa classe.
GLADE
L'interfaccia grafica che costruiremo è divisa in due parti: quella relativa
alla finestra principale, e quella relativa al nostro widget. Questo significa
che bisognerà lavorare su due file di glade separati.
mywidget.glade
Il primo passo da fare è aprire un nuovo progetto in Glade e
aggiungere come padre del nostro oggetto un GtkGrid, ovvero un
contenitore che ha la possibilità di essere diviso in più righe e
colonne (si trova nella sezione containers).
Lo rinominiamo in "MyWidget" e spuntiamo la casella composite,
grazie alla quale il nostro nuovo oggetto composto diventerà un
template.
Abbiamo creato il principale contenitore per il nostro oggetto,
che può essere composto a sua volta da altri contenitori e oggetti.
Nel mio esempio aggiungo un button (dalla sezione Control and Display) nel primo box del grid,
lo rinomino “widget_button” e faccio altrettanto con l'etichetta del button scrivendo
“Print message”.
Il pulsante, quando premuto,
servirà per stampare un messaggio.
Nel secondo box del grid
aggiungo una textview,
ovvero l'oggetto in cui verrà visualizzato il messaggio stampato,
rinominandola in “widget_textview”.
L'ultimo oggetto da aggiungere al nostro widget è un textbuffer, ovvero un oggetto che non appartiene all'interfaccia bensì al modello. È infatti qui che scriveremo realmente la stringa del messaggio, mentre la textview appena aggiunta servirà solo per visualizzarlo. Essendo parte del modello, non è necessario aggiungere il textbuffer dentro il contenitore padre; lo aggiungiamo quindi semplicemente nella pagina di Glade e lo rinominiamo in “widget_textbuffer”. A differenza degli oggetti precedenti, questo si trova nella sezione Miscellaneous.
Come ultimo punto bisogna aggiungere le signal agli oggetti: nel mio caso aggiungo una callback per collegare la pressione del button con la funzione di stampa del nostro messaggio. In alcuni casi può non essere necessario, ma in questo dobbiamo passare un parametro alla funzione: come si capirà dal codice, per poter accedere al numero dell'oggetto allocato, bisogna che venga passato il nostro nuovo widget intero. Il campo handler indica la funzione che verrà chiamata quando verrà catturata la signal del button. Il campo user_data indica l'oggetto che si vuole passare alla funzione. Il campo swap indica che come primo parametro della signal verrà passato l'oggetto user_data e non quello che emette la chiamata alla funzione. Nel codice questo passaggio sarà più chiaro.
A questo punto l'interfaccia grafica e la composizione del
nostro oggetto personale sono definite; salviamo il file in
“mywidget.glade” e passiamo all'interfaccia principale del nostro
programma.
main.glade
Apriamo un nuovo progetto in Glade.
Come nell'esempio precedente, aggiungiamo un contenitore padre,
che questa volta sarà una window (sezione Toplevels).
Questo oggetto infatti, così come tutti quelli della sua sezione,
permette di essere visualizzato come principale, ovvero senza essere
incluso in un contenitore. Rinomiamo l'oggetto in “window” e
aggiungiamo all'interno un altro button,
rinominandolo in “main_button”.
Infine, come nel caso del
nostro widget, aggiungiamo alla signal destroy dell'oggetto “window”
la funzione “on_window_destroy”. Salviamo il file in "main.glade" e passiamo al codice del nostro programma.
C, Gtk+3
Iniziamo dal nostro widget:
mywidget.h
Nel file header del nostro widget definiamo due struct, una per la classe (il nuovo tipo) ed una per l'istanza della classe
(l'oggetto).
GtkBox Grid;
GtkWidget *widget_button;
GtkWidget *widget_label;
GtkWidget *widget_textview;
GtkTextBuffer *widget_textbuffer;
int number;
};
struct _MyWidgetClass {
GtkGridClass parent_class;
};
Nella struct _MyWidget troviamo l'oggetto padre e dei puntatori agli oggetti che abbiamo definito nella nostra interfaccia con Glade. È molto importante che gli oggetti definiti nell'interfaccia abbiano esattamente lo stesso nome di quelli definiti nella struct. Solo in questo modo si riuscirà ad allocare gli oggetti così come definiti con Glade.
La struct che definisce il nuovo tipo contiene solo la classe padre, necessaria a creare il legame padre-figlio fra i tipi.
mywidget.c
La prima cosa da fare nel file i cui troviamo l'implementazione del nostro widget è:
G_DEFINE_TYPE(MyWidget, my_widget, GTK_TYPE_GRID);
Questa macro, messa a disposizione da Gtk+, permette di
risparmiarci molto tempo e codice. Viene usata per definire il nostro
nuovo tipo, legandolo all'oggetto padre e le funzioni che serviranno
per inizializzare la classe e l'oggetto. Implementa inoltre di default la
funzione my_widget_get_type(),
necessaria per fare il cast di un oggetto al nostro nuovo tipo.
static void
my_widget_class_init
(MyWidgetClass *klass) {
GtkWidgetClass *widget_class;
widget_class =
GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource
(widget_class, "/my/mywidget.glade");
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(widget_class),MyWidget, widget_button);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(widget_class),
MyWidget, widget_textview);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(widget_class),
MyWidget, widget_textbuffer);
gtk_widget_class_bind_template_callback
(widget_class, widget_print_message);
}
Questa è la funzione che
inizializza il nuovo tipo, estendendo
la classe del padre: viene
creato il collegamento tra il codice xml creato con Glade e la nuova
classe. Successivamente vengono collegati i widget figli definiti nel
template con la nuova classe, ed infine viene fatto il collegamento
tra le callback definite nel template e la nuova classe.
static void
my_widget_init (MyWidget
*widget)
{
static int count = 0;
widget->number = count;
count ++;
gtk_widget_init_template
(GTK_WIDGET (widget));
}
Questa
è la funzione che inizializza l'oggetto del nostro nuovo tipo
(l'istanza della nuova classe).
La struct dell'istanza ha un
campo number che si
riferisce al numero di oggetto allocato. Nella funzione viene dunque
settato grazie ad un contatore che poi viene incrementato. In ultimo viene
effettivamente allocato il nuovo widget inizializzato secondo il
template e, in questo caso, le nostre istruzioni.
my_widget_new ()
{return g_object_new (MY_TYPE_WIDGET, NULL);
}
È la funzione che permette di ritornare un oggetto del nuovo tipo e che ci serve per allocarlo da altre funzioni.
widget_print_message(MyWidget *widget, GtkButton *button) {
GtkTextBuffer *buffer = widget->widget_textbuffer;
char buf[20];
sprintf(buf, "I'm the widget number: %d", widget->number);
gtk_text_buffer_insert_at_cursor(buffer, buf, strlen(buf)); /*inserts text into text buffer*/
gtk_text_view_set_buffer(GTK_TEXT_VIEW(widget->widget_textview), buffer);
}
Questa è l'implementazione della callback che abbiamo dichiarato nell'interfaccia con Glade. Come si può notare, rispetto ad una normale callback, i parametri formali sono invertiti. Ed è proprio per questo motivo che abbiamo dovuto spuntare la casella swap all'atto della modifica delle signal in Glade. Riempiamo il textbuffer del nostro widget con il messaggio e lo visualizziamo sulla textview.
main.c
gint
main (gint argc, gchar *argv[]) {
GtkWidget *window;
GError *error = NULL;
GtkBuilder *gtkBuilder = NULL; /*main builder*/
gtk_init (&argc, &argv);
gtkBuilder = gtk_builder_new();
if (!gtk_builder_add_from_file (gtkBuilder, "main.glade", &error)) {
g_warning ("%s", error->message);
g_error_free (error);
}
gtk_builder_connect_signals(gtkBuilder, 0);
window = GTK_WIDGET(gtk_builder_get_object(gtkBuilder, "window"));
gtk_widget_show_all (window);
g_object_unref(G_OBJECT(gtkBuilder));
gtk_main ();
return 0;
}
La funzione main è molto semplice: si utilizza l'oggetto Builder per accedere all'interfaccia così come descritta nel file xml creato con Glade. La funzione gtk_builder_add_from_file() in effetti fa proprio questo. A questo punto si “catturano” le signal definite e gli oggetti (nel nostro caso la finestra principale, chiamata “window”) direttamente dal builder. Alla fine si visualizza tutto ciò che è contenuto nella finestra tramite gtk_widget_show_all (window) e si dereferenzia il Builder.
void
on_window_destroy (GtkWidget
*object, GThread *thread) { /*callback to close the main window*/
printf("Byebye\n");
gtk_main_quit ();
}
Questa è la callback che
chiude il programma quando
viene distrutto l'oggetto “window”.
void
create_widget(GtkButton
*button, gpointer *user_data) {
GtkWidget *widget_window;
GtkWidget *widget;
char title[20];
int widget_number;
widget = my_widget_new();
widget_number =
return_widget_number(MY_WIDGET(widget));
sprintf(title, "MyWidget
%d", widget_number);
widget_window =
gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(widget_window),
title);
gtk_container_add
(GTK_CONTAINER (widget_window), GTK_WIDGET(widget));
gtk_widget_show_all
(widget_window);
}
Ed ecco infine la callback che
permette di creare il nostro widget personale ogni volta che viene
premuto il pulsante nella finestra principale.
Come si può notare, per
visualizzare il nostro oggetto, il cui padre è un GtkGrid,
abbiamo bisogno di un contenitore toplevel.
In questo banalissimo caso utilizzo una finestra, ma si potrebbe
visualizzare il nostro widget dentro un notebook che è a sua volta
dentro un contenitore toplevel,
esattamente come facciamo ora con una semplice finestra. Utilizzando
poi il numero della pagina, viene settato il titolo della finestra
che visualizza il nostro oggetto.
Infine aggiungendo il nostro
oggetto (creato con la funzione my_widget_new())
al suo contenitore, visualizziamo il tutto come fatto nella funzione main().
CONCLUSIONI
Questa semplice guida non porta di certo a risultati esaltanti,
ma serve per affrontare per la prima volta l'idea di creare un
widget personale. Gtk+3 è uno strumento potente e completo e quindi
chiaramente offre ulteriori possibilità nella creazione di widget
personali, come quella di utilizzare variabili private,
interfacce, e tutto quello di cui dispone un linguaggio O.O. Mi sono voluto soffermare sugli aspetti fondamentali, soprattutto perchè li ritengo più utili che buttarsi in un progetto complesso e difficilmente comprensibile.
Ma con un pò di fantasia e di volontà, grazie a questi strumenti, si possono fare davvero dei bei lavori.
Per maggiori dettagli vi
rimando alla documentazione ufficiale:
Qualsiasi problema troviate nella guida, vi prego di segnalarmelo.





Nessun commento:
Posta un commento