Přehled widgetů
Dnes se podíváme, jaké widgety jsou v GTK+ k dispozici. Nebudeme se příliš pouštět do podrobností, protože třídy widgetů mají často desítky metod a podrobný výklad by byl příliš dlouhý a asi také dost nudný. Proto se zaměříme jen na nejdůležitější vlastnosti widgetů. Detaily je možné nalézt v referenční dokumentaci GTK+. Ukázkové programy demonstrující jednotlivé widgety se dají najít na http://www.gtk.org/. Další programy jsou na adrese http://www.ms.mff.cuni.cz/~beran/vyuka/X/prog/gtk/.
Widget potřebuje X okno, aby mohl kreslit na obrazovku a přijímat události. Některé widgety nepotřebují dostávat události od X serveru. Aby se šetřily systémové zdroje X serveru, nemají takové widgety vlastní X okno a pro kreslení používají okno rodičovského widgetu. Pro testování, zda má widget X okno, se používá makro GTK_WIDGET_NO_WINDOW. Podobných maker pro zjištění vlastností widgetů existuje více. GTK_WIDGET_IS_SENSITIVE testuje, jestli bude widget reagovat na podněty uživatele. Při programování GUI je obvyklé, že s prvky rozhraní, např. položkami menu, které odpovídají dočasně nepřístupným funkcím programu, nemůže uživatel pracovat. Takové widgety jsou i graficky odlišeny, standardně jsou zobrazeny nevýraznou šedou barvou. Vypínat a zapínat interakci widgetu s uživatelem umožňuje funkce gtk_widget_set_sensitive. Chování widgetů ovlivňují ještě jiné příznaky, např. focus (widget bude dostávat vstup z klávesnice), nebo default (widget zareaguje na klávesu Enter, i když má focus jiný widget, obvykle se používá pro tlačítko OK v dialogu). S příznaky manipulují makra GTK_WIDGET_SET_FLAGS aGTK_WIDGET_UNSET_FLAGS.
Téměř v každé aplikaci používající GTK+ se vyskytují tlačítka. Obyčejná tlačítka typu GtkButton se vyskytují především v dialozích, slouží pro uzavření dialogu a potvrzení (OK), resp. zrušení (Cancel) nastavených hodnot. Tlačítko obvykle obsahuje nějaký text nebo ikonu. Nicméně je to kontejner – potomek GtkBin – proto může obsahovat libovolný widget, např. hbox s ikonou a textem. Tlačítko s textem se vytvoří funkcí
GtkWidget* gtk_button_new_with_mnemonic(const gchar *label);
Znak, před kterým je v label podtržítko ‚_‘, bude podtržený a bude fungovat jako akcelerátor (spolu s klávesou Alt). Při stisku tlačítko generuje signál „clicked“. Z obyčejného tlačítka je odvozené
GtkToggleButton, které se přepíná mezi dvěma stavy (stisknuto/nestisknuto) a přitom generuje signál „toggled“. Aktuální stav tlačítka se dá zjistit pomocí gtk_toggle_button_get_toggled a nastavit voláním gtk_toggle_button_set_toggled. Variantou toggle button je GtkCheckButton, které se liší pouze vzhledem. Z něho je dále odvozené GtkRadioButton. Tento typ tlačítek se používá ve skupinách propojených pomocí spojového seznamu GSList definovaného v knihovně GLib. Vždy pouze jedno tlačítko ze skupiny je aktivní. Vzhled jednotlivých typů tlačítek je vidět na obr. 1. Tlačítka
GtkRadioButton jsou na obrázku vložená ve vboxu a ten je v GtkFrame, což je kontejner zobrazující kolem vložených widgetů rámeček s nadpisem.
Obr. 1: Tlačítka
Obsah widgetu, jako je velký obrázek, tabulka nebo delší soubor v textovém editoru, se nezobrazí celý najednou. Pro rolování viditelné části obsahu se používá kontejner GtkScrolledWindow. Tento widget má automaticky horizontální (GtkHScrollbar) a vertikální (GtkVScrollbar) scrollbar. Některé widgety jako GtkTreeView neboGtkTextView počítají s podporou rolování a dají se do GtkScrolledWindow vložit přímo funkcí gtk_container_add. Jiné widgety, např. GtkImage, nejsou připraveny na rolování. Mezi takový widget a GtkScrolledWindow je třeba vložit GtkViewport voláním gtk_scrolled_window_add_with_viewport. Tyto dvě metody nelze zaměňovat. Pokud bychom pro GtkTreeView použili viewport, fungovalo by sice rolování seznamu, ale posunovaly by se i nadpisy sloupců, což není moc vhodné. Widget GtkScrolledWindow s vloženým obrázkem typu GtkImage ukazuje obr. 2.
Obr. 2: GtkScrolledWindow obsahující GtkImage
Pro komunikaci mezi scrollbary a scrolled window se používá GtkAdjustment. Není to widget, ale objekt, který udržuje parametry pro rolování. Lze k němu připojit libovolný počet rolovacích oken a scrollbarů. Posun jednoho z nich změní položku value a zavolá
gtk_adjustment_value_changed. Tato funkce způsobí poslání signálu „value_changed“. Signál dostanou ostatní widgety připojené k adjustmentu a upraví svůj stav. Při změně některé jiné položky adjustmentu – minimální nebo maximální hodnoty, řádkového posunu, stránkového posunu nebo velikosti stránky – je třeba volat gtk_adjustment_changed. Tím se vygeneruje signál „changed“ pro všechny widgety připojené k adjustmentu. Podobné scrollbarům jsou widgety GtkHScale a
GtkVScale. Používají se pro nastavení nějaké numerické hodnoty. Aktuální hodnota a přípustný interval jsou uloženy v adjustmentu. Widgety typu scrollbar a scale, všechny napojené na jeden adjustment, jsou na obr. 3.
Obr. 3: GtkScrollbar a GtkScale
Jeden z nejjednodušších widgetů je GtkLabel – statický text, který neinteraguje s uživatelem. Obvykle se používá pro popisky položek dialogů a jako součást jiných widgetů, např. pro nápisy na tlačítkách (GtkButton). Label může být i víceřádkový, s automatickým zalamováním řádků. V dialogových oknech se pro zadávání hodnot používají kromě již zmíněných tlačítek widgety typu GtkEntry pro vstup jednoho řádku textu. Entry podporuje obvyklé editační klávesy, označování textu a cut&paste pomocí myši. Ze základního GtkEntry je odvozenýGtkSpinButton. To je vstupní řádek specializovaný na zadávání čísel. Pro uložení aktuální hodnoty a povoleného intervalu používá adjustment. Vedle editačního řádku zobrazuje dvě šipky, pomocí nichž je možné hodnotu zvětšovat nebo zmenšovat. Další variantou je GtkCombo. V něm lze kromě napsání textu z klávesnice vybírat z rozbalovacího seznamu řetězců zadaného ve formě spojového seznamu GList. Příkazem
gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(combo)->entry), FALSE);
se dá zakázat editace textu a povolit pouze hodnoty v seznamu. Alternativně lze pro výběr řetězce ze seznamu použít widget GtkOptionMenu. V něm se možné hodnoty zadávají ve formě menu (GtkMenu připojené k widgetu pomocí gtk_option_menu_set_menu). Obrázek 4 ukazuje widgety typu entry, spin button, combo box a option menu.
Obr. 4: GtkEntry, GtkSpinButton, GtkCombo a GtkOptionMenu
Hlavní okno aplikace je obvykle widget typu GtkWindow. Window manager k němu přidá rámeček, který okno zvýrazňuje a obsahuje ikony pro zavření, minimalizaci, maximalizaci apod. Titulek v rámečku okna se nastavuje voláním gtk_window_set_title. V okně je typicky vbox. Do jeho horní části se umísťuje pruh menu (GtkMenuBar), pod něj toolbar (GtkToolbar v GtkHandleBox). Ve spodní části okna je stavový řádek (GtkStatusbar). Na obr. 5 je příklad takto koncipovaného hlavního okna aplikace, kde v hlavní části okna mezi toolbarem a stavovým řádkem je textový editor GtkTextView.
Obr. 5: Hlavní okno aplikace s menu, toolbarem, stavovým řádkem a textovým editorem
Menu je obvykle kombinace hlavního pruhu menu typu GtkMenuBar, při výběru některé položky se rozbalí svislé menu GtkMenu. Struktura rozbalovacích menu může mít více úrovní. Třída GtkMenu se používá i pro kontextové popup menu, typicky aktivované pravým tlačítkem myši, a jako seznam položek v GtkOptionMenu. Struktura menu v aplikaci se dá vygenerovat postupným vytvářením jednotlivých widgetů. Vodorovné menu vytvoří
gtk_menu_bar_new, jednotlivé jeho položky jsou typu GtkMenuItem a přidávají se pomocí gtk_menu_item_new_with_mnemonic a gtk_menu_shell_append. Svislé menu se vytváří funkcí gtk_menu_new. Položky se do něj vkládají stejně jako do GtkMenuBar. Připojení GtkMenu k položce widgetu GtkMenuBar nebo k položce GtkMenu vyšší úrovně zajistí funkce gtk_menu_item_set_submenu. Alternativní metodou vytvoření celé struktury menu je použití menu factory. V poli struktur
GtkItemFactoryEntry jsou definovány jednotlivé položky. Menu podle této definice vygeneruje posloupnost volání funkcístatic GtkItemFactoryEntry items[] = { ... };
GtkWidget *window, *bar;
GtkItemFactory *factory;
GtkAccelGroup *accel;
accel = gtk_accel_group_new();
factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<MenuFactory>", accel);
gtk_item_factory_create_items(factory, sizeof(items)/sizeof(*items),
items, (gpointer)"callback_data");
gtk_window_add_accel_group(GTK_WINDOW(window), accel);
bar = gtk_item_factory_get_widget(factory, "<MenuFactory>");
Do toolbaru se vkládají tlačítka pro aktivaci nejpoužívanějších funkcí programu. Každé tlačítko obsahuje volitelně text, ikonu, nebo oboje. Kromě obyčejných tlačítek se dají do toolbaru vložit i toggle button, radio button nebo libovolný widget. V mnoha aplikacích se dá toolbar myší „utrhnout“ z okna. Odtržený toolbar se stane samostatným top-level oknem. Přesunutím na původní místo se toolbar připojí zpět do okna. Tuto funkci zajišťuje kontejner GtkHandleBox. Lze do něj sice vložit jakýkoliv widget, ale obvykle se do něj přidává toolbar.
Stavový řádek slouží k zobrazování různých zpráv pro uživatele. Obsahuje zásobník zpráv, v němž lze přidávat a mazat. Zobrazena je vždy zpráva na vrcholu zásobníku, tj. poslední vložená nesmazaná zpráva. Platí pravidlo, že ve stavovém řádku se zobrazují méně závažná hlášení, protože je uživatel může snadno přehlédnout. Pro důležité zprávy je lepší použít samostatné modální dialogové okno.
K prvkům uživatelského rozhraní je užitečné přidat tooltipy, tj. krátké vysvětlující texty zobrazované, když se na widgetu po určitou dobu zastaví kurzor myši. Funkce gtk_tooltips_new vytvoří skupinu tooltipů typu GtkTooltips. Jednotlivé tooltipy pro různé widgety se vytvářejí a přidávají do skupiny pomocí gtk_tooltips_set_tip. Na obr. 6 je tooltip připojený k tlačítku v toolbaru.
Obr. 6: Toolbar v handle boxu se zobrazeným tooltipem
Při programování GUI platí zásada, že když program dělá nějakou déle trvající akci a v jejím průběhu neinteraguje s uživatelem, měl by to dát vizuálně najevo. Pro operace o délce řádově jednotek sekund se hodí změnit kurzor na „hodiny“. Pro delší operace je lepší použít progress bar GtkProgressBar. Ten ukazuje odhad, kolik už je hotovo a kolik zbývá do konce. Odhad by měl být dostatečně přesný. Velmi špatně působí, když během dvou sekund na progress baru naskočí 98% a zbývající dvě procenta trvají půl hodiny. Když nelze zbývající čas dostatečně správně odhadnout, je lepší progress bar přepnout do „activity mode“. Pak se zobrazuje pouze kmitající blok jako indikace, že se stále něco děje a program nezamrzl. Obě varianty progress baru jsou na obr. 7.
Obr. 7: Progress bar
Pro zobrazení a editaci textu existuje v GTK+ widget GtkTextView. GTK+ používá interně při práci s textem Unicode v kódování UTF-8. V knihovně GLib jsou funkce pro překódování mezi UTF-8 a aktuálním locale. Text není uložen přímo ve widgetu GtkTextView, ale je v samostatném objektu typu GtkTextBuffer. Kromě těchto dvou tříd se při práci s textem používají ještě iterátory GtkTextIter, značky GtkTextMark a tagy GtkTextTag. Iterátor je ukazatel na znak používaný při různých operacích s textem. Důležité je, že při jakékoliv změně textu se iterátor zneplatní. Naopak značka zůstává platná i po změně obsahu textového bufferu. Je to jakýsi virtuální, neviditelný kurzor. Značky mají jména,„insert“ je pozice viditelného kurzoru, „selection_bound“ je druhý konec vybrané části textu. Tag definuje atributy úseku textu, např. font, barvu písma nebo pozadí. Ukázka widgetu GtkTextView je na obr. 5.
Widget GtkTreeView zobrazuje seznam – posloupnost řádků, data v řádcích jsou uspořádána do sloupců, všechny položky ve stejném sloupci mají stejný typ. Stejný widget umí zobrazovat i stromovou strukturu, uživatel může pomocí myši otevírat jednotlivé úrovně stromu. Zobrazování seznamů a stromů je rozděleno na čtyři části. Hlavní je widget GtkTreeView. K němu je pro každý sloupec vytvořen objekt GtkTreeViewColumn, který obsahuje informace o položkách v tomto sloupci: nadpis sloupce, renderer a specifikace položky (prvku datové struktury odpovídající celému řádku) zobrazené ve sloupci. Renderer je potomek třídy GtkCellRenderer. Umí nakreslit jednu položku v jednom řádku. Model (GtkListStore neboGtkTreeStore) je datová struktura, v níž jsou uložena data určená k zobrazení. Na obr. 8 je ukázka seznamu i stromu. Dva widgety GtkTreeView jsou vloženy v kontejneru GtkHPaned, jenž své dva synovské widgety zobrazuje v oddělených panelech. Velikost panelů může uživatel měnit posunem přepážky mezi nimi.
Obr. 8: Strom a seznam GtkTreeView ve dvou panelechGtkHPaned
Zatím jsme se nezmínili o několika užitečných kontejnerových widgetech. Jedním z nich je GtkEventBox. Používá se v případě, že máme widget bez X okna (např. GtkLabel) a potřebujeme v něm přijímat události. Řešením je takový widget vložit do event boxu, což je widget, který sám o sobě nic nedělá, ale má X okno. Proto může dostávat události od X serveru. Pro skupiny tlačítek se používají třídy GtkHButtonBox aGtkVButtonBox. Jsou to boxy, u kterých lze buď jednotlivě, nebo pro všechny button boxy v celé aplikaci nastavovat parametry pro rozmístění synovských widgetů. GtkAligment dovoluje pro synovský widget nastavovat pozici a velikost relativně vzhledem ke kontejneru. GtkFixed slouží pro vkládání synovských widgetů na pevně zadané souřadnice. V dialogových oknech s mnoha položkami se často používá GtkNotebook. Je to kolekce stránek se záložkami. Kliknutím na záložku lze vybrat určitou stránku. Na obr. 9 je ukázka notebooku v dialogovém okně, kde je v aktuálně vybrané stránce vložen standardní widget GtkColorSelection pro nastavení barvy.
Obr. 9: Kolekce stránek (GtkNotebook)
Pro dialogová okna se používá widget GtkDialog. Dialog je top-level okno, proto je odvozen z GtkWindow. Dialogové okno má dvě části oddělené horizontální čárou (separátorem GtkHSeparator). Horní část (GtkDialog.vbox) je oblast pro vkládání widgetů pro zobrazení a zadávání dat, jako jsou GtkEntry, GtkCheckButton apod. Do spodní části (GtkDialog.action_area) se vkládají tlačítka pro uzavření dialogu jako „OK“, „Cancel“,
„Yes“ nebo „No“. Pokud jsou tlačítka vložena pomocí gtk_dialog_add_button nebo gtk_dialog_add_buttons, vygeneruje stisk tlačítka signál „response“ s parametrem (response ID) přiřazeným tlačítku při přidání do dialogu. Enumerace GtkResponseType definuje standardní hodnoty response. Dialogy jsou
modální nebo nemodální. Nemodální dialog je po vytvoření zobrazen stejně jako normální top-level okno aplikace pomocí gtk_widget_show_all. V handleru signálu „response“ se pak přečtou hodnoty nastavené uživatelem. Když je na obrazovce nemodální dialog, uživatel může pracovat i s ostatními okny programu. S modálními dialogy se pracuje pomocí funkce gtk_dialog_run.resp = gtk_dialog_run(GTK_DIALOG(dialog));
switch(resp) {
case GTK_RESPONSE_NONE: ...
case GTK_RESPONSE_OK: /* přečtení uživatelem nastavených hodnot */
case GTK_RESPONSE_CANCEL: ...
case GTK_RESPONSE_DELETE_EVENT: ...
}
gtk_widget_destroy(dialog);
Volání gtk_dialog_run zobrazí dialog a spustí rekurzivně cyklus zpracování událostí gtk_main. Stisk některého tlačítka nebo zrušení dialogu způsobí ukončení rekurzivní instance gtk_main a návrat z gtk_dialog_run s response ID odpovídajícím stisknutému tlačítku. Zatímco je modální dialog zobrazen, uživatel nemůže pracovat s žádným jiným top-level oknem programu. Výhoda modálních dialogů je, že vytvoření a zobrazení dialogu, čekání na stisk tlačítka a přečtení uživatelem nastavených hodnot lze zabalit do jedné funkce. U nemodálního dialogu je potřeba na jednom místě programu dialog zobrazit a jinde (v handleru signálu"response") přečíst hodnoty. Z pohledu programátora jsou modální dialogy pohodlnější, nicméně pro uživatele jsou omezující, protože blokují zbytek aplikace. Proto je vhodnější používat spíše nemodální dialogy. Příkladem situace, kdy je naopak lepší modální dialog, je zobrazení chybové zprávy, u které chceme mít jistotu, že ji uživatel nepřehlédne.
Seznam widgetů v tomto článku není vyčerpávající. V GTK+ jsou ještě další widgety jako standardní dialogy pro výběr jmen souborů, barev nebo fontů. Toolkit obsahuje také mnoho tříd, z nichž se obvykle nevytváří instance, ale jsou to bázové třídy pro widgety s podobnými vlastnostmi. Například z GTKBox jsou odvozené GtkButtonBox, GtkHBox aGtkVBox.