GTK+/Struktura programu GTK+

GTK+ jest pisane w języku C, tak jak i sam system Linux. Można pisać programy z wykorzystaniem biblioteki GTK+ w innych jezykach ale najważniejszy jest język C. Znajomość tego języka pozwala analizować kody źródłowe GTK+ co jest bardzo cenną umiejętnością.

Struktura programu GTK+ w CEdytuj

Przykładowy program napisany w języku C dla systemu Linux - simple.c

//  gcc -Wall -g  simple.c -o test `pkg-config --cflags --libs gtk+-2.0`

#include <gtk/gtk.h>

static void hello( GtkWidget *widget,
                   gpointer   data )
{
    g_print ("-> clicked\n\tHello World\n");
}

static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
    g_print("-> delete_event\n");
    return FALSE;
}

static void destroy( GtkWidget *widget,
                     gpointer   data )
{
    g_print("-> destroy\n");
    gtk_main_quit ();
}

int main( int   argc,
          char *argv[] )
{
    GtkWidget *window;
    GtkWidget *button;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    g_signal_connect (G_OBJECT (window), "delete_event",
		      G_CALLBACK (delete_event), NULL);

    g_signal_connect (G_OBJECT (window), "destroy",
		      G_CALLBACK (destroy), NULL);

    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

    button = gtk_button_new_with_label ("Hello GTK+");

    g_signal_connect (G_OBJECT (button), "clicked",
		      G_CALLBACK (hello), NULL);

    gtk_container_add (GTK_CONTAINER (window), button);

    gtk_widget_show (button);

    gtk_widget_show (window);

    gtk_main ();

    return 0;
}

Aby go skompilować należy wydać polecenie:

 gcc -Wall -g  simple.c -o test `pkg-config --cflags --libs gtk+-2.0`. 


Polecenie to skompiluje plik simple.c do programu o nazwie "test".

Po udanej kompilacji program można już uruchomić poleceniem

 ./test

zakładając, że znajdujemy się w tym samym katalogu.


pkg-config to specjalny program, który zajmuje się dołączaniem parametrów do kompilatora gcc, niezbędnych do kompilacji programu napisanego przy użyciu biblioteki GTK+. W tym przypadku dołącza biblioteki gtk+-2.0. Należy zwrócić uwagę na znaki apostrofu otaczające polecenie pkg-config.

Aby dokładnie zobaczyć co wstawia pkg-config do parametrów kompilatora można wydać poniższe polecenie w konsoli:

 pkg-config --cflags --libs gtk+-2.0 

Jeżeli będziesz chciał kompilować programy napisane w GTK+ pod systemem Windows np. w środowisku Dev-C++ wynik takiego polecenia będziesz musiał wstawić do opcji kompilatora - oczywiście wtedy takie polecenie będziesz musiał wydać w wierszu poleceń systemu Windows. Być może będziesz musiał jeszcze dodać do zmiennych systemowych odpowiednie zmienne, które będą wskazywać na położenie plików nagłówkowych GTK+ w systemie Windows.

#include <gtk/gtk.h>

Dołącza plik nagłówkowy biblioteki GTK+. Znajdują się tam definicje biblioteki GTK+.

static void hello( GtkWidget *widget,
                   gpointer   data )
{
    g_print ("-> clicked\n\tHello World\n");
}

To statyczna funkcja używana jako funkcja zwrotna. Zostanie ona wywołana jako reakcja na kliknięcie przycisku. Posiada dwa parametry. Pierwszy to wskaźnik do struktury GtkWidget. Struktura ta jest ogólną definicją prawie każdej kontrolki w bibliotece GTK+. Choć język C nie jest obiektowy to twórcy biblioteki GTK+ bardzo efektywnie symulują mechanizm dziedziczenie dzięki czemu np. GtkButton dziedziczy po GtkWidget podstawowe właściwości i funkcje. Dzięki dziedziczeniu do tej funkcji tak naprawdę nie zostanie przekazany obiekt typu GtkWidget lecz GtkButton, który zostanie rzutowany w dół, na typ bardziej podstawowy. W ten sposób funkcja ta jest uniwersalna - obsługuje wszystkie kontrolki, które wywodzą się od GtkWidget. Kolejny parametr to data typu gpointer. Jest to tak naprawdę wskaźnik przeznaczony dla nas. Przez niego możemy do funkcji wprowadzić dane, na których chcielibyśmy operować we wnętrzu funkcji. W naszym przypadku będzie on nieużywany, wartość NULL. Typ gpointer jest jednym z podstawowych typów zdefiniowanych w bibliotece GLib. Nasza funkcja zwrotna ogólnie rzecz biorąc musi pasować do wzorca funkcji, który jest zdefiniowany dla każdego sygnału w bibliotece GTK+. My chcemy wywołać tę funkcję w momencie, gdy użytkownik wciśnie przycisk. Za takie zdarzenie odpowiada sygnał clicked kontrolki GtkButton. W ciele funkcji używamy również zdefiniowanej w GLib funkcji g_print. Jest to właściwa funkcja do wypisywania komunikatów w konsoli dla programów GTK+. Jej wynik możemy zobaczyć tylko jeżeli uruchomimy program z konsoli. Możemy również taki program uruchomić dwukrotnie klikając na jego ikonę, co spowoduje prawidłowe uruchomienie, ale komunikaty wypisywane w konsoli nie będą wtedy widoczne.

static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
    g_print("-> delete_event\n");
    return FALSE;
}

static void destroy( GtkWidget *widget,
                     gpointer   data )
{
    g_print("-> destroy\n");
    gtk_main_quit ();
}

Kolejne dwie funkcje również są funkcjami zwrotnym, czyli takimi, których zadaniem jest obsługa konkretnych sygnałów zgłaszanych przez konkretne kontrolki. W tym przypadku będą to dwa sygnały: delete-event i destroy. Przydatna jest tu również znajomość hierarchii obiektów biblioteki GTK+. Oba sygnały będą podłączone pod zdarzenia obiektu GtkWindow. Ale tak naprawdę sygnał delete-event jest sygnałem GtkWidget, a sygnał destroy jest sygnałem GtkObject. Ze struktury hierarchii dziedziczenia możemy wyczytać, że GtkWindow wywodzi się z GtkWidget, a ten z GtkObject. Te dwa sygnały będą obsługiwały zdarzenie zamknięcia okna naszego programu. Gdy użytkownik będzie próbował go zamknąć krzyżykiem, okno programu wyemituje najpierw sygnał delete-event, który odpowiada za decyzje czy pozwalamy na zamknięcie programu. Jeżeli funkcja zwróci wartość FALSE GTK+ wyemituje sygnał destroy. Ten z kolei wywołuje funkcję, w której powinniśmy zakończyć działanie programu napisanego w GTK+ i zwolnić zasoby. Każda z tych funkcji otrzymuje parametr GtkWidget - kto emituje sygnał, tzn. jaka kontrolka. Funkcja delete_event otrzymuje jeszcze parametr typu GdkEvent - strukturę ze szczegółowymi informacjami dotyczącymi zdarzenia. Ostatnim parametrem tradycyjnie jest data typu gpointer. Komunikaty wypisywane w ciele funkcji przez funkcje g_print będą nas informować na bieżąco jakie zdarzenia zachodzą w konsoli. Funkcja destroy kończy się wywołaniem bardzo ważnej funkcji gtk_main_quit(), która kończy działanie GTK+ (w obrębie tylko naszego programu).

int main( int   argc,
          char *argv[] )
{
    GtkWidget *window;
    GtkWidget *button;

Rozpoczynamy program - funkcja main(). Na początku tworzymy potrzebne nam typy danych, czyli dwa widgety: "window" - główne okno programu, "button" - przycisk umieszczony w głównym oknie programu. Obecnie nie dysponujemy jeszcze kontrolkami, a jedynie wskaźnikami do struktur, którym zostaną przypisane konkretne instancje. To znaczy, że same kontrolki należy jeszcze stworzyć.

    gtk_init (&argc, &argv);

Włączamy GTK+. Służy do tego funkcja gtk_init(), inicjalizuje ona wszelkie wewnętrzne mechanizmy GTK+. Od momentu jej wywołania kontrolki zaczynają "działać" np. wysyłać sygnały, aż do momentu wywołania funkcji gtk_main_quit().

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

Teraz, gdy mamy potrzebne obiekty i zainicjowane GTK+ (dzięki temu rejestruje łączenie sygnałów), możemy utworzyć właściwe okno programu. Służy do tego funkcja gtk_windows_new(). Przyjmuje ona jeden parametr typu GtkWindowType o wartości GTK_WINDOW_TOPLEVEL - czyli głównego okna aplikacji. Funkcja gtk_windows_new() zwraca wskaźnik do struktury GtkWidget, a nie GtkWindow.

g_signal_connect (G_OBJECT (window), "delete_event",
		  G_CALLBACK (delete_event), NULL);
g_signal_connect (G_OBJECT (window), "destroy",
		  G_CALLBACK (destroy), NULL);

Mamy już główne okno w programie GTK+. Jak pisałem wcześniej zostały zdefiniowane dwie funkcje zwrotne: delete_event() i destroy(), które obsługują emitowane przez okno sygnały o tych samych nazwach. Pierwszy z nich upewnia się czy można zamknąć główne okno, a drugi usuwał je z pamięci, jeżeli była na to zgoda. Do kojarzenia sygnałów z funkcjami zwrotnymi służy funkcja g_signal_connect(). Jest ona częścią oddzielnej biblioteki o nazwie GObject. GObject jest poświęcona na symulowanie mechanizmów obiektowości. W hierarchii dziedziczenia GTK+ znajduje się najniżej pośród takich kontrolek jak GtkWidget, GtkWindow i GtkButton. Funkcja g_signal_connect() przyjmuje cztery parametry. W skrócie mówiąc co z czym połączyć, a dokładnie:

  • window (rzutowane przez makro G_OBJECT() na typ GObject) - kontrolka, której sygnał będzie obsługiwany
  • "delete_event" - nazwa sygnału wyemitowanego przez daną kontrolkę
  • delete_event (makro G_CALLBACK() rzutuje wskaźnik do funkcji na wskaźnik do funkcji typu GCallback) - wskaźnik do funkcji, która ma być wywołana, gdy zajdzie wcześniej podane zdarzenie dla określonej kontrolki
  • data (ma wartość NULL) - to dodatkowy parametr, poprzez który możemy przekazać wywoływanej funkcji dodatkowy wskaźnik.

Tak więc pierwsza funkcja g_signal_connect() połączy sygnał "delete_event" kontrolki window z funkcją zwrotną delete_event() i nie skorzysta z możliwości przekazania do tej funkcji dodatkowych danych, wartość NULL dla ostatniego parametru. Druga funkcja g_signal_connect() ma analogiczną składnię z tym, że łączy sygnał "destroy" widgetu window z funkcją zwrotną destroy(). W ten sposób okno wcześniej stworzone będzie mogło w ogóle być zamknięte prawidłowo. Gdybyśmy nie obsłużyli tych sygnałów funkcja gtk_main_quit() nie zostałaby nigdy wywołana i program nie mógłby się zakończyć.

gtk_container_set_border_width (GTK_CONTAINER (window), 10);

Ta linia ma szczególne znaczenie dla zrozumienia jak jest zbudowany zestaw kontrolek GTK+. W hierarchii dziedziczenia zaraz za GtkWidget znajduje się GtkContainer. Tak więc wszystkie kontrolki wywodzące się z GtkWidget są również typu GtkContainer. GtkContainer to kontrolka, która nadaje właściwości pojemnika dla każdej kontrolki dziedziczącej po niej. Koncepcja pojemnika sprawia, że kontrolki możemy traktować jak pojemniki, do których można wkładać inne kontrolki. Pojemniki w GTK+ przypominają tabele, które mogą mieć tylko jedną komórkę, a w środku zawierać inną tabelę, która ma np. dwa wiersze i jedną kolumnę. Takie ułożenie kontrolek jasno precyzuje zależności typu dziecko/rodzic między kontrolkami. Poza tym ułatwia organizację kontrolek w obrębie największego pojemnika, którym zazwyczaj jest główne okno. Tworząc okno użyliśmy funkcji gtk_window_new(). Funkcja zwróciła wskaźnik typu GtkWidget. Tak to wygląda z pozoru, tak naprawdę zwróciła wskaźnik do struktury GtkWindow rzutowany na GtkWidget, aby można było używać ogólnych funkcji. Inaczej nasze okno nie byłoby kontenerem, ponieważ GtkContainer jest nad GtkWidget - GtkWidget nie jest tak naprawdę kontenerem. Nasz wskaźnik window musi być teraz rzutowany do góry, do poziomu GtkContainer. Taki właśnie wskaźnik przyjmuje funkcja gtk_container_set_border_width() jako pierwszy parametr. Wskaźnik przekształcamy za pomocą makra GTK_CONTAINER() - makro to jest zdefiniowane w pliku nagłówkowym gtkcontainer.h - na wskaźnik typu GtkContainer. Funkcja ustala wielkość obramowania kontenera, tzn. jaki ma być odstęp między "ramką" a widgetami znajdującymi się w środku. Odstęp ten określa drugi parametr podawany w pixelach, w naszym przypadku to 10 pixeli.

button = gtk_button_new_with_label ("Hello GTK+");

Wcześniej stworzyliśmy okno za pomocą funkcji gtk_window_new(), teraz w analogiczny sposób tworzymy przycisk. Używamy do tego funkcji gtk_button_new_with_label(). Tworzy ona nowy przycisk wraz z etykietą/napisem. Tak naprawdę taki przycisk to nic innego jak kontener, którym jest GtkButton, zawierający w sobie drugi kontener GtkLabel. Oczywiście mogli byśmy najpierw stworzyc GtkButton, a potem dodać do niego GtkLabel. Funkcja gtk_button_new_with_label() robi to za nas automatycznie, my musimy jej tylko podać jaki tekst ma się znaleźć na przycisku. Funkcja zwraca wskaźnik typu GtkWidget.

g_signal_connect (G_OBJECT (button), "clicked",
		  G_CALLBACK (hello), NULL);

Kolejnym krokiem po stworzeniu przycisku jest obsługa jego sygnałów po to, aby przycisk nie był tylko bezużyteczną kontrolką. W przypadku przycisku najważniejszym zdarzeniem jest kliknięcie. Odpowiada za to sygnał "clicked". Powyższy kod kojarzy funkcję zwrotną hello() z przyciskiem i jego sygnałem clicked.

gtk_container_add (GTK_CONTAINER (window), button);

Ponieważ i okno i przycisk są kontenerami trzeba je teraz upakować, jeden do drugiego. Umieścimy przycisk wewnątrz okna. W tym celu należy dodać widget do kontenera nadrzędnego. Służy do tego funkcja gtk_container_add(). Przyjmuje ona dwa parametry: kontener, do którego ma zostać włożony widget i wskaźnik do samego widgetu. Funkcja jako pierwszego parametru spodziewa się wskaźnika typu GtkContainer a my jesteśmy w posiadaniu GtkWidget, wiec musimy go rzutować do góry za pomocą makra GTK_CONTAINER(). W ten sposób umieściliśmy przycisk w głównym oknie programu, w przeciwnym razie nie byłby wcale widoczny.

gtk_widget_show (button);

gtk_widget_show (window);

Samo stworzenie i dodanie do kontenera nie czyni żadnej kontrolki jeszcze widocznej. Trzeba to uczynić samodzielnie za pomocą funkcji gtk_widget_show(). Przyjmuje ona jeden parametr - widget, który chcemy uczynić widocznym. W tym momencie nasza aplikacja jest już w sumie stworzona, brakuje tylko ostatniego impulsu, który obudziłby wszystkie mechanizmy do życia.

gtk_main ();

Tym impulsem jest funkcja gtk_main(). Zadaniem funkcji jest uruchomienie mechanizmu pętli obsługi zdarzeń dla wcześniej zdefiniowanej aplikacji GTK+. Od tego momentu program "rusza", zaczyna rysować zadane kontrolki i wychwytywać zadane komunikaty. Zakończenie tej pętli jest jednocześnie zakończeniem programu. Pętle kończy funkcja gtk_main_quit(). Zostanie ona wywołana w odpowiedzi na wciśnięcie krzyżyka na pasku tytułowym okna. W wyniku tego zostanie wyemitowany sygnał delete_event - obsłuży go funkcja o tej samej nazwie co sygnał, zwracając wartość NULL, która pozwoli na zniszczenie okna, co z kolei doprowadzi do wywołania sygnału destroy. Do sygnału destroy została podpięta funkcja również o tej samej nazwie co sygnał. W ciele funkcji destroy() wywołujemy funkcją gtk_main_quit() i tak procesy uruchomione od wywołania funkcji gtk_main() zostają wstrzymane, a sama aplikacja napisana w GTK+ zakończona.

Ten program skompilowany i uruchomiony z konsoli będzie wypisywał zadaną treść (g_print ("-> clicked\n\tHello World\n");) za każdym razem jak wciśniemy przycisk. Wciśnięcie krzyżyka spowoduje wyemitowanie dwóch sygnałów: delete_event i destroy (zostanie to wypisane w konsoli), a następnie spowoduje zamknięcie programu.

Struktura programu GTK+ w PythonieEdytuj

Python to język bardzo wygodny do pisania programów z użyciem biblioteki GTK+. Posiada on do tego specjalną biblioteke PyGTK. Jest to jedna z oficjalnie wspieranych bibliotek GTK+ dla innych języków niż C. Wiele graficznych narzędzi do konfiguracji systemu linux jest pisane właśnie w PyGTK ze wzgledu na szybkość tworzenia. Dla przykładu podaję analogiczny program do powyższego, z tym, że napisany w Pythonie.

#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk

def hello(widget, data=None):
        print "-> clicked\n\tHello World\n"

def delete_event(widget, event, data=None):
	print "-> delete_event\n"
	return False

def destroy(widget, data=None):
        print "-> destroy\n"
        gtk.main_quit()


if __name__ == "__main__":
        
	window = gtk.Window(gtk.WINDOW_TOPLEVEL)
	
        window.connect("delete_event", delete_event)
        window.connect("destroy", destroy)
	
        window.set_border_width(10)
    
        button = gtk.Button("Hello World")
        button.connect("clicked", hello, None)
	
        window.add(button)
	
        button.show()
        window.show()
	
        gtk.main()

Wyraźnie widać tu zalety Pythona. Program jest krótszy i prostszy. Poza tym PyGTK jest w przeciwnieństwie do GTK+ biblioteką naturalnie obiektową.