Programování pro X Window System (3)

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

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
V dnešním dílu si ukážeme hierarchické uspořádání widgetů. Dále probereme základní manipulaci s widgety - vytvoření, zobrazení, zrušení. S tím souvisí i správa paměti pomocí počítání referencí. Nakonec se podíváme na kontejnerové widgety, především na boxy a tabulky.

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.

top-level okno
Obr. 1: Příklad top-level oknastrom widgetů 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.

strom tříd
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_wit­h_label vyrobí tlačítko s nápisem. Další možnosti jsou tlačítko s akcelerátorem

gtk_button_new_wit­h_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_widget_show a gtk_widget_hide. Existenci widgetu – včetně GDK a X okna – ukončí funkce gtk_widget_destroy.

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_we­akref. 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_con­tainer_remove) zruší kontejner svou referenci. Pokud nechceme, aby se vyjmutý widget zrušil, musíme před voláním gtk_container_re­move 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_si­ze_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_si­ze_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_si­ze_allocate.

Některé funkce jsou společné pro všechny kontejnery a jsou definovány ve třídě GtkContainer.

void gtk_container_ad­d(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_re­move(GtkConta­iner *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_bor­der_width(GtkCon­tainer *container, guint border_width);
Nastaví šířku okraje (v pixelech), tj. oblasti, do níž nebudou zasahovat vložené widgety.
void gtk_widget_set_si­ze_request(GtkWid­get *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í.

box
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ů

hacking_tip

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); 

tabulka
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.

Autor článku

Vystudoval informatiku na MFF UK v Praze, kde následně několik let učil programování v Unixu. Poté se dlouhá léta věnoval síťové bezpečnosti a programování firewallů. V současnosti se zabývá vývojem interních backendových systémů ve společnosti Gen (dříve Avast).