Slot Container
CL_SlotContainer je jednoduchá pomocná třída sloužící ke správě slotů, k nimž připojujeme signály. Pokud je totiž signálů, které chceme odchytávat, mnoho, potřebujeme mnoho slotů, a pokud bychom postupovali, jak bylo popsáno v minulém dílu, byli bychom nuceni každý další slot deklarovat, což by mohlo být časem dosti nepohodlné a nepřehledné.
CL_SlotContainer dokáže skladovat neomezené množství slotů s připojenými signály. Syntaxe jeho použití je následující:
CL_SlotContainer Kontejner; ... Kontejner.connect(Signal1, this, &MyApp::on_Signal1); Kontejner.connect(Signal2, this, &MyApp::on_Signal2); Kontejner.connect(Signal3, this, &MyApp::on_Signal3);
Jak je vidět, abychom připojili signál k nějaké metodě, kterou chceme vyvolat, stačí vytvořit CL_SlotContainer a jeho metodě connect() předat jako parametry postupně připojovaný signál, ukazatel na třídu (typicky this), jejíž metodu chceme provést při zachycení tohoto signálu, a adresu této metody.
Metoda connect CL_SlotContaineru je šablonovou funkcí přetíženou pro různé typy parametrů. Výše uvedený příklad je asi nejtypičtějším, avšak je možné připojovat signály nejen k metodám tříd, ale například i ke globálním funkcím (pak connectu předáváme pouze první a třetí parametr oproti příkladu, tj. signál a adresu funkce).
Hotové signály v některých třídách
Většina tříd ClanLibu je vytvořena tak, že jejich instance vysílají různé signály v určitých momentech své existence. Takové třídy pak obvykle obsahují příznačně pojmenované funkce, které vracejí referenci na daný signál.
V následujících odstavcích se tedy s několika takovými třídami seznámíme a povíme si, jak využít signály, které vysílají.
CL_Sprite
Dosud jediná třída, s níž jsme se podrobně seznámili, je CL_Sprite. I ona obsahuje jednu takovou funkci, kterou je sig_animation_finished(), vracející referenci na CL_Signal_v0. Pokud tedy v našem programu přehráváme animaci pomocí CL_Sprite a chceme se dozvědět, kdy skončí, nemusíme neustále testovat metodou is_finished(), stačí připojit sig_animation_finished k metodě, kterou chceme po skončení provést. Vypadat to může zhruba nějak takto:
CL_Sprite Sprite;
CL_SlotContainer Kontejner;
...
Kontejner.connect(Sprite.sig_animation_finished(), this, &App::on_KonecAnimace);
CL_Keyboard
Tato třída je utilitou (tj. třídou se všemi položkami statickými, kterou nemá smysl instanciovat; používáme ji, jako by se jednalo o jednu globální instanci), která se stará o obsluhu klávesnice.
Má víceméně jen dvě běžně použitelné metody:
bool CL_Keyboard::get_keycode(int); std::string CL_Keyboard::get_key_name(int);
První z nich předáme jako parametr kód klávesy a ona nám odpoví, jestli je tato klávesa zrovna stisknuta.
Druhé předáme, jestli se nepletu, opět kód klávesy a ona nám vrátí její název jako string, například Space, Backspace a podobně. Tato funkce by nám měla pomoci při programování různých nastavování ovládání apod. přenositelně. Za zmínku ještě stojí, že v hlavičkovém souboru keys.h si můžete prohlédnout předdefinované přenositelné identifikátory jednotlivých kláves i jejich konkrétní hodnoty na konkrétních systémech. Tyto názvy jsou poměrně dosti intuitivní např. CL_KEY_A je makro odpovídající kódu klávesy A. Pokud tedy chcete zjistit, zda je zmáčknutá klávesa A, můžete to udělat takto jednoduše:
if (CL_Keyboard::get_keycode(CL_KEY_A)) ?
Mnohem zajímavější a užitečnější však jsou signály, které vysílá tato třída. Jsou jimi sig_key_down, sig_key_up a sig_key_dblclk (resp. funkce příslušných názvů). Jedná se tedy o signály vyslané při stisknutí, uvolnění nebo dvojitém zmáčknutí nějaké klávesy. Všechny jsou „typu“ v1 (void s jedním parametrem) a onen jeden parametr, který bude předáván příslušné funkci, k níž signál připojíte, je typu CL_InputEvent. Tedy funkce, k níž chcete takovýto signál připojit, by měla mít první parametr typu CL_InputEvent.
CL_InputEvent je třída, která obsahuje informace o nějaké vstupní události. Takovou vstupní událostí může být již zmíněné zmáčknutí, uvolnění klávesy příp. tlačítka na myši a podobně.
Abychom mohli zjistit, o jaký typ událosi se jedná, obsahuj CL_InputEvent atribut type (výčtového typu Type definovaného touto třídou), který může nabývat následujících hodnot: no_key, pressed, released, double_clicked, moved, pointer_moved, axis_moved, ball_moved. Přiznám se, že znám význam jen některých z nich, ale mám pocit, že pokud příslušnou událost potřebujete, bude vám i název jasný.
Pro obsluhu klávesnice je důležitý atribut id, v němž je uložen případný kód klávesy (nebo tlačítka myši), která vyvolala danou událost.
Atribut mouse_pos obsahuje informaci o pozici kurzoru myši v době události. Je typu CL_Point. CL_Point je třída uchovávající souřadnice (atributy x, y) s přetíženými operátory pro posun, porovnání a podobně.
V atributech left_alt, right_alt, left_ctrl, right_ctrl, left_shift, right_shift je uložen příznak o tom, zda byly v době vyvolání události tyto klávesy zmáčknuty.
CL_Mouse
CL_Mouse je utilita podobná výše popsané CL_Keyboard. I ona obsahuje metody get_keycode() a get_key_name() s obdobnou syntaxí i sémantikou. Navíc však obsahuje ještě metody, pomocí nichž jsme schopni určit polohu kurzoru myši v daný okamžik. Těmito metodami jsou get_x() a get_y(). Je dokonce možné polohu kurzoru změnit metodou set_position(int x, int y), což se může hodit například tehdy, pokud chceme, aby kurzor automaticky najel nad tlačítko v nově otevřeném okně. Metoda hide() kurzor skryje, metoda show() ho opět zviditelní.
Co se signálů týče, jsou přítomny všechny jako v CL_Keyboard se stejnými názvy, tj. signály pro stisknutí, uvolnění a doubleclick tlačítka. Navíc je zde i signál (resp. opět přístupová funkce vracející na něj referenci) sig_move, který je vyslán při každém pohybu myši.
Opět platí, že tyto signály mají jeden parametr typu CL_InputEvent (viz výše), který je předáván příslušné funkci, k níž jsou připojeny pomocí slotu. Z něj už lehce získáme potřebné údaje, jako je poloha myši v době vyvolání signálu, nebo které tlačítko bylo zmáčknuto.
CL_Joystick
Rád bych ještě podotkl, že existuje i třída CL_Joystick, která se dá využívat zcela analogicky pro ošetřování událostí vyvolaných joystickem. Syntax i sémantika jsou stejné. Signály jsou stejné jako u CL_Mouse.
Userdata
Zatím jsme popsali jen zlomek ClanLibovských tříd vysílajících nějaké signály, přesto se jedná o velmi užitečný zlomek. Drtivá většina ze zbylých tříd vysílajících signály se využívá při tvorbě GUI. Povídání o nich si necháme na příští díly.
Poslední, o čem bych se chtěl zmínit ještě dnes v souvislosti se signály a sloty, jsou tzv. userdata.
Představte si třeba následující situaci. V našem programu přehráváme několik různých spritů, např. sprity auto, motorka, kolo. Jejich vykreslování trvá určitou dobu (není ve smyčce) a my bychom chtěli, aby se po skončení vykreslování každého z nich provedla nějaká akce, například aby se vypsalo „Sprite XXX skončil svoje vykreslování!“. S tím, co už známe, to není problém naprogramovat. Vytvoříme si příslušné tři funkce, v nichž za XXX doplníme postupně auto, motorka, kolo, a připojíme k nim přes sloty příslušné sig_animation_finished těchto spritů. Vše bude dobře fungovat do doby, než přidáte dalších deset funkcí pro jiných deset spritů a pak zjistíte, že by měla hláška vypadat trochu jinak. V tu chvíli naplno pocítíte, že takový přístup vede do pekel :-).
Jedna z velmi užitečných zásad programování říká, že stejná funkcionalita se nesmí opakovat. Pokud bychom se jí chtěli řídit i v našem příkladu, což by bylo velice vhodné, potřebovali bychom všechny sig_animation finished připojovat k jedné funkci. V ní bychom však potřebovali být schopní rozlišit, který ze spritů sig_animation_finished vyvolal, abychom se mohli rozhodnout, co dosadit za XXX v naší hlášce. Tuto informaci nám však signál sám o sobě není schopen poskynout, je tedy něčím navíc, něčím, co se zde nazývá userdata.
ClanLib userdata podporuje tak, že je nastavíme při připojování daného signálu do slotu. V tu chvíli totiž sami víme, o čí signál se jedná. Userdata mohou být libovolného typu. V našem případě by se hodil třeba string:
CL_Sprite Auto;
CL_Sprite Motorka;
CL_Sprite Kolo;
...
CL_SlotContainer Kontejner;
...
Kontejner.connect(Auto.sig_animation_finished(), this, &App::Vypis, "Auto");
Kontejner.connect(Motorka.sig_animation_finished(), this, &App::Vypis, "Motorka");
Kontejner.connect(Kolo.sig_animation_finished(), this, &App::Vypis, "Kolo");
...
void App::Vypis(std::string XXX) {
cout << "Sprite " << XXX << " skoncil svoje vykreslovani!" << endl;
}
User data tedy předáváme jako další parametr při připojování signálu do slotu (ať už to je přímo do slotu, nebo přes CL_SlotContainer jako v příkladu).
Závěrem
Pro dnešek je to vše. Máme již poměrně dobrý přehled o signálech a slotech a o tom, jak zpracovávat události vyvolané klávesnicí, příp. myší. Příště se začneme věnovat tvorbě GUI.
Ještě bych se rád omluvil, že nestíhám posílat k článkům KDevelopí projekty jako u několika prvních. Je to dáno nedostatkem mého času, protože v době, kdy tyto články píši, mám zkouškové. Také je pravda, že vaše reakce na dotaz, zda vám takové rozsáhlé příklady přijdou užitečné, mne dostatečně nepřesvědčila, takže s tím máte šanci ještě něco udělat :-).
Každopádně se časem rozhodně objeví verze s chodícím ovladatelným panáčkem, kterou mám víceméně hotovou, a pak se uvidí.
Všem studujícím bych ještě rád popřál hezké prázdniny :-).