Hlavní navigace

Programovací jazyk Ada: signály, akce a reakce uživatelského prostředí

30. 7. 2015
Doba čtení: 12 minut

Sdílet

V minulém díle jsme si ukázali a okomentovali kód jednoduchého příkladu v GtkAda. Vygenerovaný kód jsme velmi jednoduše upravili a ukázali si přidání dalšího widgetu. V dílu dnešním pak přidáme další widgety a hlavně se podíváme na možnosti vytvoření jejich reakcí na uživatelské podněty.

V minulé úpravě jsme přidali do aplikace jedno tlačítko (deklarovali jsme ale celkem tři). V dalším kroku doplníme další dvě deklarovaná tlačítka:

-- Add a button 2
Gtk_New(Button2,"Button2");
VBox.Add(Button2);

-- Add a button 3
Gtk_New(Button3);
Button3.Set_Label("Button3");
VBox.Add(Button3);

Ve widgetu Button3 je ukázka, jak jinak je možné nastavit jeho popis (řešení je zajímavé třeba tehdy, když se třeba mění popis uživatelsky v aplikaci). Překlad pořád ještě trucuje kvůli deklaraci HBox, ale proběhne a spuštěná aplikace je vidět na prvním obrázku v galerii. Nyní zkusíme tlačítka uspořádat trochu jinak a změníme proto kód následovně (jsou uvedeny jenom nové nebo změněné řádky):

Gtk_New_Hbox (HBox);

HBox.Add(Button1);
HBox.Add(Button2);
HBox.Add(Button3);

VBox.Add(HBox);

Při překladu už se žádné varování neobjeví a spuštěná aplikace je vidět na druhém obrázku. Poslední úpravou přidáme a zobrazíme tři editační pole a také tlačítko pro ukončení aplikace. Tato verze je závěrečnou přípravou na vložení kódu pro uživatelské akce a je v příloze main1.adb. Zde uvedeme zase pouze nové řádky:

with Gtk.GEntry;        use Gtk.GEntry;

Button1, Button2, Button3, Btn_Quit : Gtk_Button;
HBox1, HBox2 : Gtk_Hbox;
Edit1, Edit2, Edit3 : Gtk_GEntry;

Gtk_New_Hbox(HBox1);
Gtk_New_Hbox(HBox2);

HBox1.Add(Button1);
HBox1.Add(Button2);
HBox1.Add(Button3);

-- Add a Text Edit 1
Gtk_New(Edit1);
HBox2.Add(Edit1);

-- Add a Text Edit 2
Gtk_New(Edit2);
HBox2.Add(Edit2);

-- Add a Text Edit 3
Gtk_New(Edit3);
HBox2.Add(Edit3);

Gtk_New(Btn_Quit, "Quit Application");

VBox.Add(HBox1);
VBox.Add(HBox2);
VBox.Add(Btn_Quit);

Výsledek překladu a spuštění aplikace je vidět na třetím obrázku. Tím máme tedy vše připraveno na to, abychom mohli začít přidávat reakce na nějaké uživatelské podněty. Běžně se těmto úlohám říká zpracování signálů. Pod tímto názvem o nich najdeme informace i v uživatelské příručce GtkAda. Jako první by asi bylo vhodné implementovat funkci, která by aplikaci řádně ukončila akcí uživatele. Už v minulém dílu jsme zmínili, že pro řádné ukončení běžící grafické aplikace je nutné volat funkci Gtk.Main.Main_Quit. Z důvodů, které si objasníme později, tuto funkci nepoužijeme přímo. Místo toho vytvoříme novou proceduru:

procedure Quit_Handler (Btn_Quit : access Gtk_Button_Record'Class) is
begin
   Gtk.Main.Main_Quit;
end Quit_Handler;

Po překladu a spuštění se ale samozřejmě nic nového v aplikaci neobjeví. Musíme totiž nově vytvořenou funkci odněkud zavolat. Jako nejlepší se samozřejmě nabízí tlačítko pro ukončení aplikace. Proto do těla hlavní procedury přidáme tento řádek:

Btn_Quit.On_Clicked(Quit_Handler'Unrestricted_Access);

Při deklaraci funkce využíváme definovaný typ v knihovně Gtk.Button a definujeme, co se má v případě jeho volání vykonat podle zadání uživatele. V případě volání této procedury pak využíváme další funkce, která se volá při stisku daného tlačítka. Zde bych rád upozornil na to, že v mnoha příkladech, dotazech a nápovědách je možné najít trochu jiný kód, který se v daném případě doporučuje:

Btn_Quit.On_Clicked(Quit_Handler'Access);

Pokud vyzkoušíme překlad s tímto kódem, bohužel zákeřně pohoříme:

Builder results
    ../GtkAda1/src/main.adb
        76:24 subprogram must not be deeper than access type

To jenom na okraj, aby nedošlo ke zbytečnému rozčarování a rozčilování… Je to totiž způsobeno tím, že druhý způsob se používá tehdy, když se funkce nachází v jiném balíčku, než ze kterého se volá. Kód máme tedy komplet, takže můžeme aplikaci přeložit a spustit. Pokud na její ukončení použijeme příslušné tlačítko v okně, tak se toto okno zavře. V GPS si pak můžeme všimnout, že zmizí ikona spuštěná aplikace, která zde dříve byla. Změna se projeví i v dolní části ze zprávami, kde se v záložce Run : main objeví zpráva o ukončení aplikace:

../GtkAda1/obj/main

[2015-03-18 14:00:29] process terminated successfully, elapsed time: 02.47s

Pokud bychom ale k uzavření okna použili ikonu s křížkem, tak se nic výše popsaného nestane. Aplikace běží dál, jako předtím, než jsme definovali její ukončení tlačítkem. Je to vlastně docela logické, protože funkci pro ukončení aplikace voláme jenom a pouze stiskem příslušného tlačítka. Bylo by samozřejmě vhodné tuto situaci napravit a vložit kód pro ukončení aplikace i pod běžně používanou ikonu. Problém je v tom, že nemůžeme použít již definovanou proceduru pro ukončení aplikace tlačítkem. Opět zde zasahuje silná typová kontrola jazyka Ada a nedovolí nám proceduru volat. Zatím se nebudeme zabývat podrobnostmi a vyřešíme věc nejjednodušší cestou – vytvoříme novou proceduru:

procedure App_Quit (Win : access Gtk_Widget_Record'Class) is
   begin
      Gtk.Main.Main_Quit;
   end App_Quit;

Tu pak budeme volat podobně jako u tlačítka. Rozdíl je v tom, že použijeme jinou funkci, která zajistí provedení námi definované akce při ukončení hlavního okna:

Win.On_Destroy(App_Quit'Unrestricted_Access);

Pokud aplikaci přeložíme a spustíme, tak můžeme vyzkoušet funkčnost obou způsobů ukončení aplikace (kvůli zajištění překladu je nutné odkomentovat řádek č. 4, kde se odkazujeme na knihovnu Gtk.Widget). Pokud je vše v pořádku, oba skutečně fungují. V následujícím odstavci si ukážeme ještě další způsob, jak vyřešit stejný problém – ukončení aplikace tlačítkem i „křížkovou ikonou“. Budeme k tomu potřebovat novou knihovnu, a nejen to. Opět využijeme možnosti oddělit některé součásti aplikace do zvláštního balíčku a všechny reakce si koncentrujeme na jedno místo. Založíme tedy dva nové soubory: handlery.ads

with Gtk.Main;
with Gtk.Handlers;
with Gtk.Widget;      use Gtk.Widget;

package handlery is

   package Handlers is new Gtk.Handlers.Callback (Gtk_Widget_Record);

procedure Destroy (Widget : access Gtk_Widget_Record'Class);

end handlery;

a handlery.adb

package body handlery is

   procedure Destroy (Widget : access Gtk_Widget_Record'Class) is
   begin
      Gtk.Main.Main_Quit;
   end Destroy;

begin

   null;

end handlery;

V novém balíčku se odkazujeme na knihovnu Gtk.Handlers, vytvoříme si novou instanci příslušné procedury a založíme proceduru, která vykonává již známý příkaz pro ukončení aplikace. Jak je jasné na první pohled, je zde na rozdíl od předešlého příkladu tato procedura pouze jediná. Zbývá doplnit volání procedury z hlavní procedury Main. K tomu musíme doplnit tento kód:

with Handlery;

handlery.Handlers.Connect (  Win, "destroy", handlery.Handlers.To_Marshaller (handlery.Destroy'Access));

handlery.Handlers.Connect (  Btn_Quit, "clicked", handlery.Handlers.To_Marshaller (handlery.Destroy'Access));

První příkaz se odkazuje na nově vytvořený balíček Handlery. Druhý pak volá proceduru Destroy z tohoto balíčku v momentě, kdy se ukončuje hlavní okno aplikace. Třetí nakonec vyvolá tutéž proceduru v momentě, kdy dojde ke stisknutí tlačítka Btn_Quit. Můžeme zkusit aplikaci přeložit, spustit a vyzkoušet, jestli oba typy zavření fungují (pro řádnou zkoušku je nutné zakomentovat řádky s předchozí verzí funkce pro ukončení aplikace – Win.On_Destroy… a Btn_Quit.On_Clicked…). Zkouška by každopádně měla dopadnout dobře. Zde jenom krátce připomínám výše zmíněný rozdíl ve volání atributů Access a Unrestricted_Access v rámci jedné procedury nebo při odkazu na jiný balíček. Konečný kód výše uvedeného příkladu je v přílohách main2.adbhan­dlery2.adb a handlery2.ad­s.

Ještě než se pustíme do další práce na rozdělaném projektu, vrátíme se trochu k již dříve zmiňovaným akcím a reakcím. Akcí se v kontextu grafického rozhraní většinou myslí něco, co bylo vyvoláno uživatelem (kliknutí na tlačítko, zavření okna, výběr položky ze seznamu, vložení nebo změna textu editačního pole atd.). Reakcí se pak myslí nějaký úkon, který systém (aplikace, program) provede v návaznosti na uživatelskou akci. Tyto reakce jsou ve většině programovacích jazyků řešeny prostřednictvím tzv. Handlers (překlad je trochu zavádějící, ale přece jenom – manipulátor). Můžeme to brát tak, že tyto nástroje slouží pro určitou „manipulaci“ v rámci uživatelského rozhraní, přenosu dat mezi jednotlivými jeho prvky a podobně. Vrátíme se tedy k projektu a doplníme další akce a reakce. Abychom si zbytečně nekomplikovali situaci, založíme si nový projekt GtkAda2 se stejnými parametry a zkopírujeme do něj všechny tři soubory z projektu předchozího (jsou v adresáři ../src). Kdo chce, tak může vynechat přebytečné komentáře. Každopádně z kódu odstraníme procedury Quit_Handler a App_Quit včetně jejich volání. Už je nebudeme potřebovat, protože na další rozšíření možností naší aplikace využijeme nově vytvořený balíček Handlery. Pro lepší přehled je upravený kód v přílohách main3.adbhan­dlery3.adb, a handlery3.ad­s.

Ještě se na chvíli vrátíme k funkci ukončení aplikace při stisku tlačítka a zavření hlavního okna. Tato úloha byla sice řešena a vyřešena v předchozí části tohoto dílu, ale je zde i druhá možnost, která se jeví jako jednodušší. Budeme v ní používat knihovnu Gtkada.Handlers proti minulému řešení na bázi knihovny Gtk.Handlers. Jako první krok odkomentujeme od samého počátku zakomentovaný řádek v proceduře Main:

with Gtkada.Handlers;       use Gtkada.Handlers;

V balíčku Handlery není třeba dělat žádné změny. Do procedury Main pak přidáme k již použitým voláním procedury Handlery.Handlers.Connect (…) další dva příkazy (pro větší přehlednost zde budou uvedeny původní i nové příkazy):

P

--   handlery.Handlers.Connect (  Win, "destroy", handlery.Handlers.To_Marshaller (handlery.Destroy'Access));
Widget_Callback.Connect (  Win, "destroy", Widget_Callback.To_Marshaller (handlery.Destroy'Access));

--   handlery.Handlers.Connect (Btn_Quit, "clicked", handlery.Handlers.To_Marshaller (handlery.Destroy'Access));
Widget_Callback.Connect(Btn_Quit, "clicked", Widget_Callback.To_Marshaller(handlery.Destroy'Access));

Jak je z uvedeného kódu patrné, druhý způsob je opravdu jednodušší. Zjednodušení spočívá v tom, že není nutné se dvakrát odkazovat na knihovnu Handlery.Handlers (ta definuje novou instanci volání handleru s příslušným parametrem). Obě tato volání jsou nahrazené funkcí Widget_Callback z knihovny Gtkada.Handlers. Volání příslušné výkonné procedury samozřejmě zůstává zachováno. Po doplnění uvedeného kódu můžeme původní příkazy zakomentovat, projekt přeložit a po spuštění vyzkoušet funkčnost pro ukončení tlačítkem i zavřením hlavního okna. Pokud bychom v kódu nechali pouze nový způsob, můžeme klidně v souboru handlery.ads zakomentovat řádek s příkazem

package Handlers is new Gtk.Handlers.Callback (Gtk_Widget_Record);

popř. všechny tři zakomentované řádky smazat.

Pokud se znovu podíváme na spuštěnou aplikaci, tak zjistíme, že máme kromě jiných widgetů k dispozici tři tlačítka – Button1, Button2, Button3 a k nim tři editační pole – Edit1, Edit2, Edit3. První implementovanou akcí bude kliknutí na tlačítko Button1. V reakci na to se do editačního pole Edit1 pod tímto tlačítkem vloží text, který je definovaný přímo v kódu. Text nebude následně možné v editačním poli uživatelsky měnit. Jako první tedy vložíme kód pro nastavení prvního editačního pole tak, aby bylo určeno pouze pro čtení:

Gtk_New(Edit1);
Edit1.Set_Editable(False);
HBox2.Add(Edit1);

Můžeme nyní zkusit kód přeložit a spustit aplikaci. Následně pak můžeme vyzkoušet, že se text opravdu nedá uživatelsky změnit, vymazat, doplnit atd. Nyní se trochu podrobněji podíváme na to, co se vlastně bude v rámci naší zkušební dvojice akce-reakce odehrávat. První dvě reakce byly poměrně jednoduché: k určitému widgetu (hlavní okno a tlačítko) byla přiřazena jedna reakce, která byla vyvolána určitou akcí widgetu (uzavření hlavního okna, kliknutí na tlačítko). Připravovaná funkce je ale trochu složitější. Základ je samozřejmě stejný – na základě akce (kliknutí na určité tlačítko) bude vyvolána reakce – do určitého editačního pole se zadá určitý text. My tedy musíme jasně deklarovat, do kterého pole a jaký text se má vložit.

Na první pohled to zní velmi jednoduše, ale na druhý to už tak jednoduché není. Zádrhel je totiž v tom, že reakce na akci-událost máme soustředěné v balíčku Handlery. Tím pádem ale „nevidíme“ widgety deklarované v proceduře Main. Zdánlivě jednoduché řešení v podobě zadání kódu pro odkaz na proceduru Main v balíčku Handlery ale bohužel nefunguje. Kdo by to chtěl vyzkoušet, tak narazí na chybové hlášení překladače o kruhové závislosti mezi balíčky (jeden se odkazuje na druhý). Tudy tedy cesta nevede a tak musíme najít nějaké řešení, kde bude možné při volání zadat nejenom název procedury, ve které se ukrývá reakce. Bude nutné zadat i název widgetu, kterého se daná akce má týkat. Vezmeme na pomoc referenční manuál a podíváme se na funkci, kterou jsme doposud používali: Gtk.Handlers.Callback a proceduru Connect. Tam zjistíme, že v této proceduře jsou k dispozici právě jenom ty tři vstupní parametry, které jsme použili – název widgetu, kterého se reakce týká, název signálu, který vyvolá reakci a odkaz na deklarovanou reakci. Ony jsou tam parametry ve skutečnosti čtyři, ale ten poslední nám není k užitku. Abychom mohli naplnit naše požadavky, musíme se poohlédnout po jiném řešení.

Řešení se nachází opět v knihovně Gtk.Handlers a její proceduře User_Callback. Tato procedura je velmi podobná té, kterou jsou využili výše pro první způsob ukončení aplikace. Budeme tedy postupovat podobně a do souboru handlery.ads vložíme novou instanci uvedené procedury včetně parametrů:

package User_Handlers is new Gtk.Handlers.User_Callback(Gtk_Widget_Record, Gtk_GEntry);

Jenom pro srovnání a připomenutí je zde také původní, nyní již zakomentovaný, příkaz:

--   package Handlers is new Gtk.Handlers.Callback (Gtk_Widget_Record);

Kromě názvu procedury je na první pohled zřejmé, že zásadní rozdíl je v počtu parametrů. Nová instance má ještě druhý parametr, který má stejný typ, jako deklarované editační pole. Tato konstrukce nám tedy umožní přesně specifikovat editační pole, do kterého chceme nějaký text zapsat. Na základě této znalosti můžeme do specifikace balíčku Handlery přidat novou proceduru:

procedure Edit_Field(Widget : access Gtk_Widget_Record'Class;  User_Widget : Gtk_GEntry);

Od dříve definované procedury Destroy se opět liší pouze přítomností druhého parametru, který určuje editační pole. Do implementace balíčku Handlery přidáme výkonnou část této procedury:

procedure Edit_Field(Widget : access Gtk_Widget_Record'Class;  User_Widget : Gtk_GEntry) is          --1

begin                                                                    --2
   User_Widget.Set_Text("ADA zdraví celý svět!!!");                           --3
end Edit_Field;                                              --4

Důležitý je zde pouze třetí řádek, kde je definována příslušná reakce. Procedura vezme formální parametr, který definuje nějaké editační pole a vloží do něj uživatelský text. Projekt můžeme přeložit, ale změny se zatím nikde neprojeví. Musíme ještě do procedury přidat příkaz pro volání nově vytvořené funkce. To vypadá asi následovně:

CS24_early

handlery.User_Handlers.Connect (Button1, "clicked", handlery.User_Handlers.To_Marshaller(handlery.Edit_Field'Access), Edit1);

Volání je samozřejmě velmi podobné prvnímu způsobu ukončení aplikace. Volá se příslušná instance handleru, aplikace reaguje na stisk tlačítka Button1, volá příslušnou výkonnou proceduru a zadává editační pole Edit1, do kterého se má zapsat zadaný text. Teď už můžeme projekt konečně přeložit a vyzkoušet kliknutí na Button1. V editačním poli pod ním se objeví zadaný text „ADA zdraví celý svět!!!“

Dnešní díl jsme celý věnovali ukázkám možností jazyka Ada v oblasti uživatelských akcí a reakcí. V příštím dílu budeme v tomto snažení pokračovat a ukážeme si další a obecnější možnosti, které se nabízejí.

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

Autor článku