Hlavní navigace

Programování pro X Window System (2)

8. 4. 2004
Doba čtení: 9 minut

Sdílet

Po minulém úvodním článku se teď už opravdu pustíme do programování. První toolkit, na který se podíváme blíž, je GTK+. Zkratka pochází z názvu "The Gimp Toolkit". GTK+ totiž původně vzniklo jako toolkit pro program Gimp. Dnes je na tomto toolkitu postaveno mnoho aplikací a tvoří základ desktopového prostředí GNOME.

GTK+

Po minulém úvodním článku se teď už opravdu pustíme do programování. První toolkit, na který se podíváme blíž, je GTK+. Zkratka pochází z názvu „The Gimp Toolkit“. GTK+ totiž původně vzniklo jako toolkit pro program Gimp. Dnes je na tomto toolkitu postaveno mnoho aplikací a tvoří základ desktopového prostředí GNOME. Budeme se zabývat současnou stabilní verzí GTK+ 2.2. Proti předchozí široce používané verzi 1.2 obsahuje GTK+ 2 hodně vylepšení, např. lepší widgety pro seznamy, stromy, zobrazení a editaci textu. Také se zlepšila kvalita dokumentace, nicméně stále zůstávají nedokumentované funkce. Pro pochopení detailů je často nutné kombinovat referenční dokumentaci, tutoriály, ukázkové programy a zdrojové texty toolkitu.

Toolkit GTK+ je napsaný v jazyce C. Protože je objektově orientovaný, bylo potřeba v C implementovat prostředky pro manipulaci s objekty, které jsou v jazycích jako C++ součástí jazyka. Třídy jsou definovány jako struktury, odvozená třída obsahuje strukturu bázové třídy jako svou první položku. Virtuální metody jsou realizovány pomocí signálů. Zde je třeba odlišit unixové signály (asynchronní) a signály GTK+, které poskytují mechanismus pro synchronní volání registrovaných handlerů. Pro GTK+ existují i API (language bindings) pro jiné jazyky než C. Podporovány jsou Ada, C++, Perl, Python aj. V C++ lze použít buď C-čkové rozhraní, nebo knihovnu gtkmm (dříve GTK–). Jednotlivé třídy GTK+ jsou v gtkmm„obaleny“ třídami jazyka C++. Grafické rozhraní programu se definuje buď přímo v kódu programu posloupností volání funkcí na vytvoření jednotlivých widgetů, nebo lze použít nástroj Glade pro vizuální interaktivní návrh vzhledu aplikace. Programátor myší umísťuje jednotlivé widgety na obrazovku a nastavuje jejich parametry. Glade následně vygeneruje kostru aplikace obsahující kód na vytvoření widgetů. Do této kostry je pak nutné doplnit zbytek programu, především těla handlerů signálů.

Než se pustíme do programování, musíme si ještě ujasnit několik základních pojmů. Widget je prvek uživatelského rozhraní (např. check box nebo editační textový řádek). V programu je reprezentovaný objektem třídy odvozené od GtkWidget. Objekt je instance třídy GObject nebo odvozené třídy. Widget je také objekt, protožeGtkWidget je potomkem GObject. Okno ve smyslu GTK+ je top-level okno programu (hlavní okno nebo dialog). Pomocná knihovna GDK, která tvoří rozhraní mezi GTK+ a Xlib, používá termín okno pro strukturu reprezentující X okno spravované X serverem. GDK okna existují v klientovi. Naopak X okno je zdroj poskytovaný X serverem, klient si drží pouze jeho ID (číselný identifikátor). Je to obdélníková oblast obrazovky, do které může klient kreslit. Když X server pošle událost, je její součástí informace o tom, ve kterém okně nastala. Aby byl widget vidět na obrazovce, potřebuje X okno, do něhož bude kreslit. Typicky má jeden widget jedno GDK a tedy i jedno X okno. Korespondence 1:1 ale není nutná. Widget může používat několik X oken. Naopak widgety, které nepotřebují přijímat události – např. statický text – nemají vlastní X okno. Pro kreslení používají X okno rodičovského widgetu.

X server posílá klientovi informace o aktivitě uživatele ve forměudálostí. Události se zpracovávají asynchronně. Příchozí událost je uložena do fronty. Když na ni někdy později přijde řada, hlavní cyklus zpracování událostí ve funkci gtk_main ji vyzvedne a zpracuje. Widgety komunikují pomocí signálů. Když se s widgetem stane něco důležitého, na co je potřeba reagovat (např. uživatel vybere položku menu), widget emituje signál voláním g_signal_emit_by_na­me. Následně jsou postupně zavolány všechny handlery registrované pro danou dvojici zdrojový widget, typ signálu. Signály se zpracovávají synchronně – funkce emitující signál zavolá všechny příslušné handlery a teprve pak se vrátí. Události jsou funkcí gtk_main přeloženy na signály a následně obslouženy. Handlery signálů dostávají jako parametr widget, který signál emitoval, a ukazatel nastavený při registraci handleru funkcí g_signal_connect. Některé typy signálů používají další parametry. Například handler událostí dostává strukturu popisující událost. Handlery mohou mít i návratovou hodnotu. Handlery událostí vracejí boolovskou hodnotu. TRUE znamená, že událost byla obsloužena a není třeba volat další handlery. Když handler vrátí FALSE, událost se předá dalšímu handleru v pořadí. GTK+ nekontroluje, zda má handler správné typy parametrů a návratové hodnoty. Pokud programátor použije špatný typ handleru, obvykle to skončí havárií programu.

Hello World!

Při výkladu nového programovacího jazyka nebo nástroje se obvykle začíná triviálním programem ve stylu „hello world“. Ani my neuděláme výjimku. Celý zdrojový kód programu je v souboru hello.c. Program otevře okno s tlačítkem a po stisknutí tlačítka skončí.

Na začátku je potřeba načíst hlavní hlavičkový soubor GTK+. Ten zajistí načtení všech dalších potřebných hlavičkových souborů GTK+ a podpůrných knihoven.

#include <gtk/gtk.h>

Funkce main musí na začátku inicializovat toolkit pomocí

gtk_init(&argc, &argv);

Této funkci se předávají parametry z příkazového řádku. GTK+ odebere parametry, které zná, např. –display (jméno X serveru, k němuž se má připojit), –no-xshm (nebude se používat sdílená paměť pro komunikaci s lokálním X serverem) nebo –sync (synchronní komunikace s X serverem používaná pro ladění). Funkce gtk_init inicializuje objektový systém GTK+ a musí se volat jako první funkce toolkitu.

Následuje vytvoření top-level okna

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

Parametr GTK_WINDOW_TOPLEVEL znamená, že okno dostane rámeček a bude spravováno window managerem. Hlavní okno bude mít připojené dva handlery signálů.

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

Při registraci handlerů se zadává dvojice objekt a jméno signálu, pro kterou se handler registruje. Následuje handler a hodnota, předávaná handleru jako poslední parametr. Handler dostane ukazatel na objekt jako první parametr. Za ukazatelem na objekt mohou ještě následovat parametry specifické pro určitý signál. Ve volání g_signal_connect se objevuje charakteristický rys GTK+ – volání přetypovacích maker. Makro G_OBJECT přetypuje parametr na GObject. Pro každou třídu existuje makro, které dostane ukazatel, zkontroluje, zda lze přetypovat, a vrátí přetypovaný ukazatel nebo vypíše chybové hlášení. Takto se většinou přetypovává nějaký objekt na objekt předka, což se v C++ děje automaticky. C nezná hierarchii dědičnosti a odvozená třída, i když obsahuje objekt zděděné třídy na začátku struktury, je pro překladač jiným typem a přetypování je nutné explicitně napsat.

Událost delete_event vygeneruje window manager, když uživatel stiskne zavírací tlačítko na rámečku okna. Obvyklá reakce na tuto událost je taková, že se program zeptá uživatele, zda chce skončit a popřípadě uložit na disk neuložená data. Když uživatel potvrdí ukončení programu, handler vrátíFALSE, a tím umožní pokračování zpracování události. Když nejsou připojené další handlery, vyvolá se standardní obsluha, která zruší top-level okno. Jestliže se uživatel rozhodne, že se program nemá ukončit, handler vrátí TRUE, událost je tím obsloužena a další handlery se nevolají. Handler našeho „hello world“ pouze vypíše hlášení na standardní výstup a nechá program pokračovat.

static gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
    g_print("delete event occurred\n");
    return TRUE;
} 

Widget generuje signál destroy při svém zrušení. Když je zavřeno hlavní okno aplikace, je potřeba přerušit zpracování událostí. Jinak by sice program zmizel z obrazovky, ale běžel by dál. O ukončení se postará handler

static void destroy(GtkWidget *widget, gpointer data)
{
    gtk_main_quit();
} 

V dalším kroku vytvoříme tlačítko s nápisem „Hello World“ a vložíme ho do okna.

button = gtk_button_new_with_label("Hello World");
g_signal_connect_swapped(G_OBJECT(button), "clicked",
                         G_CALLBACK(gtk_widget_destroy), G_OBJECT(window));
gtk_container_add(GTK_CONTAINER(window), button); 

Když uživatel stiskne tlačítko, ať už s použitím klávesnice, nebo myši, je vygenerován signál clicked. Jako handler jsme nastavili funkci gtk_widget_destroy, která zruší widget zadaný jako parametr. Protože chceme zrušit celé okno a ne jenom tlačítko, voláme g_signal_connec­t_swapped. Tato funkce pracuje podobně jako g_signal_connect, ale závěrečný parametr se předává handleru jako první a ukazatel na objekt, který emitoval signál, jako poslední parametr. O parametry funkce se v C stará volající, proto nevadí, že handler má jen jeden parametr a při volání dostane dva. Důležité je, že souhlasí typ prvního parametru. Podobně bychom nemuseli definovat handler destroy a místo něj bychom připojili přímo funkcigtk_main_qu­it.

Každý widget musíme zobrazit, jinak by sice existoval jako objekt v programu, ale nebyl by vidět na obrazovce.

gtk_widget_show(button);
gtk_widget_show(window);

Místo volání gtk_widget_show pro každý widget můžeme zobrazit widget a všechny v něm obsažené widgety funkcí gtk_widget_show_a­ll. Předchozí dva řádky bychom mohli změnit na

gtk_widget_show_all(window);

Nakonec spustíme zpracování událostí

gtk_main();

Když je tato funkce ukončena pomocí gtk_main_quit v handlerudestroy volaném při zavření okna, ukončíme program návratem z funkce main.

Překlad a linkování programu

Náš první program v GTK+ přeložíme příkazem

gcc -o hello `pkg-config --cflags --libs gtk+-2.0` hello.c 

Utilita pkg-config se postará o správné nastavení kompilátoru a linkeru. Když se zavolá s přepínačem –cflags, vrátí seznam cest, kde jsou hlavičkové soubory pro GTK+ nebo pro jinou knihovnu, jejíž jméno je zadáno jako poslední parametr. Verze GTK+ 2.2 opravuje chyby předchozí verze 2.0 a jinak je s ní kompatibilní, proto v pkg-config obě verze používají název gtk+-2.0. Seznam cest se vypisuje ve tvaru direktiv kompilátoru -Iadresář. Přepínač –libs způsobí vypsání seznamu potřebných knihoven a adresářů, kde jsou knihovny uloženy (direktivy -Ladresář a -lknihovna).

Pro řízení překladu větších programů složených z více zdrojových modulů se obvykle používá utilita make. Úsek Makefile pro náš první program v GTK+ by vypadal nějak takto:

CC=gcc
CFLAGS=`pkg-config --cflags gtk+-2.0`
LIBS=`pkg-config --libs gtk+-2.0`

hello: hello.o
        $(CC) -o hello $(LIBS) hello.o

hello.o: hello.c

        $(CC) $(CFLAGS) -c hello.c

Knihovny používané v GTK+

Na obr. 1 jsou schematicky znázorněny vztahy mezi hlavními knihovnami.

knihovny
Obr. 1: Knihovny v GTK+

root_podpora

Nad základní klientskou knihovnou Xlib leží vrstva GDK (GTK Drawing Kit). Takto knihovna obaluje volání Xlib a má dvě hlavní funkce. Za prvé poněkud zjednodušuje komplikované API Xlib a za druhé usnadňuje přenositelnost na jiné grafické systémy než X (např. MS Windows nebo linuxový frame buffer), protože se mění pouze implementace funkcí GDK a nikoliv API. Proto mohou vyšší vrstvy toolkitu zůstat téměř nezměněné. Další vrstvu tvoří vlastní toolkit GTK+. Skládá se z implementace objektového systému, podpory zpracování událostí a signálů a tříd pro jednotlivé widgety. Aplikace volá funkce z GTK+. Pokud potřebuje přímo kreslit vlastní grafiku, definovat nové třídy widgetů nebo provádět speciální operace s okny, může volat i GDK. Mezi knihovnami GTK+ a GDK leží ještě theme engine. Je v ní definován vzhled jednotlivých widgetů. Implementace widgetů v GTK+ nevolají přímo funkce pro kreslení grafických primitiv (např. čar nebo obdélníků) z GDK. Místo toho volají funkce z theme, které kreslí složitější objekty, např. celé tlačítko. Theme engine překládá tyto komplexní kreslicí operace do série grafických primitiv. Výměnou této knihovny – i za běhu programu – lze měnit celkový vzhled uživatelského rozhraní.

Balík GTK+ obsahuje tyto hlavní knihovny:

  • GLib (-lglib-2.0) … pomocné funkce a datové struktury
  • GDK (-lgdk-x11–2.0) … wrapper funkcí Xlib, popř. jiného grafického systému
  • GObject (-lgobject-2.0) … generický typový a objektový systém, signály
  • GTK+ (-lgtk-x11–2.0) … jádro toolkitu GTK+ a definice widgetů
  • Pango (-lpango-1.0) … zobrazování textu
  • ATK (-latk-1.0) … Accessibility Toolkit, zpřístupnění aplikací v GTK+ pro tělesně postižené uživatele

Byl pro vás článek přínosný?