Hierarchické uspořádání widgetů a tříd
Widgety v GTK+ jsou hierarchicky uspořádané. Existují dvě hierarchie – widgetů a tříd. Widgety v programu jsou uspořádány do stromové struktury. V kořeni stromu je vždy top-level okno. Je to většinou hlavní okno aplikace nebo dialog. Program má tolik stromů widgetů, kolik má top-level oken. V uzlech stromu jsou widgety uvnitř top-level okna. Všechny uzly kromě listů jsou takzvané kontejnery, tedy widgety, do nichž lze vložit další jeden nebo více widgetů. Vzhled oken na obrazovce přímo odpovídá hierarchii widgetů. Okna potomků leží vždy uvnitř okna rodičovského widgetu. Na obr. 1 je příklad top-level okna programu a odpovídající strom widgetů je na obr2.
Obr. 1: Příklad top-level okna Obr. 2: Příklad stromu widgetů
Obrázek 2 není úplně přesný, protože objekty typu GtkButton a GtkCheckButton nejsou pravé listy. Ve skutečnosti jsou to kontejnerové widgety, které obvykle obsahují nápis typuGtkLabel.
Hierarchie widgetů je v každém programu jiná a dynamicky se mění podle toho, jak vznikají a zanikají jednotlivé widgety. Druhý typ hierarchie je daný vztahy dědičnosti mezi třídami widgetů. Společným předkem všech tříd je GObject definovaný ve stejnojmenné knihovně. V knihovně GTK+ je od GObject odvozená třída GtkObject a z ní dále GtkWidget, což je bázová třída pro všechny widgety. Základ stromu widgetů je vždy stejný, odlišnosti vznikají, pokud program definuje vlastní nové typy widgetů. Malý výřez hierarchie typů widgetů je na obr. 3.
Obr. 3: Část stromu tříd widgetů
Manipulace s widgety
Pro každou třídu existuje jedna nebo více funkcí pro vytvoření objektů této třídy. Např. prázdné tlačítko se vytvoří voláním gtk_button_new. Funkce gtk_button_new_with_label vyrobí tlačítko s nápisem. Další možnosti jsou tlačítko s akcelerátorem
gtk_button_new_with_mnemonic nebo předdefinované tlačítko s nápisem a ikonou gtk_button_new_from_stock. Zavoláním funkce pro vytvoření widgetu vznikne objekt uvnitř programu – X klienta. Aby byl widget vidět na obrazovce, je třeba ho realizovat, tj. vytvořit pro něj GDK okno a dále X okno, které existuje v X serveru. Realizaci provádí funkce gtk_widget_realize. Následně je widget zobrazen pomocí gtk_widget_show, popř. gtk_widget_show_all zobrazí widget včetně všech potomků. Tyto dvě funkce se postarají i o vytvoření X oken, proto není nutné explicitně volat gtk_widget_realize. Existující widget lze opakovaně zobrazovat a schovávat pomocí
GTK+ používá při správě paměti počítání referencí na objekty (reference counting). U každého objektu se pamatuje počet referencí na něj, tedy počet ukazatelů na objekt. Při uložení odkazu na objekt do nějakého ukazatele je třeba voláním funkce g_object_ref zvýšit o 1 počet referencí. Naopak, pokud se hodnota ukazatele změní, je nutné snížit počet referencí pomocí g_object_unref. Když počet referencí klesne na nulu, znamená to, že objekt nadále není dostupný přes žádný ukazatel. GTK+ takový objekt automaticky zruší. Reference counting nefunguje dobře pro cyklické datové struktury. Jestliže existuje několik objektů, které tvoří cyklus vzájemných odkazů, počet referencí na každý objekt v cyklu je vždy aspoň jedna, i když „zvenku“ na žádný z objektů neexistuje žádný odkaz a datová struktura jako celek je nedostupná. V takovém případě je možné některé ukazatele nezahrnovat do počtu referencí. Potom ovšem při zrušení objektu zbude neplatný ukazatel. Lepším řešením je používání tzv. slabých referencí (weak reference). Při nastavení hodnoty ukazatele se volág_object_weakref. Tím se zaregistruje funkce, která bude automaticky zavolána při zrušení objektu. Tato funkce může např. vynulovat ukazatel. Při změně hodnoty ukazatele je možné odregistrovat slabou referenci voláním g_object_weakunref.
Mechanismus počítání referencí poněkud komplikují tzv. plovoucí reference. Funkce pro vytvoření widgetu vrátí ukazatel na objekt, který představuje jednu referenci. Obvykle je nový widget následně vložen do kontejneru, který přidá další referenci. Při zrušení kontejneru většinou chceme automaticky zrušit i všechny v něm vložené widgety. Abychom nemuseli vždy po vložení widgetu do kontejneru volat g_object_unref, je první reference na objekt vytvořena jako plovoucí (floating). Kontejner tuto referenci zruší pomocí gtk_object_sink poté, co přidá vlastní referenci. Je třeba dávat pozor na to, že při vyjmutí widgetu z kontejneru (funkcígtk_container_remove) zruší kontejner svou referenci. Pokud nechceme, aby se vyjmutý widget zrušil, musíme před voláním gtk_container_remove použít g_object_ref. Takto vytvořená reference už není plovoucí, proto zůstane platná i při opětovném vložení widgetu do kontejneru. Plovoucí je vždy pouze první reference na objekt. První volání gtk_object_sink ji odstraní, opakovaná volání této funkce pro stejný objekt už nedělají nic.
Kontejnery
Kontejnery jsou widgety, do nichž lze vkládat jiné widgety (včetně dalších kontejnerů). Všechny kontejnery vycházejí ze společného předka GtkContainer. Dají se rozdělit do dvou hlavních skupin. Jednu skupinu tvoří třídy odvozené z GtkBin a vyznačují se tím, že mohou obsahovat maximálně jeden synovský widget, přístupný přes položku GtkBin.child. Asi nejdůležitějším zástupcem této skupiny je třída GtkWindow, tedy top-level okno. Do kontejnerů z druhé skupiny lze vložit více widgetů. Jejími nejpoužívanějšími členy jsou boxy (GtkHBox a GtkVBox) obsahující widgety uspořádané do řádku nebo sloupce a tabulky (GtkTable), které umísťují synovské widgety do dvojrozměrné mřížky.
Kontejnery zajišťují automatické přidělování místa pro jednotlivé widgety. Než se zobrazí top-level okno, zjistí GTK+ požadovanou velikost okna a všech jeho potomků. K tomu se používá metoda gtk_widget_size_request, která vrátí minimální widgetem požadovanou velikost. Kontejnery mají tuto metodu předefinovanou tak, že se nejprve zavolá pro všechny synovské widgety a z jejich požadavků kontejner spočítá, jak musí být velký, aby se do něj všichni potomci vešli. Např. horizontální box vrátí požadovanou šířku rovnou součtu šířek synovských widgetů plus velikost mezer podle parametrů boxu a výšku rovnou maximu výšek synovských widgetů. Následně GTK+ ve spolupráci s window managerem nastaví velikost top-level okna. Skutečná velikost se může lišit od požadované, např. pokud uživatel pomocí window manageru změnil rozměry okna. Nakonec GTK+ oznámí oknu skutečně přidělenou velikost voláním metody gtk_widget_size_allocate. V kontejnerech tato metoda rozdělí oblast widgetu mezi jednotlivé synovské widgety a výsledek rozdělení jim oznámí opět voláním gtk_widget_size_allocate.
Některé funkce jsou společné pro všechny kontejnery a jsou definovány ve třídě GtkContainer.
- void gtk_container_add(GtkContainer *container, GtkWidget *widget);
- Vloží widget do kontejneru. Obvykle se používá pro kontejnery odvozené z GtkBin. Každá kontejnerová třída má navíc definované vlastní funkce pro vkládání widgetů.
- void gtk_container_remove(GtkContainer *container, GtkWidget *widget);
- Odebere widget z kontejneru. Sníží o 1 počet referencí na widget, takže pokud jedinou referenci držel kontejner, bude widget zrušen.
- void gtk_container_set_border_width(GtkContainer *container, guint border_width);
- Nastaví šířku okraje (v pixelech), tj. oblasti, do níž nebudou zasahovat vložené widgety.
- void gtk_widget_set_size_request(GtkWidget *widget, gint width, gint height);
- Nastaví minimální velikost widgetu. Během procesu přidělování místa widgetům se bude rodičovský kontejner snažit nezmenšit widget pod tuto velikost. Funkce je definovaná pro všechny widgety, nejen kontejnery.
Horizontální (GtkHBox) a vertikální (GtkVBox) boxy jsou odvozené ze společného předka GtkBox. Pro vytvoření boxu slouží funkce
GtkWidget* gtk_hbox_new(gboolean homogeneous, gint spacing);
GtkWidget* gtk_vbox_new(gboolean homogeneous, gint spacing);
Parametr homogeneous říká, zda všechny synovské hodnoty budou stejně velké. Parametr spacing nastavuje velikost mezery (v pixelech) ponechané mezi každou dvojicí widgetů. Pro vkládání widgetů do boxů jsou definovány funkce
void gtk_box_pack_start(GtkBox *box, GtkWidget *child, gboolean expand,
gboolean fill, guint padding);
void gtk_box_pack_end(GtkBox *box, GtkWidget *child, gboolean expand,
gboolean fill, guint padding);
První z nich vkládá widgety od levého/horního okraje boxu směrem doprava/dolů, druhá vkládá od pravého/dolního okraje směrem doleva/nahoru. Parametr expand říká, zda widget může zabírat více místa, než je jeho minimální velikost. Pokud je nastaven na TRUE a fill je takéTRUE, bude widget zvětšen tak, aby zaplnil veškeré dostupné místo. Při fill rovném FALSE nebude widget zvětšen a případné nadbytečné místo se stane součástí mezer kolem widgetu. Parametr padding definuje šířku prázdného místa po obou stranách widgetu. Parametry ovlivňují pouze jeden rozměr vkládaných widgetů. Výška všech synů hboxu, resp. šířka synů vboxu, je stejná a je rovna výšce (šířce) boxu zmenšené o okraj.
Význam jednotlivých parametrů boxu je zobrazen na obr. 4. Pro experimentování s boxy lze využít program boxes.c, který nejdříve přečte ze standardního vstupu specifikace boxu a jeho synovských widgetů a následně box zobrazí.
Obr. 4: Parametry boxu
Tabulka (GtkTable) se vytvoří funkcí
GtkWidget* gtk_table_new(guint rows, guint columns, gboolean homogeneous);
Parametry definují počet řádků a sloupců a to, zda všechna políčka tabulky mají být stejně velká. Při vkládání widgetů pomocí
void gtk_table_attach_defaults(GtkTable *table, GtkWidget *widget,
guint left_attach, guint right_attach,
guint top_attach, guint bottom_attach);
se definuje, ve kterých sloupcích a řádcích budou ležet jednotlivé strany widgetu. Existuje i funkce gtk_table_attach, která umožňuje specifikovat ještě další parametry. Fungování tabulky si lze představit tak, že sloupce a řádky jsou pevné tyče, které se mohou volně pohybovat, ale nesmí si vyměnit pořadí. Jednotlivé widgety se připevňují svými okraji k tyčím. Každý widget je gumový obdélník, může být libovolně roztažen, ale nedá se stlačit pod svou minimální velikost. Po vložení všech widgetů se tyče představující sloupce a řádky rozmístí tak, aby žádný widget nebyl menší, než je jeho povolené minimum, a aby zároveň žádný widget nebyl větší, než je nezbytně nutné. Na obr. 5 jsou vlevo znázorněny minimální velikosti čtyř widgetů. Vpravo je tabulka, jež vznikne posloupností příkazů
table = gtk_table_new(3, 3, FALSE);
gtk_table_attach_defaults(table, widget1, 0, 3, 0, 1);
gtk_table_attach_defaults(table, widget2, 0, 1, 1, 3);
gtk_table_attach_defaults(table, widget3, 1, 2, 1, 2);
gtk_table_attach_defaults(table, widget4, 2, 3, 2, 3);
Obr. 5: Vytvoření tabulky
Automatické řízení rozměrů a pozic widgetů je výhodné, protože se programátor nemusí zabývat přesným umístěním jednotlivých widgetů. Navíc, když uživatel změní velikost okna, GTK+ na to zareaguje a rozumným způsobem upraví rozmístění widgetů v okně. Pro seznámení se s fungováním kontejnerů je užitečné spustit si program Glade a v něm si vyzkoušet vkládání widgetů do kontejnerů. Jeho velkou výhodou je, že lze interaktivně měnit parametry kontejnerů a okamžitě vidět na obrazovce, jak se změní vzhled okna.