Vala 4: grafické rozhraní GTK+

>V dnešním díle se podíváme na grafické rozhraní GTK+. Jde o jednu ze dvou největších grafických knihoven v unixovém světě. Zatímco QT začíná u C++, tak doménou GTK+ je stále ANSI C. To ale neznamená, že by nemělo podporu dalších programovacích jazyků. Vedle C++, Javy nebo C# v podobě projektu Mono, je to právě rozhraní pro jazyk Vala. Ten by se dal postavit někam mezi C++ a C#.

23.2.2014 15:00 | Ondřej Tůma | přečteno 16176×

O grafické knihovně GTK+ se nechci příliš rozepisovat, na to je k dispozici i českým čtenářům poměrně hodně zdrojů. Nicméně základní úvod a představení termínů považuji za drobnou nutnost.

Celá knihovna je postavena nad GObject, resp. nad GLib knihovnou. Tedy vše co jsem psal o GObject systému platí i pro GTK+. Základním rozdílem je pak právě použití grafického rozhraní, to sice není nutné použít, ale pak použití grafické knihovny ztrácí význam. GTK+ samo o sobě nepoužívá jen GLib, ale ředu dalších důležitých knihoven, které jsou programátorům v podobě API více či méně odkryty. Mezi ty nejdůležitější, na které můžete narazit patří GLib, GIO, Pango, Cairo, GDK a ATK.

architektura GTK+
Struktura GTK+ zdroj: gtk.org

Celé GTK+ pak může běžet nad různými grafickými rozhraními. Tato knihovna je zároveň multiplatformní, díky tomu můžete najít GTK+ na Linuxu, Windows, MacOS nebo libovolném unixovém systému. GTK+ umí běžet nad Xserverem, Waylandem, Frame Bufferem nebo dokonce jako HTML aplikace. A samozřejmě její vzhled nastavit a rozšiřovat. Některé aplikace tak ani nevypadají že GTK+ používají.

GTK+ pro programátora

V GTK+ se setkáte s několika druhy objektů. Nejjednodušší struktury popisují různé základní vlastnosti. Například barvu (Gdk.Color), definice čtverce (Gdk.Rectangle nebo Cairo.Rectangle), událost (Gdk.Event) a podobně. Složitější struktury, sloužící k dalšímu zpracování jsou popsány třídami. Jde v podstatě o rozsáhlé datové typy, proto je vhodné je držet v paměti jako třídy. Jsou to například Pango.Font, Gtk.Settings, Gtk.Acction, File.Filter a další. Poslední sadou objektů jsou tzv. Widgety. Jsou to takové objekty, které již obsahují konkrétní vizualizaci. Tyto widgety jsou základní stavební prvky GTK+ knihovny. Interně a nejen interně se tedy velmi často setkáte s tím, že stačí pouze informace o tom, že objekt, se kterým se dál pracuje je potomek třídy Gtk.Widget. Do této skupiny patří ikony, tlačítka, menu ale i tzv. kontejnery, jako okna, panely a záložky resp. kartotéka.

Celou hierarchii grafických prvků může programátor skládat ručně. Konečně v mnoha případech je to jediná možnost. GTK+ však má, stejně jako ostatní knihovny, nástroj na „malování“ aplikace. Tímto nástrojem je Glade. Ten se postupem času značně vyvinul a v poslední verzi je to „malovátko“, ve kterém si programátor poskládá aplikaci ze všech dostupných widgetů, které mu GTK+ nabízí. Výstup v podobě XML souboru pak lze snadno nahrát a používat v aplikaci. I tento způsob práce dostane v seriálu prostor.

Události

Než se pustíme do vytouženého příkladu, zastavíme se u událostí. Na většině grafických rozhraní, které znám jsou události základním prvkem interakce rozhraní s uživatelem. Aby také ne. To uživatel často kliká na tlačítka, scrolluje v editoru, pohybuje kurzorem a očekává, že aplikace na jeho akci bude reagovat. Vedle uživatelských událostí, má však nejen GTK+ mnoho dalších interních, programových, knihovních a aplikačních událostí. Tyto události jsou generovány například při vytvoření, zobrazení, nebo v době ničení objektu. Další obdobné události jsou vyvolány při manipulaci s tzv. dětmi atd.

Každá událost může vyvolat nějakou akci, může být jedna, nebo jich může být více. A právě na tyto události je Vala už připravena. V prvním díle tohoto seriálu jsem se krátce zmínil o tzv. signálech. Signály umožňují programátorovi tvořit asynchronní aplikaci, bez ohledu na to, zda používá nějakou pokročilejší techniku paralelního běhu. Tyto signály jsou programátorským ekvivalentem nějaké události. Programátor může tyto události různě používat. Přetěžovat, zastavovat, vyvolávat ale především na ně může reagovat.

Než se pustíme do první grafické aplikace, rád bych využil této subkapitoly k ukázce použití signálů v konsolové aplikaci. Signály budou fungovat vždy, jsou pouštěny sériově tak, jak jsou vyvolány. Aplikaci však lze rozšířit pomocí tzv. smyčky, díky které máme další možnosti.

Nejprve obyčejná verze:
class SignalTester : GLib.Object {
    // pouze vlastnosti s avizovaným set a get přístupem generují událost notify
    public string property { set; get; }

    public SignalTester() {
        notify.connect(debug);          // vytvoření události zavolá metodu debug
    }

    private void debug(GLib.ParamSpec p) {
        stdout.printf("Vlastnost '%s' byla změněna\n", p.name);
                                        // po dvou vteřinách zavolá lambda funkci
    }

    public static int main (string[] args) {
        SignalTester tester = new SignalTester();
        tester.property = "property je vlastnost";
        tester.property = "property je vlastnost";

	return 0;
    }
}
soubor: signal.vala

Všimněte si co program udělá. Každé nastavení vlastnosti s názvem property způsobí zavolání metody debug.

Poté rozšířená verze:
class SignalTester : GLib.Object {
    // pouze vlastnosti s avizovaným set a get přístupem generují událost notify
    public string property { set; get; }

    public SignalTester() {
        notify.connect(debug);          // vytvoření události zavolá metodu debug
    }

    private void debug(GLib.ParamSpec p) {
        stdout.printf("Vlastnost '%s' byla změněna\n", p.name);
                                        // po dvou vteřinách zavolá lambda funkci
        GLib.Timeout.add (2, (() => {
            stdout.printf("2 vteřiny timeout ...\n");
            return false;
        }));
    }

    public static int main (string[] args) {
        var loop = new GLib.MainLoop();
        var time = new TimeoutSource(10000);        // 10 vteřin

        time.set_callback(() => {
            stdout.printf("Nashledanou!\n");
            loop.quit();                            // ukončí smyčku
            return false;
        });
        time.attach(loop.get_context());

        SignalTester tester = new SignalTester();
        tester.property = "property je vlastnost";
        tester.property = "property je vlastnost";

        loop.run ();
	return 0;
    }
}
soubor: signal2.vala

Druhá verze umožňuje v podstatě „paralelní“ běh. Všimněte si, že po metoda debug dvakrát načasovala volání další, tentokrát lambda metody.

Tato ukázka byla jen nástin toho, co budeme v grafické aplikaci řešit. Proto, že jsou události neoddělitelnou součástí GTK+, budeme se jimi ještě mockrát zabývat.

Hello gtk+ world

Začneme rovnou kódem:
public class Application : GLib.Object {            // obecná třída Application, vlastně není potřeba

    public static int main (string args[]) {        // standardní statická metoda main
        Gtk.init (ref args);                        // inicializace GTK+

        var window = new Gtk.Window ();             // vytvoří nové okno
        window.destroy.connect (Gtk.main_quit);     // při jeho zavření se ukončí Gtk

                                                    // Nové tlačtko s nápisem
        var button = new Gtk.Button.with_label ("Klikni na mě!");
        button.clicked.connect ((w) => {            // po kliku na tlačítko se vypíše jeho název
            stdout.printf ("kliknuto na %s\n", w.name);
        });

        window.add (button);                        // tlačítko se přidá do okna
        window.show_all ();                         // vše v okně, včetně se zobrazí

        Gtk.main ();                                // pustí se GTK+ smyčka - až tady se zobrazí GTK+ okno
        return 0;
    }
}
soubor: hello_gtk_world.vala

GTK je obsaženo v gtk+ balíčku. Náš seriál bude popisovat již verzi 3.x, nicméně v mnoha případech bude kód přeložitelný i s verzí 2.x. Při kompilaci je tedy nutné uvést balíček.

$ ~ valac hello_gtk_world.vala --pkg gtk+-3.0

V závislosti na verzi GTK+, Vala kompilátoru a C kompilátoru je možné, že kompilace bude generovat různé množství varování z C kompilátoru. Tyto varování jsou známé, a spočívají v nedokonalém generování zdrojového C kódu. Ten se generuje na základě vapi souborů příslušných knihoven, a ty se s knihovnami občas rozchází. Chyby jsou známé a v dalších verzích Valy jsou opraveny.

Probereme si ještě co kód dělá. Veřejná třída Application zde vlastně není potřeba, je to jen hezký ~ správný zápis Vala aplikace. V dalších příkladech již ale bude tato třída používána tím typičtějším způsobem.

Než začneme cokoli dělat s widgety, je nutné inicializovat samotné GTK+ metodou init. Té předáváme parametry příkazové řádky, to proto, že samotné GTK+ některým parametrům rozumí. Na unixovém systému si můžete vyzkoušet parametr --display, který umožní spustit aplikaci na jiném Xserveru.

V druhé fázi vytvoříme widget okna. Okno má mnoho událostí, jedna z nich se jmenuje destroy a je volaná v době, kdy uživatel zavírá okno ze správce oken. Tedy kliknutím na křížek v pravé části panelu správci oken, nebo kombinací kláves Alt + F4, či jiným způsobem, typickým pro daného správce oken. Na tuto událostí navěsíme metodu Gtk.main_quit (). Tato metoda po sobě uklidí, ale především ukončí hlavní smyčku Gtk.main ().

Pokračujeme vytvořením tlačítka. V příkladu používáme jeden z konstruktorů, jenž na vstupu očekává text, který bude vidět na tlačítku. A i na událost tlačítka, tentokrát s názvem clicked, navěsíme naší funkci. V jednom z minulých dílů jsme si říkali o lambda funkcích. K nim se jistě ještě mnohokrát dostaneme, pro začátek nám stačí, že je to takový inline způsob, jak pustit nějaký náš kód bez toho, aniž bychom vytvářeli další metodu. Na vstupu je volitelný parametr typu Gtk.Widget. A každý widget má své jméno, můžeme jej proto vytisknout na standardní výstup.

Aby se tlačítko někde objevilo, je třeba jej přidat do okna. Třída Gtk.Window je potomkem třídy Gtk.Container. Potomci této třídy mohou obsahovat další prvky, někdy jeden, někdy dva, někdy několik. Ty nejdůležitější si představíme v dalších dílech seriálu. Po přidání je důležité tyto prvky zobrazit. To se dělá voláním metody show () nebo show_all (). Tyto metody jsou velmi podobné. Metoda show () zobrazí pouze daný widget, show_all () zobrazí widget, a všechny děti – tedy takové widgety, jenž byly do prvku přidány. Aby byly widgety vidět, musí být vidět i jejich rodiče – kontejnery. To si můžete sami vyzkoušet a doufám že to také uděláte.

Na závěr je nutné zavolat metodu Gtk.main (). Jak název napovídá, tato metoda spustí hlavní GTK+ smyčku, ve které aplikace žije. Na této metodě se aplikace „zastaví“, dokud není zavolána metoda Gtk.main_quit ().

Co nás čeká příště

Příště si představíme některé z kontejnerů a probereme si jak je to s jejich používáním. Také se podíváme na některé další zajímavé Widgety které by se nám mohli hodit. Na závěr bych rád upozornil na diskuzi pod článkem, kterou bych byl rád, pokud budete využívat. Pomůžete mě se tak zaměřit se místa, která Vás trápí.

Stránky projektu GTK+
Oficiální tutoriál k Vale
Tutoriál signálů ve Vale
Vala dokumentace Gtk balíčku
Vala dokumentace třídy Gtk.Widget
Vala dokumentace třídy Gtk.Window

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=1999