Hlavní navigace

Programovací jazyk Ada: akce a reakce podruhé a obecněji

6. 8. 2015
Doba čtení: 12 minut

Sdílet

Další díl seriálu o programovacím jazyce Ada, který je sice méně rozšířený, ale velmi zajímavý. Minulý díl jsme celý věnovali ukázkám možností jazyka v oblasti uživatelských akcí a reakcí. V dnešním dílu budeme v tomto snažení pokračovat a ukážeme si další a obecnější možnosti, které se nabízejí.

Minule uvedené řešení vypadá celkem jednoduše a plní naše původní přání i zadání. Ale co když budeme chtít při volání zadávat nejenom konkrétní editační pole, ale také konkrétní text? Zdá se to být celkem logický požadavek, že? Nejenom proto si ukážeme, jak je možné tento požadavek naplnit. Předem je nutné upozornit, že to nebude úplně snadné a přímočaré. Ve standardních knihovnách totiž není žádná rozumná procedura či funkce, která by to uměla podobně jako ta předchozí. Proto si budeme muset vzít na pomoc dva konstrukční prvky jazyka Ada, kterým jsme se doposud úspěšně vyhýbali – záznamy (typ record) a ukazatele (typ access). Pomocí těchto dvou konstrukcí a trochou snahy dosáhneme kýženého výsledku!

Proč je nutné použít záznam? Důvode je jednoduchý: potřebujeme do jedné „proměnné“ umístit dvě položky s různým typem hodnot. Do specifikace balíčku Handlery vložíme deklaraci příslušného záznamu s požadovanou strukturou:

type My_Data is record      --1
   Edit : Gtk_GEntry;       --2
   Text : String (1..14);   --3
end record;         --4

Na prvním řádku se definuje název a typ proměnné. Na druhém se definuje položka, která má typ editačního pole. Na třetím řádku se pak definuje položka s pevnou délkou řetězce. K té se ještě později vrátíme. Poslední řádek je pak ukončením typu record. V dalším příkazu vytvoříme ukazatel na definovaný typ záznamu:

type My_Data_Access is access My_Data;

Deklarace spočívá pouze v použití klíčového slova is access. Tento typ má mnohem širší možnosti a varianty, my si ale vystačíme s touto úplně základní. Pak už můžeme definovat novou instanci handleru jako v předchozím případě:

package Handle_Edit is new Gtk.Handlers.User_Callback (Gtk_Widget_Record, My_Data_Access);

Rozdíl je zde pouze ve druhém parametru, kde není odkaz na nějaký typ widgetu, ale odkaz na deklarovaný záznam. Tato definice nám umožní dostat do procedury obecně neomezený počet uživatelských parametrů. A o tom to celé bylo a je!!! Proto můžeme vložit specifikaci a implementaci výkonné procedury:

procedure Edit_Edit (Widget : access Gtk_Widget_Record'Class; Edit_Data : My_Data_Access);

   procedure Edit_Edit (Widget : access Gtk_Widget_Record'Class; Edit_Data : My_Data_Access) is
   begin
      Edit_Data.Edit.Set_Text (Edit_Data.Text);
   end Edit_Edit;

Specifikace se liší opět pouze typem druhého parametru, který je samozřejmě také ukazatelem na záznam. Výkonná procedura je pak trochu obecnější. K přiřazení textu do příslušného editačního pole používá položky seznamu. Na tomto místě je třeba opět připomenout a pochválit GPS, které i v těchto poměrně zamotaných situacích vzorně napovídá!

Vše jej již připravené na závěrečná dva kroky: do položek záznamu vložit konkrétní hodnoty a zavolat příslušnou výkonnou proceduru. K jejich provedení budeme potřebovat tři příkazy. Všechny budou v proceduře Main a první z nich deklaruje lokální proměnnou:

UData : handlery.My_Data_Access;

Ta musí být samozřejmě odkazem na původní ukazatel z balíčku Handlery. Pak přijde na řadu přiřazení editačního pole Edit3 a vybraného textu:

UData := new handlery.My_Data' (Edit => Edit3, Text => "Ahoj_světe!!!");

Přiřazení se provádí jako nová instance typu záznam. Je použita delší notace, aby bylo jasné, co k čemu se přiřazuje. Bylo by samozřejmě možné použít i ve tvaru:

UData := new handlery.My_Data' (Edit3, "Ahoj_světe!!!");

Posledním příkazem je pak volání výkonné procedury, který bude reakcí na kliknutí tlačítka Button3:

handlery.Handle_Edit.Connect(Button3, "clicked", handlery.Handle_Edit.To_Marshaller (handlery.Edit_Edit'Access), UData);

Formát volání je opět prakticky shodný s předchozím způsobem. Po uložení můžeme zkusit překlad a spuštění aplikace. Po kliknutí na tlačítko Button3 se v editačním poli pod ním objeví zadaný text Ahoj_světe!!!. Editační pole je standardně v módu editace, podle potřeby je možné mód změnit na pouhé zobrazení pomocí známého příkazu:

Edit3.Set_Editable(False);

Když už umíme vnutit vybranému editačnímu poli zadaný text, asi by bylo vhodné se zamyslet nad tím, jak přečíst nějaký text z jiného pole či widgetu a přenést ho do editačního pole. V principu by to samozřejmě neměl být zásadní problém a navíc je to celkem očekávaná funkce, kterou by měla každá slušná aplikace umět. Problém zde sice není zásadní, ale přece jenom tu nějaký je. Ten problém spočívá v deklaraci textové položky v záznamu, která má typ String s přesně zadanou délkou řetězce. Vrátíme se trochu obecně k řetězcům v jazyce Ada. Jsou zde k dispozici tři typy řetězcových typů:

  1. řetězce s pevně stanovenou délkou. To je právě ten na první pohled nejjednodušší typ, ale se zákeřnou deklarací. Vyžaduje totiž přesný počet znaků (je to vlastně pole znaků s pevnou délkou), kde nemůže být ani více, ani méně. Ve standardní knihovně Ada.Strings.Fixed jsou k dispozici nástroje pro doplňování kratších a ořezávání delších řetězců
  2. řetězce s pevně stanovenou maximální délkou. Maximální délka musí být předem známá a hlavně zadaná jako omezení. Tento typ pobere kratší řetězce, s delšími než maximum si ale neporadí. Nevýhodou tohoto typu je to, že pro uložení rezervuje tolik paměťového prostoru, který odpovídá maximální zadané délce. Typ najdeme v knihovně Ada.Strings.Bounded
  3. řetězce s neomezenou délkou. Ona není tak úplně neomezená, maximum je délka v hodnotě Integer'Last, což je docela dost nebo dostupná operační paměť. Typ je ale implementován s využitím dynamické alokace paměti, takže ani toto omezení nepřipadá moc v úvahu. Vzhledem k popisu všech typů řetězců a z předešlé úvahy je asi jasné, že toto bude v rámci aplikací s grafických rozhraním nejčastěji používaný typ řetězců. Najdeme jej v knihovně Ada.Strings.Unbounded.

Nyní už máme všechny potřebné znalosti a informace a můžeme přistoupit k řešení problému. Jako první krok změníme deklaraci položky Text v záznamu. To není možné udělat jenom tak, proto musíme zadat odkaz na výše uvedenou knihovnu (ve specifikaci balíčku Handlery):

with Ada.Strings.Unbounded;

Abychom si trochu ulehčili práci, přidáme k odkazu také alias:

package UStr renames Ada.Strings.Unbounded;

a změníme deklaraci záznamu:

type My_Data is record
   Edit : Gtk_GEntry;
   Text : UStr.Unbounded_String;
end record;

I když nemáme změněné volání procedury, můžeme zkusit překlad. Tam ale celkem očekávaně pohoříme:

Builder results
    ../GtkAda2/src/main.adb
        67:59 expected private type "Ada.Strings.Unbounded.Unbounded_String"
        67:59 found a string type

Využijeme tedy nástrojů, které nám knihovna nabízí, a změníme volání v proceduře Main takto:

UData := new handlery.My_Data' (Edit => Edit3, Text => handlery.UStr.To_Unbounded_String("Ahoj_světe!!!"));

Zde jsme převedli běžný řetězec na neomezený, ale v proceduře Handlery.Edit_Edit to musíme udělat naopak:

Edit_Data.Edit.Set_Text (UStr.To_String(Edit_Data.Text));

Teď už bude překlad v pořádku včetně funkčního tlačítka Button3. Můžeme klidně vyzkoušet i s jiným textem, ale bylo by to zbytečné. Další úpravu kódu naznačeným směrem dokončíme v dalším textu. Dosavadní kód je v přílohách main4.adb, handlery4.adb, handlery4.ads.

Vrátíme se tedy k tomu, že jsme chtěli „slíznout“ text z jednoho editačního pole a vepsat jej do jiného. Konkrétně tedy načteme text z pole Edit1 a zobrazíme ho v poli Edit2. Máme k tomu dvě možnosti. Jedna z nich využije stávající proceduru v balíčku Handlery a druhá pak novou proceduru v Main. My si zde ukážeme obě zmíněné metody a začneme tou druhou. Pořád nesmíme zapomínat na to, že všechny deklarované widgety jsou přímo přístupné pouze v proceduře Main. Proto musí být nové procedura umístěna zde. Její kód bude velmi jednoduchý:

procedure View_Edit (Widget : access Gtk_Widget_Record'Class) is        --1

begin                                       --2

   Edit2.Set_Text (Edit1.Get_Text);                     --3

end View_Edit;                                  --4

Zajímavý je zde pouze třetí řádek, který provádí to, co jsme od funkce požadovali: přečte text v poli Edit1 a vloží ho do pole Edit2. Aby bylo vše v pořádku, musíme odkomentovat řádek 4 v proceduře Main, kde je odkaz na knihovnu Gtk.Widget. Pak už stačí jenom doplnit už známý kód volání, které reaguje na kliknutí tlačítka Button2 a využívá novou proceduru:

Widget_Callback.Connect (Widget => Button2, Name   => "clicked", Marsh  => Widget_Callback.To_Marshaller(View_Edit'Unrestricted_Access));

Podobný kód už jsme použili, ale zde bych upozornil na dvě věci:

  • opět je nutné použít atribut Unrestricted_Access, protože volaná procedura je ve stejné úrovni, jako ta volající
  • parametry nejsou vypsané rovnou, ale za pomoci názvů formálních parametrů. Jedná se o využití jedné z funkcí GPS. Když totiž zadáme text Widget_Callback.Connect (, objeví se jako první položka kontextové nápovědy s názvem params of Connect. Pokud si jí vybereme, dostaneme „kostru“ příkazu, kterou pak jenom můžeme doplnit „masem“ hodnot parametrů:
    Widget_Callback.Connect (Widget => ,
                             Name   => ,
                             Marsh  => ,
                             After  => )

Projekt je nyní možné přeložit a spustit aplikaci. V ní jsou už funkční všechna tři tlačítka, která na akci kliknutí reagují deklarovanou reakcí. Ve druhé variantě využijeme stávající proceduru Handlery.Edit_Edit a změníme pouze její parametry. Tuto metodu jsme již použili s tím, že reagovala na kliknutí tlačítka Button2 a využila k tomu parametry, které byly uloženy v záznamu. Pro změnu nám tedy stačí změnit akci na tlačítko Button2 a zadat nové parametry. Uděláme to ve dvou krocích. V prvním si zadáme přímo text, abychom funkci vyzkoušeli. Přidáme do kódu tři řádky:

UData.Edit := Edit2;

UData.Text := handlery.UStr.To_Unbounded_String("Pokusný text");

handlery.Handle_Edit.Connect(Button2, "clicked", handlery.Handle_Edit.To_Marshaller (handlery.Edit_Edit'Access), UData);

V prvních dvou řádcích měníme parametry tak, že cílovým editačním polem bude Edit2 a do něj vložený text je přímo zadaný. Třetí řádek volá stejným způsobem danou proceduru, pouze je nastavena akce na tlačítko Button2. Před pokusným překladem si zakomentujeme řádek s voláním akce k tlačítku Button 2, aby se nám volání „nepřetahovala“. Po spuštění aplikace a kliknutí na Button2 se v editačním poli skutečně objeví text Pokusný text. Sice jsme to před chvílí udělali, ale zkusíme úkon vrátit zpět: odkomentujeme řádek s voláním akce tlačítka Button2. Máme tedy v kódu volání dvě: prvně voláme akci s parametrem přenosu textu z pole Edit1 a následně pak se zadaným textem. Výsledek je asi jasný, ale přesto jenom pro objasnění: provede se to volání, které je v kódu poslední, a proto bude pole obsahovat zadaný text a ne ten z pole Edit1. První řádek s voláním opět zakomentujeme a zkusíme druhý krok. V něm se určitě nezmění volání procedury, protože k tomu není žádný důvod. Provede se jenom změna parametrů v záznamu. V prvním kroku jsme si ukázali způsob jejich změny postupně či odděleně. Je možné to ale provést najednou, v jediném příkazu:

UData.all := (Edit => Edit2, Text => handlery.UStr.To_Unbounded_String(Edit1.Get_Text));

Provedli jsme samozřejmě také změnu v nastavení textu, který se do procedury předává. Projekt přeložíme, spustíme aplikaci a klikneme na Button2. Zdánlivě se ale nic neděje! Ono to není úplně pravda, náš kód je správný a dělá to, co jsme po něm chtěli. Problém je v tom, že v momentě přiřazení hodnoty obsahu pole Edit1 v něm nic není, protože se plní až kliknutím tlačítka Button1. A toto kliknutí už zase nemá vliv na přiřazení hodnoty do záznamu. Zkusíme tedy před nastavení parametrů přidat jeden řádek:

Edit1.Set_Text("Vložený standardní text");

Po překladu a spuštění aplikace je vidět, že v poli Edit1 je tento text ihned po spuštění. Kliknutí na tlačítko Button2 ho samozřejmě přenese do pole Edit2. Po kombinaci kliknutí Button1 a Button2 se do Edit1 vrátí text, zadaný ve volané proceduře a do Edit2 se přenese naposledy vložený text. Dále asi nemá smysl možnosti rozebírat a hledat ještě další řešení. Tato záležitost jednoznačně ukazuje, že jazyk Ada toho nabízí opravdu hodně, ale také toho docela dost chce po svém uživateli. A také připomíná, že ne vždy je možné najít jediné řešení na všechny nastolené problémy a nápady…

Když už jsme u editačních polí, ukážeme si ještě další funkci, kterou mohou disponovat. Jedná se o možnost, kdy se do jednoho pole uživatelsky zadá nějaký text a bez pomoci kliknutí tlačítka se přenese do jiného pole. Třída Gtk.GEntry nabízí několik možností, jak reagovat na akce, které se mohou při práci s editačním polem vyskytnout. My si ukážeme konkrétní příklad: do pole Edit2 se zadá nějaký text a po stisku tlačítka Enter se tento text přenese do pole Edit3 bez ohledu na to, jaký je jeho aktuální obsah. Jako první si připravíme v proceduře Main výkonnou proceduru:

procedure Handle_Edit (Widget : access Gtk_Widget_Record'Class) is                           --1

   Pos : Glib.Gint := 0;                                     --2

begin                                                --3
   Edit3.Insert_Text (New_Text  => Edit2.Get_Text, New_Text_Length => 20, Position => Pos);     --4
end Handle_Edit;                                         --5

Zajímavé jsou pouze řádky 2 a 4. Na druhém řádku je deklarovaná proměnná nového typu. Aby její deklarace prošla překladačem, musíme přidat odkaz na příslušnou knihovnu:

with Glib;

Na čtvrtém řádku je výkonná funkce, která využívá zatím nepoužitou funkci editačního pole – Insert_Text (v tomto momentě by bylo vhodné upozornit na to, že existují dvě varianty tohoto příkazu. Námi použitý je ten delší. Kratší varianta má pouze dva parametry – New_Text a Position. Bohužel pro nás překladač tvrdošíjně vyžadoval variantu delší…). Tato funkce zajistí přidání textu do příslušného pole a to sice od zvoleného místa = pozice. Zadává se text jako takový a také jeho maximální délka. Pokud je zadaný text delší než maximální hodnota, funkce ho zprava ořízne. Námi vytvořená funkce konkrétně přidává text do pole Edit3 od začátku do maximální délky 20 znaků. Text si bere z obsahu v poli Edit2. Pak už zbývá jenom vytvořit volání, kde zadáme požadavek na to, aby se text vložil po použití tlačítka Enter v poli Edit2:

Widget_Callback.Connect (Widget => Edit2, Name => "activate", Marsh => Widget_Callback.To_Marshaller(Handle_Edit1'Unrestricted_Access));

Volání opět není ničím výjimečné, pouze byl použit signál On_Activate, který se spouští právě tlačítkem Enter. To už je vše, co k provedení popisované funkčnosti potřebujeme, takže můžeme projekt přeložit, spustit aplikaci a experimentovat s novými možnostmi. Ty staré samozřejmě zůstaly zachované.

root_podpora

Kompletní kód bude uveden v příloze příštího, posledního dílu naší série.

Dnešní díl jsme celý věnovali obecnějšímu řešení problémů s akcí a reakcí v rámci grafických uživatelských widgetů. V příštím dílu dokončíme náš ukázkový kód jednou krátkou ukázkou. Vzhledem k tomu, že příští díl je posledním z naší série, provedeme v něm i celková shrnutí a závěr.

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