1 2 3 České vysoké učení technické v Praze Fakulta elektrotechnická Katedra počítačů Diplomová práce Automatický rezervační systém Bc. Jan Búda Vedouc...
České vysoké učení technické v Praze Fakulta elektrotechnická Katedra počítačů
Diplomová práce
Automatický rezervační systém Bc. Jan Búda
Vedoucí práce: Ing. Martin Šlapák
Studijní program: Elektrotechnika a informatika, strukturovaný, Navazující magisterský
Obor: Výpočetní technika květen 2012
Poděkování Děkuji vedoucímu práce, Ing. Martinu Šlapákovi, za přínosné rady k vývoji aplikace, doporučení vhodných technologií a také za podrobné připomínky k textu této práce.
iii
Prohlášení Prohlašuji, že jsem předloženou práci vypracoval samostatně a že jsem uvedl veškeré použité informační zdroje v souladu s Metodickým pokynem o dodržování etických principů při přípravě vysokoškolských závěrečných prací. Nemám závažný důvod proti užití tohoto školního díla ve smyslu §60 Zákona č. 121/2000 Sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých zákonů (autorský zákon).
Abstract This thesis deals with the analysis and design of an application for automated booking of free seats on Student Agency, s.r.o. lines. The application is designed to allow easy implementation of new plug-in modules. A module for checking free seats for theatre performances at the Žižkovské Divadlo Járy Cimrmana was implemented too. The thesis includes a programmer’s guide to creating new plug-in modules as well as tests of usability.
Abstrakt Tato diplomová práce se zabývá analýzou a návrhem programu pro automatickou kontrolu a rezervaci volných míst ve spojích společnosti Student Agency, s.r.o. Program je navržen tak, aby umožňoval jednoduché rozšíření v podobě zásuvných modulů. Byl implementován také zásuvný modul pro kontrolu volných míst na představení Žižkovského Divadla Járy Cimrmana. Součástí práce je programátorská příručka pro tvorbu nových zásuvných modulů a testy použitelnosti aplikace.
Existující řešení - SA Notify . . . . . . . . Existující řešení - SA Notify, výběr spoje . Existující řešení - SA Checker 2011 . . . . Popis aplikace - Počítačová gramotnost . Případy užití - Nastavení . . . . . . . . . Případy užití - Nastavení - Možnosti . . . Případy užití - Nápověda . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
5 5 7 11 13 15 16
3.1 3.2 3.3 3.4 3.5 3.6 3.7
Analytický model - základní struktura Analytický model - menu . . . . . . . Analytický model - správa pluginů . . Analytický model - menu pluginů . . . Analytický model - univerzální třídy . Návrh GUI . . . . . . . . . . . . . . . Návrh GUI - Splash Screen . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
25 26 27 28 29 33 33
4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8
Menu Nápověda . . . . . . . . . . . . . Menu v oznamovací oblasti . . . . . . Dialog Možnosti - Obecné . . . . . . . Dialog Možnosti - Zvuky a upozornění Třídní diagram pluginu . . . . . . . . StyledText – výchozí kurzor . . . . . . StyledText - vybraný text . . . . . . . Dialog Aplikace je již spuštěna . . . .
Úvod Tématem této implementační diplomové práce (viz [6]) je Automatický rezervační systém v podobě volně šiřitelné, modulární desktopové aplikace, která umožní automatické ověřování dostupnosti volných míst v internetových rezervačních systémech, zejména v rezervačním systému vnitrostátní autobusové a vlakové dopravy společnosti Student Agency s.r.o.
1.1
Motivace
Společnost Student Agency s.r.o. (dále jen Student Agency) provozuje stále se zvětšující síť autobusové a vlakové dopravy, nabízející ve většině případů na české poměry nadprůměrný komfort a příznivé ceny, včetně cenových zvýhodnění pro studenty. Služeb Student Agency proto využívá i značné množství dojíždějících studentů. V exponované dny, jako je neděle, pondělí dopoledne, čtvrtek odpoledne a pátek, bývají spoje Student Agency vyprodané na některých trasách i na několik týdnů dopředu. Mnoho cestujících ale své rezervace těsně před cestou ruší, na což čeká množství těch, na které se lístek nedostal a ihned rezervuje uvolněné lístky. Tím ovšem dochází ke stavu, kdy při uvolnění místa ve spoji je toto místo opět zarezervováno během maximálně několika málo minut. Cestující, který doufá v uvolnění místa ve vyhlédnutém spoji tak musí sedět u počítače, neustále aktualizovat webovou stránku rezervačního systému a snažit se být první, kdo objeví případné uvolněné místo. Rozhodl jsem se proto vytvořit aplikaci, která by umožňovala automatické kontrolování volných míst ve spojích Student Agency, případně i následné automatické zarezervování uvolněného místa. Analogie s vyprodanými spoji Student Agency lze ovšem nalézt i u dalších webových služeb: Například některá oblíbená divadla (za všechny Žižkovské divadlo Járy Cimrmana nebo Divadlo v Dlouhé) mívají zarezervována všechna volná místa během několika málo hodin od zveřejnění nového programu. I zde pak dochází k situacím, kdy si lidé rezervaci nevyzvednou a místa se opět uvolní. Aplikaci jsem proto navrhoval tak, aby bylo možné formou zásuvných modulů (pluginů) přidat podporu pro další rezervační systémy.
1
1.2. STRUKTURA A FORMA TEXTU
1.2
Struktura a forma textu
Všechny odkazy v PDF verzi této práce jsou interaktivní. To platí jak pro interní (odkazy na jinou kapitolu, obrázek apod.), tak internetové odkazy. Internetové odkazy jsou od okolního textu odlišeny neproporcionálním fontem. Použitá literatura je uváděna ve formátu odpovídajícím normě ISO 690 [1]. V textu jsou použity některé ikony z kolekce Function Icon Set ze stránek http://www.wefunction.com. Kolekce je zdarma k použití pod Royalty free licencí [30]. Dále jsou použity ikony od uživatele Tatice ze stránky http://www.iconarchive.com pod licencí CC Attribution-Noncommercial-No Derivate 3.0 [18]. 1.2.1 Používané pojmy Použité zkratky a pojmy jsou pro větší přehlednost definovány v příloze A. Některé běžně užívané programátorské pojmy v příloze neuvádím – například getter, setter, listener (zde obecně konstrukce spouštěná v reakci na definovanou akci uživatele), fields (také počeštěný tvar fieldy - data třídy) ad.
2
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
Kapitola 2
Existující řešení, Popis aplikace, Specifikace požadavků 2.1
Existující řešení
Primárním důvodem vývoje aplikace je potřeba programu, který by dokázal upozorňovat na volná místa ve spojích společnosti Student Agency. Nicméně, jak již bylo zmíněno výše, předpokládám, že upotřebení najdou i další pluginy, pro ověřování volných míst či automatickou rezervaci míst například v oblíbených divadlech. V době psaní tohoto textu neexistuje v České Republice žádný program, který by umožňoval automatickou rezervaci ve více různých službách a který by bylo možné modulárním způsobem dále rozšiřovat. Pokud se ale zaměříme na programy pro automatické ověřování volných míst v autobusech Student Agency, je situace odlišná. Takových programů existuje hned několik. Bohužel žádný z nich není v současné době funkční. Společnost Student Agency neposkytuje žádné API (A.2) pro přístup k rezervačnímu systému. Všechny uváděné programy proto fungují na stejném principu – nejprve načtou celou webovou stránku s vypsanými časy spojů pro zvolenou trasu a den a následně syntaktickou analýzou naleznou požadovaný čas spoje a číslo, udávající počet volných míst. Student Agency však v minulosti svůj webový rezervační systém několikrát změnila a tvůrci své programy již neupravili tak, aby nadále fungovaly. Následně popisované existující programy jsou tedy již nepoužitelné, je ale vhodné je vzájemně porovnat pro získání lepší představy o požadavcích na aplikaci a především o podobě pluginu pro autobusy a vlaky Student Agency. V době vzniku všech níže uváděných programů společnost Student Agency provozovala pouze autobusové spoje, vlakové spoje proto v této kapitole nejsou brány v úvahu. 2.1.1 Automatická detekce volného místa v autobusech na lince Liberec –
Praha Student Katedry měření Fakulty elektrotechnické ČVUT Jan Breuer na své osobní stránce (http://jaybee.cz) v březnu 2008 zveřejnil v článku Automatická detekce volného
3
2.1. EXISTUJÍCÍ ŘEŠENÍ
místa v autobusech na lince Liberec – Praha [8] php skript pro detekci míst v autobusech Student Agency. Během následujícího roku a půl byl skript postupně vylepšován. Byla přidána podpora pro všechny tuzemské linky Student Agency a pro autobusy DPMLJ na lince Praha – Liberec. Po změně webu rezervačního systému Student Agency v roce 2009 byl celý skript přepracován a fungoval až do roku 2010. V roce 2010 došlo k další změně webu Student Agency, skript se díky tomu stal nefunkčním a jeho další vývoj byl zastaven. 2.1.1.1
Použití
Skript se spouští přes příkazovou řádku, jako parametr je nutné zadat ID požadovaného spoje, které lze zjistit z URL adresy rezervačního systému. Při nalezení volného místa je pomocí PHP funkce exec() spuštěn program notify-send, který v Linuxových operačních systémech zobrazí upozornění na volné místo. Skript je tedy určen pouze pro OS Linux s nainstalovaným notify-send (je součástí balíku libnotify-bin). Umožňuje sledování více autobusových spojů najednou. Sleduje pouze volná místa, neumožňuje žádné nastavení preferovaných míst apod. Kontrola, zda se ve sledovaném spoji uvolnilo místo, probíhá periodicky každých 20 vteřin. Takto krátký interval je výhodný pro uživatele, zejména v případě většího rozšíření aplikace by ale mohl způsobovat nadměrné zatížení serverů rezervačního systému a vyvolat případné protiopatření ze strany Student Agency. Klady a zápory skriptu z webu http://jaybee.cz:
+ Lze sledovat více spojů najednou
- Použití pouze pod OS Linux
+ Kontroluje volná místa každých 20 s
- Použití pouze přes příkazovou řádku
+ Umí sledovat i autobusy DPMLJ na lince Praha – Liberec
- Nutné ručně kopírovat ID spoje z URL adresy - Nelze zadat preferované sedadlo - Na volné místo pouze upozorní
2.1.2 SA Notify Matěj Humpál vytvořil, inspirován skriptem od Jan Breuera, prográmek SA Notify (obr. 2.1), který zveřejnil na svých osobních stránkách http://weblog.finwe.info v polovině roku 2008. Program má jednoduché a přehledné grafické rozhraní, je psán v programu C# za použití frameworku .NET 2.0.
4
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
Obrázek 2.1: Prográmek SA Notify, verze 1.7.1
Prošel poměrně dlouhým vývojem: první verze uměla hlídat pouze spoje na trase Praha Liberec a vstupní parametry bylo, podobně jako v předchozím případě, nutné ručně kopírovat z URL adresy rezervačního systému. Nejnovější verze již zvládá všechny tuzemské trasy Student Agency a požadovaný spoj lze vybrat z kontextového rozbalovacího menu. Příjemné také je, že program nevyžaduje instalaci a má pouhých 33 kB. Po stažení je ihned připraven k použití. Vyžaduje pouze přítomnost .NET 2.0 frameworku (OS Windows) nebo MONO frameworku pro Unixové operační systémy (Linux, Mac OS). .NET framework je zapotřebí pro běh velkého množství aplikací pod OS Windows, většina počítačů s tímto OS ho tedy nainstalovaný již má.
Obrázek 2.2: Prográmek SA Notify, výběr spoje
5
2.1. EXISTUJÍCÍ ŘEŠENÍ
2.1.2.1
Použití
Po spuštění si uživatel výběrem z kontextových menu (obr. 2.2) jednoduše navolí požadovanou trasu, výchozí a cílovou stanici a čas zvoleného spoje. Časy spojů jsou načítány přímo z webu Student Agency, jsou tedy vždy aktuální, nevýhodou je někdy delší doba odezvy. Oproti tomu dostupné trasy autobusů jsou do programu napevno zakomponovány a není možné jednoduchým způsobem přidat trasy nové. Ke změnám obsluhovaných tras a především k zavedení tras nových přitom dochází relativně často (i několikrát do roka). Volná místa jsou kontrolována každých 90 vteřin, v případě úspěchu se zobrazí upozornění a ozve zvukový signál. 90 vteřin je znatelně větší interval než 20 vteřin v předchozím případě, i tak se ale autor na stránkách zamýšlí, zda interval a datová náročnost programu není příliš velká a v případě většího rozšíření nebude pro web Student Agency znamenat příliš velkou zátěž. Zdá se, že se tato obava nenaplnila. Ovšem, stejně jako v případě skriptu z kapitoly 2.1.1, po změně rezervačního systému v roce 2010 program přestal fungovat. Klady a zápory programu SA Notify (verze 1.7.1):
+ Online načítání času spojů
- Načtení spojů někdy trvá delší dobu
+ Lze sledovat více spojů najednou
- Nelze aktualizovat trasy spojů
+ Jednoduché grafické rozhraní
- Na volné místo pouze upozorní
+ Funguje pod všemi rozšířenějšími OS
- Nelze zadat preferované sedadlo
+ Na OS Windows běží schován v pravé dolní liště
2.1.3 SA Checker 2011 SA Checker 2011 (obr. 2.3) od Lukáše Hakulina je funkčností velmi podobný programu SA Notify, je ale psán v jazyce Java. Je nutné jej instalovat, instalační soubor má velikost 1,04 MB. Byl zveřejněn začátkem roku 2011, bohužel již v létě téhož roku web Student Agency doznal dalších změn, které by vyžadovaly přepracování větší části programu. Novou, funkční verzi proto zatím autor neplánuje. Za největší změnu oproti programu SA Notify považuji možnost uživatelů doplnit jednoduchým zásahem do části zdrojových kódů případné nové autobusové trasy. 2.1.3.1
Použití
Použití je prakticky totožné s programem SA Notify, největší funkční změnou, kterou uživatel pocítí, je kontrolování volných míst pouze jednou za 3 minuty.
6
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
Obrázek 2.3: Prográmek SA Checker 2011, verze 1.1 (zdroj: http://lukashakulin.com) U nejvíce exponovaných spojů je tento interval příliš velký. Například několik hodin před odjezdem vyprodaného autobusu je případné uvolněné místo zabráno zpravidla během minuty či dvou. To přitom platilo i v době, kdy žádný ze zde uváděných programů nebyl funkční a zájemci o místo v daném spoji museli stránku rezervačního systému manuálně aktualizovat. Díky lépe zpracovanému grafickému rozhraní se s programem pracuje příjemně a velmi intuitivně. 2.1.3.2
Hlavní rozdíly programu SA Checker 2011 oproti SA Notify:
• Umožňuje jednoduchou úpravou XML souboru implementovat nové trasy. Na stránkách autora je podrobný a přehledný návod doplněný obrázky, přidat požadovanou trasu trasu by měl být bez větších obtíží schopen i ne-programátor. • Fungoval pod novějším rezervačním systémem Student Agency, který byl v provozu v letech 2010-2011. • Kontroluje volné spoje pouze každé 3 minuty (z obavy před přetížením serveru). • Existuje-li více spojů ve stejný čas, program prohledává pouze poslední spoj v řadě. Na mnoha trasách je ve špičce více autobusů ve stejný čas nasazováno běžně. Například
7
2.1. EXISTUJÍCÍ ŘEŠENÍ
na trase Praha-Brno odjíždí v obou směrech dva autobusy najednou hned 4x každý všední den. Jedná se tedy o poměrně vážný nedostatek. • Program dokáže sledovat i spoje, které mají volná místa (viz obr. 2.3). • Vylepšené grafické rozhraní • Program se instaluje a pro jeho funkčnost je nutné mít nainstalovanou Java Virtual Machine. SA Notify instalaci nevyžaduje, pro svou funkčnost potřebuje nainstalovaný .NET 2.0 framework. • Výraznější zvukový signál při nalezení volného místa. Ten lze navíc v případě potřeby jednoduše změnit nahrazením souboru alarm.wav v kořenovém adresáři programu. • Na OS Windows při minimalizování zůstane na hlavní liště, SA Notify se minimalizuje do oznamovací oblasti v pravém dolním rohu. Klady a zápory programu SA Checker 2011 (verze 1.1):
+ Lze manuálně přidat nové trasy
- Kontroluje spoje jen 1× za 3 minuty
+ Jazyk Java – snadná přenositelnost mezi platformami
- Při více spojích ve stejný čas umí hlídat pouze jeden
+ Lze sledovat více spojů najednou
- Na volné místo pouze upozorní
+ Přehledné grafické rozhraní
- Nelze zadat preferované sedadlo
O existenci SA Checker 2011 jsem se, na rozdíl od programu SA Notify, dozvěděl až v době, kdy již byl kvůli změněnému webu pro rezervace nefunkční. Nemohl jsem proto zahrnout jeho hodnocení z hlediska rychlosti načítání spojů. 2.1.4 PHP Skript Začátkem roku 2011, kdy nebyl funkční žádný z dosud uvedených programů, jsem napsal svůj vlastní PHP skript pro hlídání volných míst v autobusech Student Agency. Můj skript je v zásadě velmi podobným se skriptem, který popisuji v kapitole 2.1.1. Hlavní rozdíl spočívá v určení pro webový server a spouštění přes běžný webový prohlížeč. Pro spuštění na osobním počítači je vhodné použít balíček EasyPHP ( http://www.easyphp. org/ ). Skript byl určen pro mé osobní použití, případně pro několik málo mých známých, kterým jsem mohl osobně ukázat instalaci i použití. Při jeho vývoji byl hlavní důraz kladen na rychlost vývoje (pouze několik hodin). Není proto příliš uživatelsky přívětivý – neexistuje žádné uživatelské rozhraní, parametry spoje se zadávají ručně do zdrojového kódu.
8
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
Při nalezení volného místa je uživatel upozorněn spuštěním Javascriptového příkazu alert(). Jeho nevýhodou je, že v novějších verzích webových prohlížečů není alert doprovázen zvukem, uživatel si proto upozornění nemusí všimnout – z nejpoužívanějších prohlížečů je zvuk pouze u Internet Exploreru 9. Prohlížeče Mozilla Firefox v. 10, Google Chrome v. 17, Opera v. 11.6 ani Safari v. 5.2 zvukové upozornění nevydají. Skript kontroluje volná místa každých 90 sekund, parametr je ale možné jednoduše změnit. Experimentováním jsem zjistil, že u nejvytíženějších spojů je vhodné změnit interval na 60 s, jinak roste nebezpečí propásnutí volného místa. Klady a zápory PHP skriptu:
- Složitější na zprovoznění + Při umístění na webový server lze skript spouštět vzdáleně z libovolného zařízení s přístupem na internet
- Nutné ručně kopírovat parametry spoje z URL adresy - Umí sledovat jen jeden spoj - Nelze zadat preferované sedadlo - Na volné místo pouze upozorní
2.1.5 Rozšíření ReloadEvery ReloadEvery je rozšíření pro prohlížeč Mozilla Firefox, které lze použít pro pohodlnější manuální sledování volného místa, uvádím ho zde pouze pro úplnost. Protože v současné době žádný z uvedených programů nelze použít, zájemci o uvolněná místa ve vyprodaných spojí musí tyto spoje sledovat ručně. Pomocníkem může být toto rozšíření, které v zadaném intervalu aktualizuje webovou stránku. Okno prohlížeče pak stačí zmenšit a umístit na viditelné místo tak aby nepřekáželo v další činnosti na PC a pouze průběžně sledovat počet volných míst bez nutnosti neustálého ručního aktualizování stránky a zdlouhavého čekání, až se stránka opět načte.
9
2.1. EXISTUJÍCÍ ŘEŠENÍ
2.1.6 Srovnání existujících řešení PHP Skript 1 (2.1.1)
PHP Skript 2 (2.1.4)
SA Notify (2.1.2)
SA Checker (2.1.3)
Programovací jazyk
PHP
PHP
C#
Java
Velikost
8 kB
6 kB
33 kB
1,04 MB
Multi-platformní Grafické rozhraní Sledování více spojů najednou Online načítání času spojů Možnost zadat nové trasy spojů Poradí si s více spoji v 1 čas Minimalizace do oznamovací oblasti (OS Windows-pravý dolní roh)
Lze změnit interval hledání Výrazné nebo nastavitelné zvukové upozornění při úspěchu Volné místo zarezervuje Lze zadat preferovaná sedadla Lze zadat preferovaný spoj (Při více spojích v 1 čas mívá tzv. posilový spoj méně služeb pro cestující)
Jak je vidět z tabulky, všechna řešení mají některé výrazné nedostatky, zároveň ale také každé řešení nabízí i funkcionalitu, kterou zbylá řešení nemají.
10
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
Analýza existujících řešení proto poskytla dobrý základ pro specifikaci požadavků na plugin pro hlídání volných míst v autobusech Student Agency.
2.2
Popis aplikace
2.2.1 Cíloví uživatelé a jejich počítačová gramotnost
Obrázek 2.4: Počítačová gramotnost v ČR (zdroj: http://www.zive.cz) Předpokládanými cílovými uživateli jsou v první řadě všichni zákazníci Student Agency. Mezi lidmi, kteří se Student Agency cestují pravidelně, se dá o aplikaci očekávat poměrně vysoký zájem. Provedl jsem menší orientační průzkum potenciálního zájmu o aplikaci. Respondenty bylo 10 studentů vysoké školy, kteří služeb Student Agency pravidelně využívají (typicky nejméně několikrát do měsíce). Všichni uvedli velký zájem o podobnou aplikaci. Vzhledem k tomu, že jediný společný předpoklad pro uživatele je cestování se společností Student Agency, mohou být uživatelé velmi rozmanití. Silnou skupinu budou jistě tvořit studenti vysokých škol. Zájem o podobnou aplikaci může ale mít kdokoli. S vývojem dalších pluginů, např. pro rezervaci divadel, můžeme navíc očekávat i více uživatelů středního či vyššího věku. Na obrázku 2.4 je graf počítačové gramotnosti obyvatel v ČR (Více o metodice průzkumu a údajích z obrázku viz článek na serveru Živě.cz [11]. Průzkum byl prováděn v roce 2005, dnes tedy může být skutečnost mírně odlišná, pro přibližnou orientaci ale postačí.
11
2.3. SPECIFIKACE POŽADAVKŮ
Ve věku 18-60 let je pouze přibližně třetina populace považována za počítačově gramotnou, třetina neumí s počítačem pracovat vůbec. 2.2.2 Obecné požadavky, forma aplikace Vzhledem ke skutečnostem uvedeným v podkapitole 2.2.1 by navrhovaná aplikace měla být jednoduše spustitelná a maximálně uživatelsky přívětivá – předpokládá se velké množství potenciálních uživatelů, z nichž mnozí mohou mít velmi malé nebo žádné zkušenosti s instalací programů apod. Jednou z uvažovaných forem aplikace bylo i rozšíření pro webové prohlížeče. Výhodou této formy je především možnost používání bez nutnosti instalovat či spouštět samostatnou aplikaci, která zabírá více systémových prostředků a místo na obrazovce či v systémové liště. Nevýhodou je nutnost vývoje pro každý prohlížeč zvlášť a malé obecné povědomí o možnostech rozšíření pro webové prohlížeče. Také zamýšlená modularita aplikace by se obtížně realizovala. Toto řešení by bylo vhodné spíše pro jednoúčelový program pro kontrolu volných míst ve spojích Student Agency. Vzhledem k předpokládaným běžným uživatelům jsem se proto rozhodl pro samostatnou, desktopovou aplikaci, ideálně spustitelnou bez nutnosti instalace.
2.3
Specifikace požadavků
Následující požadavky jsou pro rodičovskou aplikaci, poskytující rozhraní pro jednotlivé pluginy. Specifikace požadavků pro plugin SA Notify je v kapitole 5. 2.3.1 Nefunkční požadavky • Uživatelsky přívětivá. Aplikace by měla být přehledná a jednoduše ovladatelná. • Přenositelná a multiplatformní. Aplikace musí být spustitelná na nejrozšířenějších operačních systémech – MS Windows, MacOS a Linux. • Modulární. Měla by být rozšiřitelná formou zásuvných modulů a poskytovat API rozhraní usnadňující tvorbu nových pluginů. 2.3.2 Funkční požadavky Při sestavování funkčních požadavků jsem vycházel z obvyklých funkcí desktopových programů (zejména pro menu Nápověda) a také z funkcí rozsahem či účelem podobných aplikací, například programu FreeRapid Downloader (http://wordrider.net/freerapid/) od studenta ČVUT-FEL Ing. Ladislava Vitáska. Autor program zveřejnil pod licencí GNU General Public License verze 2 [19] a psal o něm svou diplomovou práci [13], kterou jsem také použil jako literaturu při výběru jednotlivých technologií.
12
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
2.3.2.1
Uživatelské role
Aplikace nevyžaduje žádnou údržbu, nezahrnuje správu uživatelských účtů a obecně neobsahuje funkce, které by měly být přístupné jen některým uživatelům. Existuje proto jen jedna uživatelská role. 2.3.2.2
Případy užití
Následující UML diagramy zahrnují všechny základní případy užití aplikace. Při tvorbě diagramů jsem se snažil respektovat obecná doporučení pro tvorbu UML diagramů – udržovat diagramy přehledné, v jednom diagramu mít ideálně nejvíce 7 položek. 2.3.2.3
Nastavení programu
Obrázek 2.5: Nastavení programu • Zapnout Tichý režim - aplikace v Tichém režimu nevydává žádné zvuky - v Nastavení aplikace je možné upřesnit chování Tichého režimu
13
2.3. SPECIFIKACE POŽADAVKŮ
• Ukládat při ukončení - uživatel může zvolit ukládání stávajících hledání při ukončení aplikace. - ve výchozím nastavení je tato možnost zapnutá • Spouštět při startu - uživatel může zvolit automatické spouštění aplikace po startu operačního systému - ve výchozím nastavení je tato možnost vypnutá • Nastavit minimalizaci do oznamovací oblasti - aplikace se při minimalizaci skryje do oznamovací oblasti v pravém dolním rohu (OS Windows) • Zobrazit Možnosti - aplikace zobrazí okno s možnostmi nastavení. Více viz diagram 2.6 2.3.2.4
Nastavení – Možnosti
Diagram na obrázku 2.6 představuje konkrétní možnosti případu užití Zobrazit Možnosti z obrázku 2.5. • Kontrolovat aktualizace po spuštění - aplikace po spuštění zkontroluje, zda je k dispozici nová verze. - pokud ano, upozorní uživatele na existenci nové verze • Změnit jazyk - uživatel může změnit jazyk aplikace - výchozím jazykem je čeština • Správa pluginů Aplikace zobrazí seznam instalovaných pluginů s těmito informacemi: - jméno pluginu - verze - stav pluginu – Aktivní, nebo Neaktivní (uživatel může stav změnit) - autor pluginu • Změnit vzhled Uživatel může upravit vzhled aplikace: - může změnit velikost ikon v menu - může změnit barvy pozadí jednotlivých pluginů
14
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
Obrázek 2.6: Možnosti nastavení programu • Vytvořit zástupce - uživatel může zvolit vytvoření zástupce programu na Ploše, v nabídce Start nebo ve složce Po spuštění (vše pouze na OS Windows) • Zvuky a upozornění Uživatel může: - změnit nebo vypnout zvuk aplikace při nalezení volného místa pluginem. Může si vybrat z přednastavených zvuků, nebo vybrat zvuk vlastní. - změnit nebo vypnout zvuk aplikace při zobrazování upozornění nebo chyb. Může si vybrat z přednastavených zvuků, nebo vybrat zvuk vlastní. - vypnout upozorňování na běžící vyhledávání při vypínání programu. - specifikovat chování aplikace v Tichém režimu: - vypnout, nebo zapnout zvuky v Tichém režimu - vypnout, nebo zapnout vyskakující upozornění v Tichém režimu
15
2.3. SPECIFIKACE POŽADAVKŮ
Program může být spuštěn dlouhou dobu, po kterou se může měnit okolní prostředí. Je proto důležité, aby si uživatel mohl nastavit vlastní profil (vyhovující systém upozornění a zvukových upozornění) a aby tento mohl jednoduše měnit, respektive vypínat všechny zvuky zapnutím Tichého režimu. 2.3.2.5
Nápověda
Obrázek 2.7: Nápověda • Zobrazit nápovědu - aplikace otevře nápovědu ve webovém prohlížeči Rozhodl jsem se pro nápovědu k rodičovské aplikaci i nápovědu k pluginům ve formě HTML a k jejich umístění na webový server. Tuto formu využívají i jiné programy. Většinou se jedná o lokální HTML stránky, distribuované spolu s programem. Má aplikace je ale použitelná pouze s funkčním internetovým připojením, není tedy důvod distribuovat nápovědu spolu s aplikací. Umístění na webových stránkách má navíc výhodu ve snadnější správě nápovědy a možnosti rychle reflektovat časté problémy uživatelů např. doplněním sekce „Často kladené otázky“. • Nápověda k pluginům - aplikace otevře ve webovém prohlížeči nápovědu k jednotlivým pluginům
16
KAPITOLA 2. POPIS APLIKACE, POŽADAVKY NA ŘEŠENÍ
• Zkontrolovat aktualizace - aplikace zkontroluje, zda je k dispozici novější verze. Pokud ano, nabídne možnost přejít na webovou stránku s novou verzí. • Přejít na domovskou stránku - aplikace otevře ve webovém prohlížeči svojí domovskou stránku • Zobrazit licenční ujednání - aplikace zobrazí název licence, pod kterou je distribuována a její stručný popis - zobrazí také webový odkaz na plné znění licenčního ujednání • Zobrazit info o programu Aplikace zobrazí informace o programu, zejména: - aktuální verze - datum vydání aktuální verze - autor aplikace - krátké info o pozadí vzniku aplikace
17
2.3. SPECIFIKACE POŽADAVKŮ
18
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
Kapitola 3
Analýza a návrh řešení 3.1
Použité technologie
3.1.1 Java 1.7 Vzhledem k požadavku na snadnou přenositelnost a multiplatformnost aplikace jsem zvolil programovací jazyk Java 1.7. Verze 1.7 dosud není mezi uživateli příliš rozšířená, oficiálně uvedena a nabídnuta ke stažení na serveru Java.com na stránce Download (http://www.java.com/download/) byla v květnu 2012. Např. server Slunečnice.cz ale verzi 1.7. nabízí již od února 2012. Odkaz na server Slunečnice je přitom na prvním místě vyhledávání hesla „java download“ ve dvou dominantních webových vyhledávačích v ČR (Seznam a Google). Lze tedy očekávat brzké rozšíření této verze. Uživatelům, kteří Javu 1.7 dosud nemají, by zprovoznění aplikace nemělo dělat přílišné problémy ani při případné distribuci bez Java Runtime Enviroment. Nejnovější verze má řadu výhod (plný výčet nových vlastností viz [4]). Vedle těch obecných (vylepšená stabilita, výkon, bezpečnost) obsahuje množství drobných vylepšení, která usnadňují programování. Z nových vlastností se ukázaly přínosné například: • Zjednodušený zápis generických instancí Místo L i s t <S t r i n g > s t r i n g s = new A r r a y L i s t <S t r i n g > ( ) ; lze nyní použít L i s t <S t r i n g > s t r i n g s = new A r r a y L i s t ( ) ;
19
3.1. POUŽITÉ TECHNOLOGIE
• Řetězení výjimek V konstrukci try{ } catch{ } lze použít zřetězení výjimek. Chceme-li odchytit více výjimek, není nutné psát pro každou vlastní catch{ } : try { C l a s s a = C l a s s . forName ( " wrongClassName " ) ; } c a t c h ( ClassNotFoundException | I l l e g a l A c c e s s E x c e p t i o n | I n s t a n t i a t i o n E x c e p t i o n ex ) { . . . } Tuto vlastnost považuji z nových syntaktických vlastností za nejužitečnější, velmi pomáhá zlepšit přehlednost kódu. • Switch pro String Dosud bylo možné příkaz switch použít pouze pro proměnné typu integer. • Konstrukce try-with-resources Nová konstrukce umožňuje vynechat blok finally { } při otevírání „resources“. Použijemeli try-with-resources, Java se sama postará o zavření např. příslušného InputStreamu, ať už blok try{ } skončí chybou, nebo ne: t r y ( B u f f e r e d R e a d e r i n=new B u f f e r e d R e a d e r ( new F i l e R e a d e r ( " f i l e " ) ) ) { String line = null ; w h i l e ( ( l i n e = i n . r e a d L i n e ( ) ) != n u l l ) { System . out . p r i n t l n ( l i n e ) ; } } c a t c h ( IOException ex ) { . . . }
3.1.2 SWT Standard Widget Toolkit je open-source knihovna pro tvorbu grafických aplikací v Javě, která je ve správě nadace Eclipse Foundation. Je alternativou ke knihovně Swing, jenž je součástí Java SE. Knihovna SWT je uvolněna pod licencí Eclipse Public License v 1.0 [3]. Tato licence umožňuje libovolné použití knihovny, jedinou podmínkou je, že při zveřejnění zdrojových kódů musí být tyto zveřejněny pod stejnou licencí. 3.1.2.1
Výhody SWT
Komponenty Operačního sytému SWT na rozdíl od knihovny Swing nevykresluje jednotlivé GUI komponenty samostatně, ale používá komponenty operačního systému. Vykreslování je proto rychlejší a SWT má mnohem menší paměťové nároky. Například autoři přednášky SWT & MigLayout z cyklu přednášek Czech Java User Group uvádí příklad, kdy při testování stejné aplikace napsané v SWT i ve Swingu zabírala
20
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
aplikace používající knihovnu Swing stabilně 9 MB operační paměti, zatímco verze používající SWT maximálně 800 kB [22]. Univerzální konstanty SWT také nabízí funkce pro jednoduchou tvorbu multiplatformních GUI. Například při definování klávesových zkratek nebo jejich vypisování uživateli lze použít konstanty místo názvů kláves – tedy např. místo použití klávesy Ctrl, která na některých platformách není, lze použít konstantu SWT.MOD1. Knihovna při spuštění konstantu nahradí odpovídajícím tlačítkem na dané platformě. Obdobně lze použít konstanty i pro další funkční klávesy, ale také pro ikony: Univerzální ikony Konstrukce display.getSystemImage(SWT.ICON_ERROR) vrátí ikonu, kterou daná platforma používá pro chybové hlášky. Při využití těchto vlastností tak aplikace spuštěná na určitě platformě vypadá, jako by byla psaná nativně pro tuto platformu. Tato vlastnost může významně zvýšit uživatelský komfort při práci s aplikací – umožňuje uživatelům pracovat v prostředí, na které jsou zvyklí. SWT Snippets SWT Snippets je sbírka příkladů použití SWT komponent, dostupná na www.eclipse.org/swt/snippets/ a poskytovaná pod stejnou licencí jako SWT ([3]). Sbírka obsahuje velké množství jednoduchých i složitějších příkladů implementace nejrůznějších komponent a jejich vlastností. Spolu s kvalitní dokumentací je tak další výhodou SWT knihovny. 3.1.2.2
Nevýhody SWT
Různé verze pro různé platformy Hlavní nevýhoda plyne z první uváděné výhody SWT: díky používání systémových komponent je třeba používat pro každou platformu jinou verzi SWT a dále na zvolené platformě používat verzi podle bitovosti JRE (32, nebo 64 bitů). Je tedy nutné vytvořit samostatný distribuční balíček pro každou platformu nebo vytvořit balíček univerzální, obsahující všechny verze SWT, a manuálně načíst správnou verzi podle platformy, na které je aplikace spuštěna. Nelze dědit Další nevýhodou je nemožnost dědění ze základních komponent. Tato vlastnost opět souvisí s více verzemi SWT – jedna GUI komponenta bude mít různé implementace pro různé platformy. I námi vytvořená podtřída by proto musela brát tyto odlišnosti v úvahu a mít různé verze pro různé platformy. SWT má ale komponenty Composite a Canvas, které dědění umožňují (komponenta, dále také widget, Composite je nadtřídou všech ostatních komponent). Ruční uvolňování prostředků Poslední významnou nevýhodou je nutnost ručního uvolňování systémových prostředků [14]. Konkrétně všechny vytvořené fonty, barvy a obrázky je nutné ručně uvolnit zavoláním jejich metody .dispose(), jinak by došlo k úniku paměti (memory leak). Tím je omezena jedna
21
3.1. POUŽITÉ TECHNOLOGIE
z velkých výhod Javy např. oproti jazykům C/C++. Jedno vlákno SWT běží pouze v jednom vlákně a pokus o přístup k widgetům z jiného vlákna skončí chybou – přístup je nutné provádět pomocí konstrukce D i s p l a y . g e t D e f a u l t ( ) . asyncExec ( new Runnable ( ) { @Override p u b l i c v o i d run ( ) { // kód p ř i s t u p u j í c í k SWT komponentům // i n i c i a l i z o v a n ý m v jiném v l á k n ě } });
Klady a zápory SWT (ve srovnání s knihovnou Swing):
+ Nativní vzhled na všech platformách
- Nelze dědit
+ Nativní chování na všech platformách (funkční tlačítka, ikony)
- Složitější distribuce (jiná verze SWT pro každou platformu)
+ Rychlejší načítání
- Ruční uvolňování prostředků
+ Menší systémové nároky
- Jedno vlákno
Použití nativních komponent operačního systému, zajišťující Look&Feel odpovídající danému OS a rychlejší vykreslování, dle mého názoru daleko převáží všechny nevýhody SWT, které jsou navíc nevýhodami především pro vývojáře a ne pro cílového uživatele. 3.1.3 MigLayout MigLayout je open source Java Layout Manager pro Swing i SWT, který je volně ke stažení na stránce www.miglayout.com. Jeho tvůrce se snažil o vytvoření Layout Manageru, který dokáže nahradit všechny standardní Java Layout Managery (FillLayout, GridLayout atd.) a přitom si zachová jednoduchost a přehlednost. MigLayout je šířen pod BSD [28] nebo GNU GPL [20] licencí (autor dává uživatelům na vybranou) a pravděpodobně bude přidán do Javy SE (má nejvíce hlasů mezi požadavky na změny v Javě na http://bugs.sun.com). Existuje pro něj i plugin do IDE NetBeans, umožňující tvorbu GUI ve vizuálním editoru, systémem Drag&Drop, při vývoje aplikace jsem se ale rozhodl dát přednost manuálnímu programování MigLayoutu.
22
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
3.1.3.1
Rychlost MigLayoutu s SWT
V benchmarcích (např. [10] nebo již zmiňovaná přednáška o SWT a MigLayoutu [22]) vycházejí rychlejší aplikace za použití MigLayoutu v kombinaci s knihovnou Swing, rozdíly nejsou ale příliš výrazné (kolem 10%) a velmi závisí na použitém Look&Feel ve Swingu. Při použití SWT s MigLayoutem aplikace navíc rychleji startuje (v benchmarku [10] trvá start 203 ms versus 360 až 390 ms pro Swing+MigLayout). Při testování demo aplikace psané pomocí SWT a MigLayoutu (k dispozici na miglayout.com) mi odezva přišla subjektivně velice dobrá, srovnatelná s odezvou oken operačního systému.
3.1.3.2
Použití MigLayoutu
MigLayout nabízí mnoho způsobů nastavení layoutu pomocí mnoha různých parametrů. Jednotlivé parametry se v SWT zadávají pomocí konstrukce w i d g e t . setLayoutData ( S t r i n g [ z ř e t ě z e n é _ p a r a m e t r y ] ) ; Jistou nevýhodou je, že díky zadávání parametrů a jejich hodnot ve Stringu MigLayout neumožňuje typovou kontrolu, programátor je upozorněn na syntaktickou chybu až při překladu. Přehled všech parametrů MigLayoutu je k dispozici na jeho domovské stránce. Obecně layout umožňuje jednoduše definovat pozici komponent relativní i absolutní, jejich zarovnání i zarovnání jejich vnitřku, odsazení, mezery mezi řádky i sloupci, způsob vyplňování volného místa, minimální, preferovanou a maximální velikost komponenty atd. K zajímavým parametrům layoutu patří např.: • debug: Tento parametr způsobí vykreslení čárkovaného rámečku okolo všech komponent. Lze tak snadno zkontrolovat jejich rozložení a zjistit, která komponenta narušuje zamýšlené rozložení. • sizegroup [group_name]: Komponenty se stejnou sizegroup budou mít stejnou velikost (velikost největšího z nich). • hidemode: Lze nastavit chování skryté komponenty (widget.setVisible(false);): – hidemode 0: S neviditelnou komponentou je zacházeno stejně jako s viditelnou (zabírá místo) – hidemode 1: Velikost komponenty je nastavena na 0 × 0. – hidemode 2: Velikost komponenty je nastavena na 0 × 0 a mezery kolem ní (margin) také na 0. – hidemode 3: Komponenta se neúčastní layoutu, není brána v úvahu.
23
3.1. POUŽITÉ TECHNOLOGIE
3.1.4 Jsoup Vzhledem k tomu, že základem činnosti všech zamýšlených pluginů bude pravděpodobně syntaktická analýza webových stránek a extrahování informací na nich uvedených, bylo důležité vybrat vhodnou technologii usnadňující tuto činnost. Jsoup (http://jsoup.org/)) je Java HTML parser, poskytovaný pod MIT licencí [23], který umožňuje: • Načíst HTML stránku z URL, Stringu nebo souboru • Procházet a extrahovat data pomocí HTML DOM (A.2) nebo za použití CSS selektorů • Odesílat a přijímat HTML requesty, nastavovat jejich parametry (hlavičky, cookies, data,...) • a další (viz http://jsoup.org/). Jsoup umožňuje nejen jednoduché a rychlé extrahování hledaných dat z HTML dokumentu, ale díky možnosti odesílat HTML requesty a libovolně nastavovat jejich parametry také dokáže věrně simulovat webový prohlížeč, včetně odesílání GET či POST requestů apod. 3.1.5 Eclipse JFace Data Binding Aplikace obsahuje množství formulářů s možnostmi nastavení a další formuláře lze očekávat pro předvolby pluginů. Je proto vhodné použít knihovnu usnadňující provázání hodnot navolených ve formuláři s objekty, které daný formulář reprezentují. Pro knihovnu Swing existuje JGoodies binding [5], snažil jsem se proto najít alternativu pro SWT. Jednou ze zvažovaných možností byla knihovna SWT Binding od Mathiase Mullera [29]. SWT Binding je uvolněn pod BSD licencí [28] a je založen na zmiňovaném JGoodies binding, které portuje pro SWT. Nejnovější verze (0.2) je ovšem z roku 2009 a je nekompletní (umí obsluhovat jen některé widgets). Nekompletní je také dokumentace. Existuje sice oficiální javadoc dokumentace, většina funkcí ale komentář postrádá. Rozhodl jsem se proto pro použití JFace Data Binding. JFace Data Binding je součástí vývojového prostředí Eclipse. Pro vývoj aplikace jsem používal prostředí NetBeans, bylo proto nutné importovat potřebné knihovny. Framework umožňuje pomocí jednoduchých konstrukcí provázat GUI komponenty s Java bean, respektive s jakýmkoli objektem, který má pro parametr provázaný s komponentou getter a setter. Není tak nutné přidávat pro každou komponenty ve formuláři selection nebo modification listenery ani ji ručně aktualizovat při změně hodnoty příslušného fieldu, o vše se postará JFace Data Binding.
24
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
3.2
Analytický model
V této kapitole je rozebrán analytický model aplikace. Popisuji zde jednotlivé třídy a jejich vzájemné vztahy. 3.2.1 Základní struktura aplikace Struktura základní části aplikace je zobrazena na obrázku 3.1.
Obrázek 3.1: Základní struktura aplikace
MainClass je statická třída (obsahuje pouze statické metody a fieldy – diskuzi o použití statických vs. dynamických tříd jsem provedl v kapitole 3.2.6) obsahující metodu main (). Dále obsahuje proměnné s informacemi o programu – např. verze programu, autor apod. Tyto informace jsou tak jednoduše přístupné z kterékoli části aplikace. BaseAppGui je třída, instanciovaná třídou MainClass, která vytvoří a otevře GUI aplikace. Stará se tedy o inicializaci všech potřebných komponent, včetně jednotlivých pluginů – k tomu jí slouží třída PluginLoader. PluginLoader má na starosti načítání jednotlivých pluginů i jejich ukončování (implementuje případ užití „Ukládat při ukončení“).
25
3.2. ANALYTICKÝ MODEL
MyMenu je abstraktní třída, která je nadtřídou všech položek v hlavním menu aplikace. Obsahuje společné metody pro položky menu. MyFileMenu reprezentuje menu „Soubor“. MySettingsMenu reprezentuje menu „Nastavení“ a implementuje tak případy užití z kapitoly 2.3.2.3. MyHelpMenu reprezentuje menu „Nápověda“ a případy užití z kapitoly 2.3.2.5. Podrobnější zobrazení návrhu menu aplikace je v kapitole 3.2.2. 3.2.2 Menu aplikace
Obrázek 3.2: Menu aplikace
26
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
Na obrázku 3.2 je menu aplikace 1 . Třídy, které jsou inicializovány v objektech MySettingsMenu a MyHelpMenu reprezentují jednotlivé případy užití: • MyMenu_Save ... případ užití „Ukládat při ukončení“ • MyMenu_Startup ... „Spouštět při startu“ • MyMenu_Minimize ... „Nastavit minimalizaci do oznamovací oblasi“ • Preferences ... „Zobrazit Možnosti“ a všechny související případy užití z kapitoly 2.3.2.4 • UpdateChecker ... „Zkontrolovat aktualizace“ • About ... „Zobrazit info o programu“ • About_license ... „Zobrazit licenční ujednání“ 3.2.3 Správa pluginů
Obrázek 3.3: Správa pluginů Jak je vidět na obrázku 3.3, třída PluginLoader obsahuje 0...* referencí na interface PluginInterface, který implementují všechny pluginy. PluginLoader obsluhuje pluginy (může 1
Dvojtečka před jménem třídy značí, že se jedná o instanci dané třídy
27
3.2. ANALYTICKÝ MODEL
je inicializovat, zobrazit, uložit atd.) přes PluginInterface, nezáleží proto, o jaký konkrétní plugin se jedná. Abstraktní třída Plugin obsahuje společné metody pluginů, aby nedocházelo ke zbytečným duplicitám kódu v jednotlivých pluginech. 3.2.4 Menu pluginů
Obrázek 3.4: Menu pluginů Menu pluginů představuje zároveň jakousi záložku, přes kterou uživatel vybere daný plugin (viz návrh GUI aplikace na obrázku 3.6). Menu tedy může mít 2 stavy – menu aktivního pluginu (plugin je vybrán, jeho tělo je momentálně zobrazeno uživateli) a menu neaktivního pluginu. Pro menu byl proto použit návrhový vzor Stav. PluginLoader zavolá metodu PluginInterface.getMenu(), která vrátí příslušné menu pro aktivní, nebo neaktivní plugin. Každý plugin má vlastní třídu pro aktivní menu, díky čemuž může být aktivní menu libovolně uzpůsobené konkrétnímu pluginu (na obr. 3.6 je jako příklad menu pluginu SA Notify – SAMenu).
28
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
Naopak neaktivní menu mají všechny pluginy společné a reprezentuje ho třída IdleMenu. Tím je zajištěn jednotný vzhled lišty s menu jednotlivých pluginů. Dále jsem navrhl dvě abstraktní třídy, PluginMenu a PluginActiveMenu, které obsahují metody společné pro všechny druhy menu (PluginMenu) a metody společné pro menu aktivního pluginu (PluginActiveMenu). 3.2.5 Univerzální třídy
Obrázek 3.5: Univerzální třídy Na obrázku 3.5 jsou třídy, které mohou být instanciovány v kterékoli části aplikace nebo se jedná o třídy statické. V obou případech jsou to třídy poskytující nějakou univerzální funkci, která je potřeba v různých částech aplikace. ImageHandler je statická třída, určená k načítání všech obrázků. Vzhledem k nutnosti ručního disposingu (viz 3.1.2.2 – odstranění objektu a uvolnění jeho systémových prostředků) obrázků jsem se rozhodl pro umístění všech objektů typu Image do této třídy. Tím je zajištěna snadnější správa všech obrázků a sníženo nebezpečí, že některé obrázky nebudou disposovány. Při zavírání aplikace je zavolána metoda ImageHandler.dispose(), která zajistí disposing všech obrázků. ImageHandler má veřejné (public) metody pro načtení obrázku ze složky zadané v parametru metody. Jednotlivé pluginy tak mohou mít vlastní obrázky ve své složce a vlastní třídu pro načítání obrázků, dědící od třídy ImageHandler a využívající její public metody. ErrorHandler je metoda pro zobrazování chyb aplikace uživateli. Obsahuje statický objekt Messenger messenger, inicializovaný při spouštění aplikace, který zajišťuje zobrazení chyb v dialogovém okně. Objekt messenger je public, pluginy tedy opět mohou dědit od této třídy a využívat její, již inicializovaný, Messenger.
29
3.2. ANALYTICKÝ MODEL
CommentedProperties Aplikace potřebuje při ukončení ukládat mnoho proměnných. K tomu je vhodná třída Properties, poskytující jednoduché API pro načtení konfiguračního souboru a získávání hodnot z tohoto souboru. Nevýhodou třídy Properties, která je součástí Javy SE, je mazání komentářů: načteme-li obsah souboru, který kromě hodnot obsahuje i komentáře nebo prázdné řádky, do objektu Properties, ten upravíme a opět uložíme, původní soubor je přepsán a přijdeme o uložené komentáře a formátování konfiguračního souboru. Použil jsem proto upravenou verzi CommentedProperties extends Properties z diskuzního fóra www.dreamincode.net, kde ji uživatel waltf zveřejnil k volnému použití. Tato třída zachovává komentáře i volné řádky v konfiguračním souboru. Rozdíly CommentedProperties oproti Properties: • metoda setProperty(String key, String value) funguje jen pokud už CommentedProperties daný klíč obsahuje, jinak neudělá nic (Properties.setProperty() v takovém případě klíč vytvoří). • CommentedProperties mají proto metodu add(String key, String value), která vloží na konec souboru novou položku. Nekontroluje přitom, zda už se použitý klíč v properties nachází. Upravil jsem proto tuto metodu tak aby zkontrolovala, zda se zadaný klíč v properties již nachází a následně ho buď vytvořila, nebo pouze změnila jeho hodnotu zavoláním metody setProperty(). • CommentedProperties mají metody addLine(String line), která na konec souboru přidá komentář. Při vytváření nového klíče je tak možné před něj programově přidat i komentář. MenuListeners obsahuje množství metod s návratovou hodnotou Listener, které vrací Listenery opakovaně používané zejména v menu aplikace. Jsou to například Listenery zajišťující zobrazení, nebo skrytí textu ve stavovém řádku (za tímto účelem má MenuListeners statický field Label statusBar, inicializovaný při startu aplikace) nebo Listenery zajišťující otevření URL adresy ve webovém prohlížeči. Messenger zajišťuje zobrazování zpráv uživateli a to jak chybových, tak informačních. Vytvoří nový Shell dialog se zprávou a ten zobrazí uprostřed okna aplikace. Buttons je třída usnadňující tvorbu tlačítek (SWT Button). U většiny vytvářených tlačítek jsou nastavovány vždy ty samé parametry, třída Buttons proto obsahuje univerzální metody pro vytvoření tlačítka a nastavení nejobvyklejších parametrů, jako jsou Text, ToolTipText, StatusBarText ad. Sounds obsahuje metody pro přehrávání zvuků a také hodnoty z CommentedProperties pro nastavení zvuků (jaké zvuky a kdy se mají přehrávat). Její instance je předávána jednotlivým pluginům, plugin tedy v případě potřeby může použít vlastní třídu a přehrávat vlastní zvuky.
30
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
Výchozí nastavení zvuků, včetně cest k přehrávaným zvukům, je načteno z CommentedProperties v konstruktoru této třídy. Pro případ, že v CommentedProperties požadované položky nejsou, jsou nastaveny výchozí hodnoty. Commons je statická třída obsahující univerzální metody, které není vhodné zařadit do žádné z předchozích tříd. Její součástí jsou metody • int getIntegerProperty() • boolean getBoolProperty() • String getStringProperty(), které mají shodné parametry CommentedProperties properties, String key, String defaultValue. Všechny tři vracejí hodnotu z CommentedProperties, parsovanou do návratového typu metody. V případě, že properties hledaný parametr neobsahují, nebo dojde k chybě parsování (např. položka, která má obsahovat číslo, obsahuje text) vrátí defaultValue a tuto hodnotu také uloží do properties. 3.2.6 Statické vs. Dynamické třídy Pozn.: Java nezná statické třídy, tímto pojmem označuji třídy, které mají statické všechny fieldy i metody. Obecně není doporučováno nadměrné použití statických metod či fieldů ([9], [7]). Například budou-li všechna konfigurační data aplikace v podobě statických fieldů v jedné třídě, není možné spouštět aplikaci nebo části aplikace simultánně v různých konfiguracích. Naopak použití centrálních, statických fieldů velmi zjednodušuje jejich úpravu, přístup k nim apod. U atributů typu název aplikace lze přitom snadno předpokládat, že si i do budoucna vystačíme s jediným statickým fieldem. Všechny univerzální třídy uvedené v předchozí kapitole by teoreticky mohly být dynamické i statické. Snažil jsem se proto u každé z nich zvážit konkrétní situaci a způsob použití a zvolit výhodnější možnost. Pokud jsem nenašel konkrétní důvody pro statičnost třídy nebo jejích fieldů, navrhl jsem je jako dynamické. Třídy ErrorHandler, Buttons nebo MenuListeners jsou dynamické. ErrorHandler má ale statický field Messenger messenger. Messenger pro inicializaci potřebuje jako parametr Display a Shell, ErrorHandler může být ale volán z libovolného místa aplikace, kde Shell ani Display nemusí být k dispozici. Jednoduchým a účelným řešením je tedy deklarovat field Messenger jako statický a inicializovat ho při startu aplikace. MenuListeners původně měly po vzoru SWT Snippet 152 některé Listenery jako statické fieldy: p u b l i c s t a t i c L i s t e n e r s h o w L i s t e n e r = new L i s t e n e r ( ) { @Override p u b l i c v o i d handleEvent ( Event e v e n t ) { . . . . }};
31
3.3. NÁVRH GRAFICKÉHO ROZHRANÍ APLIKACE
V takovém případě by se ale zbytečně všechny tyto Listenery inicializovaly při vytvoření každé instance MenuListener (i těch, které potřebují jiný Listener, tzn. např. tento field Listener showListener vůbec nepoužijí). Raději jsem proto pro každý Listener vytvořil dynamickou metodu s návratovou hodnotou Listener. Třída ImageHandler je z dříve uvedených důvodů statická (snadnější správa obrázků). CommentedProperties jsou předávány objektům jako parametr. Zde by mohlo dojít k výše popsané situaci, kdy budeme chtít určité části programu předat pozměněná konfigurační data, je tedy bezpečnější předávat je jako parametr – instanci třídy CommentedProperties. Label statusBar, reprezentující stavový řádek aplikace, je statický. Nepředpokládám potřebu více různých stavových řádků a vzhledem k tomu, že ke stavovému řádku potřebuje přístup mnoho různých tříd, statický Label výrazně usnadňuje programování. Konfigurace Tichého režimu, včetně booleanu isQM, který udává, zda je Tichý režim zapnutý, je z podobných důvodů jako statusBar statická. Zde je dokonce žádoucí, aby část aplikace nemohla použít jiné nastavení – zapne-li uživatel Tichý režim, pravděpodobně nebude chtít, aby např. některý plugin měl vlastní nastavení Tichého režimu a globální nastavení ignoroval.
3.3
Návrh grafického rozhraní aplikace
Na obrázku 3.6 je návrh GUI rodičovské aplikace a pluginu SA Notify2 , v této kapitole se zabývám pouze GUI rodičovské aplikace, které je tvořeno systémovým menu, menu s výběrem pluginů (dále jako menu pluginů nebo plugin menu) a stavovým řádkem (ten na obrázku není). Při návrhu jsem se snažil ponechat maximum místa pro tělo pluginu, zároveň ale udělat plugin menu dostatečně výrazné a velké, aby si ho uživatel ihned všiml. Jednotlivé položky v plugin menu mají různé barvy a jejich barva je zároveň použita jako podkladová pro tělo konkrétního pluginu (plugin body). Díky tomu si uživatel může asociovat barvy s jednotlivými pluginy a i při letmém pohledu na otevřenou aplikaci ihned pozná, který plugin je právě zvolen. To může být důležité zejména v případě vzniku většího množství pluginů, které budou mít podobné funkce (např. rezervaci autobusových spojů, kdy každý plugin bude umět rezervaci jiného dopravce) a budou mít tedy velice podobné rozložení prvků v plugin body. Na obrázku 3.7 je Splash Screen aplikace – obrázek, který se zobrazí ihned po startu aplikace a je zavřen po načtení celé aplikace. Slouží tedy jako ukazatel pro uživatele, že aplikace byla spuštěna a probíhá její načítání.
2 Návrh je určen především ke znázornění rozložení jednotlivých komponent. Jejich skutečná podoba (zejména rámečky, barva apod.) je závislá na platformně, na které je aplikace spuštěna.
32
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
Obrázek 3.6: Návrh grafického rozhraní
Obrázek 3.7: Splash Screen
33
3.3. NÁVRH GRAFICKÉHO ROZHRANÍ APLIKACE
34
KAPITOLA 4. IMPLEMENTACE
Kapitola 4
Implementace Vývoj aplikace probíhal v IDE NetBeans 7.1 na OS Windows 7. Zároveň jsem prováděl kontrolu správného zobrazení i chování aplikace na OS Windows XP a Ubuntu (GUI GNOME). Zjištěné rozdíly uvádím v kapitole 6.3.
4.1
Univerzální třídy
4.1.1 ImageHandler Obrázků jsou řádově desítky, ImageHandler proto používá Just In Time (JIT) inicializaci: Každý obrázek je uložen jako private field a má svůj getter a setter. V getteru je zkontrolováno, zda byl obrázek inicializován a pokud ne, je zavolán příslušný setter. ImageHandler odchytí výjimky způsobené špatným formátem souboru, nebo neplatným názvem souboru. V takovém případě vrátí getter pro požadovaný obrázek hodnotu null. Dále v aplikaci je proto nutné ošetřit používání obrázku, který nesmí být null. Jedná se například o metodu shell.setImages(Image [ ]), která nastaví ikonu v záhlaví okna1 . Při každém nastavování ikon Shellu je tedy nutné tuto metodu obklopit konstrukcí try { . . . } catch ( IllegalArgumentException e ) {
. . .
}
Animované GIF SWT neumí jednoduše zobrazit animovaný GIF soubor. Použili jsem proto třídu AnimatedGif, založenou na SWT Snippet 141, která spustí nové vlákno, starající se o animaci obrázku. Vlákno se ukončí, pokud zjistí, že Shell, pro který byl obrázek určen, byl ukončen (isDisposed() ). Zároveň je nastaveno jako daemon, takže se ihned ukončí v případě ukončení celé aplikace. 1
V závislosti na platformě je nastavený obrázek používán nejen v záhlaví okna, ale také na systémové liště nebo při procházení otevřených oken pomoci Alt+Tab (OS Windows). Lze proto nastavit více obrázků různých velikostí a SWT knihovna použije v konkrétních případech nejvhodnější velikost. Pro aplikaci jsem použil 2 ikony o velikosti 16 × 16 a 32 × 32 pixelů
35
4.1. UNIVERZÁLNÍ TŘÍDY
Metoda getImage(String fileName) Většina obrázků je ve třídě ImageHandler uložena jako samostatný field, který má svůj getter a setter. Někdy je ale potřeba načítat nové obrázky dynamicky za běhu programu. (např. výběr jazyka, viz 4.2.5.3). K tomu slouží metoda getImage(String fileName), která vrátí obrázek načtený ze zadaného souboru, který zároveň uloží do ArrayListu imagesToDispose. Při ukončení aplikace jsou všechny obrázky z tohoto listu automaticky disposovány. ImageCodeGen Vytvořil jsem také jednoduchou třídu ImageCodeGen, generující kód pro ImageHandler. Kód nutný vytvořit pro každý obrázek je obdobný, mění se v zásadě pouze jméno proměnné a soubor s obrázkem, obrázků je přitom mnoho, je tedy výhodné použít generátor kódu. Velikost obrázků Snažil jsem se, aby všechny použité obrázky i ikony zabíraly co nejméně diskového prostoru. Většina obrázků je proto ve formátu GIF. Při vytváření GIF formátu jsem zároveň volil nejmenší počet barev, při kterém se subjektivně ještě nesnížila kvalita obrázku. 4.1.2 Messenger Existuje jen jedna instance třídy Messenger, která je předávána třídám jako parametr. Díky tomu v případě, že uživatel změní nastavení vyskakovacích zpráv (případ užití „Zvuky a upozornění“), je tato změna promítnuta do instance třídy Messenger a změny se ihned projeví ve všech nových vyskakovacích zprávách. Messenger zobrazuje 4 základní druhy zpráv: • message_info – informační zpráva • message_warning – zpráva obsahující varování • message_error – zpráva o chybě • message_waiting – zpráva o probíhající akci, na jejíž dokončení aplikace čeká (např. pokus o aktualizaci) Díky tomuto dělení zpráv je možné budoucí rozšíření např. o přehrávání různých zvuků podle typu zprávy, pro zvýraznění jejího typu (Varování, Chyba ad.). Metoda message_waiting bývá volána z jiného vlákna, proto na začátku kontroluje, zda Shell aplikace nebyl mezitím zavřen (tento postup je doporučen v dokumentaci SWT). Všechny metody pro zobrazování zpráv mají parametr boolean successMessage, udávající, zda se jedná o zprávu o úspěchu hledání některého pluginu. Pokud je successMessage true, je přehrán zvuk nastavený pro úspěšné hledání (metoda Sound.playSuccessSound() ). Ikony Ikony zobrazované u zpráv jsou pro věrnější přizpůsobení vzhledu platformě nastavovány pomocí SWT konstant (např. display.getSystemImage(SWT.ICON_ERROR), viz kapitola 3.1.2.1).
36
KAPITOLA 4. IMPLEMENTACE
ICON_CANCEL
ne
ICON_ERROR
ano
ICON_INFORMATION
ano
ICON_QUESTION
ano
ICON_SEARCH
ne
ICON_WARNING
ano
ICON_WORKING
ano
Tabulka 4.1: SWT ikony k dispozici na OS Windows 7 Některé ikony definované pomocí SWT.ICON ovšem na některých platformách nejsou k dispozici (tabulka 4.1 zobrazuje ikony, které jsou k dispozici na OS Windows 7). V takovém případě je použita odpovídající vlastní ikona, inicializovaná ve třídě ImageHandler. 4.1.3 Sounds Třída Sounds obsahuje metody pro přehrávání zvuku i pro ukončení přehrávání. Přednastavené zvuky jsou dostatečně krátké aby nebylo nutné jejich přehrávání zastavovat. Uživatel si ale může navolit i vlastní libovolně dlouhé zvuky, jejichž přehrávání musí být možné programově zastavit. Stejně tak budoucí pluginy mohou vyžadovat funkci zastavení přehrávaného zvuku. Licence použitých zvuků Některé použité zvuky jsou ze stránek www.freesound.org od autorů Robinhood76 a ivanbailey, kteří je zveřejnili pod licencemi CC Attribution 3.0 Unported [16] a AttributionNonCommercial 3.0 Unported [17]. Další zvuky jsou z kolekce Designer Sound FX od firmy Final Image Inc. (webové stránky videocopilot.net), zakoupené pod licencí Royalty free. 4.1.4 UpdateChecker a UpdateInterface Třída UpdateChecker spustí vlákno (daemon), které ověří dostupnost nové verze programu. Implementuje interface UpdateInterface: UpdateInterface je jednoduchý interface, obsahující metodu setCanceled(). Metoda Messenger.message_waiting() má jako jeden z parametrů tento interface. Tato metoda zobrazí dialog obsahující zprávu (další z parametrů) a tlačítko Zrušit. Při jeho stisku je dialog ihned zavřen a je zavolána metoda UpdateInterface.setCanceled(), která nastaví vláknu příznak značící, že uživatel operaci přerušil.
37
4.2. MENU APLIKACE
Třída UpdateChecker pak pravidelně kontroluje, zda nebyla operace přerušena a případně zastaví svou činnost. Interface UpdateInterface a metodu Messenger.message_waiting() tak mohou využívat i pluginy, pokud chtějí uživateli oznámit informaci o probíhající akci s možností jejího přerušení. Protože UpdateChecker spouští kontrolu nové verze v samostatném vlákně, Messenger, který zobrazí výsledek kontroly, musí být volán přes dříve zmíněnou konstrukci D i s p l a y . g e t D e f a u l t ( ) . asyncExec ( new Runnable ( ) { . . . } ) ; Pro přístup na server při samotné kontrole nové verze je použit objekt URLConnection, který na rozdíl od jednoduššího URL umí nastavit ConnectTimeout (timeout pokusu o připojení) a ReadTimeout (timeout čtení z URL). 4.1.5 CommentedProperties a Commons Veškeré přístupy k objektům CommentedProperties (konfigurační soubory aplikace) jsou prováděny přes metody ve třídě Commons, popsané v kapitole 3.2.5, které mají jako jeden ze vstupních parametrů výchozí hodnotu hledaného klíče. Díky tomu nedojde k chybě, pokud hledaný klíč v CommentedProperties není. V případě, že uživatel nechtěně změní konfiguraci programu a bude se chtít vrátit k výchozí konfiguraci, postačí smazat konfigurační soubor. Aplikace pak při dalším spuštění použije výchozí hodnoty nastavení a při ukončení vytvoří nový konfigurační soubor.
4.2
Menu aplikace
4.2.1 Mnemonic Všechny položky menu všech úrovní používají tzv. Mnemonic – klávesové zkratky, definované ampersandem(&) před písmenem v textu položky. Například na OS Windows lze položku menu vybrat zkratkou Alt+Mnemonic. Klávesové zkratky pro jednotlivé položky jsem vybíral podobné těm používaným v OS Windows (např. položku Nápověda – Zobrazit nápovědu lze stejně jako v programech MS Office vybrat zkratkou Alt+V, N). U podmenu Nápověda k pluginům, jehož položky jsou generovány při načítání pluginů, jsou Mnemonic přiřazeny automaticky – program přiřadí první písmeno názvu položky, které dosud nebylo použito (vynechává mezery). Nápověda pro konkrétní plugin tedy nebude mít přiřazený Mnemonic jen pokud by už všechna písmena z jeho názvu byla použita. Výměnou za toto malé riziko nemusí programátor nového pluginu položky v menu vytvářet ručně. 4.2.2 Akcelerátory Některé položky menu používají tzv. akcelerátory – klávesové zkratky, které na rozdíl od Mnemonics okamžitě vyberou příslušnou položku v menu (uživatel nemusí nejprve zadávat zkratku pro menu vyšší úrovně). Na většině platforem je tato zkratka zobrazena vedle názvu položky, se zarovnáním vpravo.
38
KAPITOLA 4. IMPLEMENTACE
Obrázek 4.1: Menu Nápověda s Mnemonics zvýrazněnými podtržítkem a akcelerátorem F1 pro zobrazení nápovědy 4.2.3 Menu Soubor Položka menu Soubor obsahuje pouze volbu Zavřít a to pod všemi platformami mimo MacOS X (SWT používá krycí jméno „cocoa“). Na této platformě není položka Soubor – Zavřít obvyklá. Na OS Windows je k položce Zavřít přiřazen i obvyklý akcelerátor Alt+F4. 4.2.4 Menu Nastavení 4.2.4.1
Uložit při ukončení
Při výběru této položky objekt MyMenu_Save změní v menu příslušnou ikonu a nastaví v CommentedProperties hodnotu saveOnExit. Je-li tato hodnota nastavena na true, při ukončení aplikace je zavolána metoda PluginInterface.save() všech načtených pluginů a při opětovném spuštění metoda PluginInterface.load() (podrobnosti viz kapitola 4.4.4.1). 4.2.4.2
Spouštět po startu systému
Tato položka je platná pouze na OS Windows. Na jiných platformách v menu tato volba zůstane, ale je nastavena jako disabled, uživatel ji tedy nemůže zvolit. Za název položky je v takovém případě přidán dodatek „Pouze OS Windows“. Další možností by bylo položku pod jinými OS vůbec nezobrazovat, domnívám se ale, že tento postup je lepší pro uživatele. Zamezí jeho zmatení pokud by program spouštěl na dvou různých platformách a na každé viděl jiný obsah menu. Spouštění po startu je zajištěno přidáním zástupce do složky Po spuštění. Pro přidání zástupce jsem zamýšlel použít knihovnu java.nio.file, uvedenou v Javě 1.7. Při jejím použití ale nelze zapisovat do systémových adresářů, včetně složky Po spuštění (pokus vyvolá výjimku AccessDeniedException). Použil jsem proto externí knihovnu JShortcut. JShortcut (domovská stránka http://alumnus.caltech.edu/ jimmc/jshortcut/) je šířen pod licencí GNU LGPL [21] a umožňuje jednoduše vytvořit zástupce na OS Windows.
39
4.2. MENU APLIKACE
Jeho nevýhodou je potřeba správné verze knihovny jshortcut.dll podle architektury, na které je spuštěna (jsou celkem 3 verze jshortcut.dll: x86, x86_64 a amd64). K dispozici je verze shortcut-0.4-oberzalek, která sama vybere správnou verzi .dll knihovny. Tato verze ale při každém spuštění opětovně extrahuje .dll soubor spuštěním C++ kódu, který správnou verzi pod dočasným názvem uloží do adresáře Temp systému Windows, kde pak dochází k jejich kupení. Upravil jsem proto soubor JShellLink knihovny JShortcut tak, aby při načítání .dll knihovny po spuštění aplikace zkontroloval architekturu a načetl správný soubor ze souborů jshortcut_x86.dll, jshortcut_ia64.dll a jshortcut_amd64.dll, které jsou pod těmito jmény distribuovány spolu s aplikací. 4.2.4.3
Minimalizovat do oznamovací oblasti
Obrázek 4.2: Menu nad ikonou v oznamovací oblasti OS Windows Tato položka menu je povolena pouze pokud platforma podporuje tzn. System tray (Display.getSystemTray()) – tedy např. OS Windows, Ubuntu GNOME. V opačném případě položku nelze vybrat a k textu je přidán dodatek „Není k dispozici na této platformě“. Tuto položku obsluhuje třída MyMenu_Minimize. Ta se stará o změnu chování při minimalizaci, o vytvoření ikony v systémové oblasti a jednoduchého kontextového menu zobrazovaného nad touto ikonou (obr. 4.2). Při prvním vybrání položky je vytvořen objekt TrayItem, který reprezentuje ikonu v oznamovací oblasti a ten je při dalším přepínání položky již jen skrýván a zase zobrazován (zároveň je přidáván / odebírán Listener pro skrytí Shellu aplikace z hlavní systémové lišty při minimalizaci). Kontextové menu zobrazované nad ikonou v oznamovací oblasti má tyto položky: • Zobrazit SA Notify – otevře okno aplikace • Tichý režim – zapne, nebo vypne Tichý režim • Skrývat při minimalizaci – je-li skryté (minimalizované), zobrazí okno aplikace a schová ikonu v oznamovací oblasti • Ukončit – ukončí aplikaci
40
KAPITOLA 4. IMPLEMENTACE
Při implementaci tohoto menu jsem narazil na chybu SWT: Celé menu je považováno za součást objektu TrayItem. Pokud má TrayItem přiřazený Listener pro kliknutí myší, který zobrazí menu, tak při kliknutí do libovolné části tohoto menu je opět zavolán Listener pro TrayItem a dojde k jeho opětovnému vykreslení menu na novém místě (vykresluje se vždy nad aktuální polohou kursoru myši). V metodě handleEvent(Event event) přiřazeného Listeneru je proto nutné provést kontrolu, zda menu již není zobrazené. 4.2.5 Menu Nastavení – Možnosti
Obrázek 4.3: Dialog Možnosti, záložka Obecné Vybrání položky Možnosti zobrazí dialog se všemi možnostmi nastavení aplikace. Pro položku je použit akcelerátor SWT.MOD1+P. SWT nahradí konstantu SWT.MOD1 kódem tlačítka platformy (na OS Windows Ctrl, na MacOS tlačítko Command Key). Text akcelerátoru je „CTRL+P“. Dle [26]2 SWT automaticky přetíží text akcelerátoru a např. na MacOS ho nahradí textem „Command + P“. Jednotlivé položky nastavení jsou seřazeny tématicky do záložek dialogu Možnosti. Při rozmýšlení pořadí záložek i pořadí položek v záložkách jsem se snažil o seřazení podle důležitosti – tak, aby položky, které by uživatel mohl nejspíše chtít změnit, byly co nejvýše. Položky jsou navíc v rámci záložek řazeny do Group (SWT objekt, který způsobí ohraničení svých potomků rámečkem). Na obrázku 4.3 jsou na vybrané kartě Obecné 3 Groupy: Aplikace, Jazyk a Vytvořit zástupce. 2
Kapitola 2.4: Accelerators
41
4.2. MENU APLIKACE
Při vytváření jednotlivých záložek jsem narazil na SWT bug 16438: Všechny obrázky v záhlaví záložek mají velikost nastavenou na velikost prvního obrázku. Je proto nutné používat pro všechny záložky stejně velké obrázky. 4.2.5.1
Preferences.java
Dialog Možnosti je reprezentován třídou Preferences.java. V ní probíhá inicializace dialogu i ukládání navolených hodnot do CommentedProperties. Této třídě jsou proto předány objekty MyMenu_Safe, MyMenu_Startup a MyMenu_Minimize – obsahují aktuální hodnoty příslušných nastavení (viz 4.2.4) a také umožňují okamžité promítnutí změn, které uživatel provede. 4.2.5.2
Tlačítko Uložit
Tlačítko Uložit je při otevření dialogu Možnosti nastaveno jako disabled. Je povoleno až ve chvíli, kdy uživatel provede změnu nastavení. Tlačítko je také nastaveno jako enabled, pokud uživatel zvolí vytvořit zástupce na záložce Obecné. To je teoreticky zbytečné, k vytvoření zástupce dojde ihned po stisknutí tlačítka. V opačném případě by se ale uživatel mohl domnívat, že k vytvoření zástupce nedošlo (nic se nezměnilo, protože tlačítko Uložit je stále nefunkční). 4.2.5.3
Záložka Obecné
Na tuto záložku jsem umístil i položky, které lze volit přímo z menu Nastavení. Tento přístup používají i jiné aplikace a považuji ho za přehlednější pro uživatele, který všechna nastavení najde na jednom místě. Do hlavního menu jsou pak pro urychlení umístěny pouze některé často používané volby. Výběr jazyka SWT widget Combo může obsahovat pouze text. Použil jsem proto třídu ImageCombo ze serveru www.richclient2.de, poskytovanou pod licencí Eclipse Public License (respektive její vylepšenou verzi ze stránek dev.eclipse.org, zveřejněnou pod stejnou licencí). Druhá verze umožňuje zobrazení obrázku i u právě vybrané položky (viz obr. 4.3), zatímco první jen u položek v seznamu, který se rozbalí kliknutím na Combo. Obě verze používají knihovny org.eclipse.ui.forms a org.eclipse.jface, bylo proto nutné importovat je do IDE NetBeans, ve kterém probíhal vývoj aplikace. Z knihovny org.eclipse.jface je ale třeba jen třída org.eclipse.jface.util.Geometry.class, v rámci úspory místa (celá knihovna má 1052 kB, samotná třída 62 kB) proto s aplikací z dané knihovny distribuuji pouze tuto třídu. ImageCombo pro výběr jazyka je generováno ze souboru available_locales.xml (načten pomocí knihovny Jsoup), kde jsou uloženy informace o dostupných jazykových verzích, včetně textu, který se zobrazí v ImageCombu a jména obrázku, který se zobrazí vedle textu.
42
KAPITOLA 4. IMPLEMENTACE
Jazykové verze Jednotlivé jazykové verze jsou uloženy v .properties souborech, které mají v názvu příponu identifikující jazykovou verzi, kterou obsahují. Veškeré texty zobrazované uživateli jsou nahrazeny konstrukcí ResourceBundle . getBundle ( " Bundle " ) . g e t S t r i n g ( [ S t r i n g −key ] ) Knihovna java.util.ResourceBundle načte soubor odpovídající právě nastavenému jazykovému prostředí (Locale) a v něm vyhledá text pro zadaný klíč. Při prvním spuštění se aplikace pokusí nastavit jazyk odpovídající jazykovému prostředí operačního systému. Pokud tento jazyk není k dispozici, použije výchozí nastavení (v současné distribuci je výchozím jazykem čeština).
Obrázek 4.4: Dialog Možnosti, záložka Zvuky a upozornění
4.2.5.4
Záložka Zvuky a upozornění
Vyskakovací upozornění Je-li zaškrtnuta položka Vyskakovací upozornění, třída Messenger při zobrazení všech vyskakovacích dialogů zavolá shell.forceActive() pro přenesení okna aplikace do popředí. Přesné chování se liší podle platformy. Např. na OS Windows 7 program pouze začne blikat na systémové liště – operační systém záměrně nedovoluje aplikacím přivlastňovat si programově focus. Toto chování lze obejít např. programovým stisknutím kláves Windows + D (zobrazí plochu) a následnou maximalizací okna, rozhodl jsem se ale respektovat standardní chování platformy. Aplikace má navíc možnost nastavit zvuková upozornění na vyskakovací dialogy, nemělo by tedy hrozit, že si uživatel nevšimne například upozornění na úspěšné hledání pluginu.
43
4.3. LOGOVÁNÍ CHYB APLIKACE
Zvuky Uživatel může přehrát vybrané zvuky stisknutím tlačítka Test zvuku. Listener přiřazený tomuto tlačítku se pokusí přehrát zvukový soubor vybraný v sousedícím widgetu Combo nebo Text. Tlačítko má dva stavy – Přehrát a Zastavit, měnící se podle toho, zda právě probíhá přehrávání zvuku. Ke každému přehrávanému klipu je přidán Listener pro odchycení konce přehrávání a nastavení tlačítka Test zvuku zpět do stavu Přehrát. Přehrávání zvuku probíhá v samostatném vlákně, změnu stavu tlačítka je proto nutné iniciovat pomocí konstrukce zmíněné v kapitole 3.1.2.2. Vlastní zvuk Uživatel může pomocí File dialogu vybrat vlastní zvuk – v současnosti jsou podporovány formáty .wav a .aif. 4.2.6 Menu Nápověda 4.2.6.1
Nápověda k pluginům
Položka Nápověda k pluginům obsahuje podmenu s odkazy na online nápovědu ke všem pluginům. Podmenu je generováno dynamicky při načítání pluginů ve třídě PluginLoader. Každý plugin má metody getHelpPage() a getHelpToolTip(), definované v interfacu PluginInterface, které vrátí webovou adresu nápovědy a tooltip zobrazovaný nad položkou v menu. 4.2.6.2
O programu
Položka O programu zobrazí dialog s informacemi o aplikaci. Na pozadí textu je vykreslen barevný přechod (gradient). Všechny texty v tomto dialogu jsou vytvořeny pomocí widgetu StyledText, který ovšem neumožňuje nastavit transparentní pozadí – lze nastavit pouze zděděné pozadí Shellu pomocí konstrukce s h e l l . setBackgroundMode (SWT.INHERIT_DEFAULT ) ; V takovém případě ale StyledText bere v úvahu pouze pozadí Shellu, definované obrázkem nebo jednolitou barvou. Gradient na pozadí Shellu, vykreslený pomocí GC (Graphics Context), se na pozadí StyledTextu nepromítne. Ve třídě ImageHandler.setGradientImg() je proto vytvořen nový obrázek o velikosti Shellu dialogu, na něj je nakreslen gradient a tento obrázek je nastaven na pozadí Shellu. Funkce pro vykreslení gradientu na obrázek byla převzata z [25].
4.3
Logování chyb aplikace
Aplikace využívá pro záznam chyb třídu java.util.logging.Logger. Nastavení je uloženo v souboru bin/log/logging.properties a je načteno před inicializací GUI aplikace, ve třídě MainClass.
44
KAPITOLA 4. IMPLEMENTACE
Soubor logging.properties obsahuje nastavení FileHandleru (java.util.logging.FileHandler), který ukládá logy do souborů. Jsou nastaveny 2 logovací soubory s maximální velikostí 100 000 bajtů. Jakmile jeden soubor dosáhne maximální velikosti, pokračuje ukládání chyb do druhého souboru (jeho minulý obsah je vymazán). Díky tomu nemůže dojít ke stavu kdy by logovací soubor neobsahoval žádné záznamy protože nedávno dosáhl maximální velikosti a byl přepsán. Do logovacích souborů jsou uloženy všechny odchycené výjimky. Při nastavení boolean proměnné MainClass.debug na true jsou zároveň všechny chyby vypsány do konzole. Zachycené výjimky, které je vhodné oznámit uživateli, (např. nefunkční internetové připojení) jsou zároveň předány tříde ErrorHandler. Celé tělo metody MainClass.main() je v bloku try{}, následovaným catch(Exception e){} pro zachycení všech neočekávaných chyb aplikace a jejich uložení do .log souborů.
4.4
Pluginy
Všechny pluginy jsou inicializovány ve třídě PluginLoader a uloženy do ArrayListu plugins v této třídě. Odtud se načítají při inicializaci plugin menu nebo menu Nápověda – Nápověda k pluginům. PluginLoader také při načítání pluginů zkontroluje, který plugin byl vybrán při posledním zavírání aplikace a opět ho nastaví jako vybraný (hodnota je uložena v CommentedProperties, klíč lastActivePlugin). 4.4.1 Soubory pluginů Soubory jednotlivých pluginů jsou ukládány v adresáři /plugins/<jmeno_pluginu>. Jedná se pouze o doporučení, cesta není nikde napevno daná. Každý plugin má svou cestu uloženou ve fieldu pluginFilesPath své nadtřídy Plugin – díky tomu může být ve třídě Plugin implementována metoda save() (viz 4.4.4.1). 4.4.2 Plugin menu Nejjednodušší řešení pro plugin menu by bylo použití widgetu TabFolder. Ten ale není dostatečně variabilní – menu by například nemohlo obsahovat tlačítka. Jednotlivé položky menu (záložky konkrétních pluginů) jsou proto tvořeny widgetem Composite. Obsah Composite pro konkrétní plugin je z velké části dán implementací pluginu: PluginInterface má definované gettery a settery pro fieldy, které jsou použity při vykreslování menu pluginu. Některé z těchto fieldů (ty, u kterých se předpokládá, že je programátor pluginu nebude chtít měnit) jsou inicializovány v abstraktní třídě Plugin. Jsou to: • borderActive – barva rámečku menu u aktivního pluginu • borderIdle – barva rámečku menu u neaktivního pluginu při najetí myší
45
4.4. PLUGINY
• whiteColor – druhá barva gradientu na pozadí menu i těla pluginu V případě potřeby programátor může definovat vlastní hodnoty přetížením getterů a setterů pro tyto fieldy. Další fieldy použité pro menu, inicializované ve třídách konkrétních pluginů, jsou: • name – název pluginu • namePaddingTop – odsazení názvu pluginu odshora • nameColor – barva názvu pluginu • font – jméno fontu použitého pro vykreslení názvu pluginu • fontHeight – velikost fontu • fontHeight2 – velikost fontu při najetí myší na menu neaktivního pluginu • labelColor – první barva gradientu na pozadí menu i těla pluginu • labelColor2 – první barva gradientu na pozadí menu pluginu při najetí myší na menu Programátor si tak může libovolně uzpůsobit vzhled menu svého pluginu při současném zachování automatického vytvoření menu, včetně změny fontu, rámečku a pozadí při najetí myší na menu. 4.4.2.1
Tlačítka v menu pluginu
Pro snadnější vytváření tlačítek v menu jsou v abstraktní třídě PluginActiveMenu implementované dvě metody addButton(). Jedna vytvoří tlačítko s minimální šířkou zadanou v parametru, druhá s rozpínavou šířkou. Tlačítka jsou přidávána do menu vedle sebe, v případě použití druhé metody bude tedy celková šířka tlačítek rovna šířce celého menu. Všechna tlačítka přidaná pomocí některé z metod addButton() používají vlastnost MigLayoutu sizegroup, nastavenou u všech na stejnou hodnotu. Všechna tedy mají šířku rovnou šířce největšího z nich. 4.4.2.2
Vybrání pluginu
Jednotlivá menu jsou potomkem Composite topMenu ve třídě PluginLoader Při kliknutí myší na menu pluginu je zahozen celý obsah Composite topMenu (nad všemi potomky topMenu je zavolán dispose() ), vybranému pluginu je nastaven příznak selected a celé menu se znovu vykreslí voláním metod PluginInterface.drawMenu(). Tento způsob je náročnější na systémové prostředky než pouhé překreslení menu původně vybraného a nově vybraného pluginu. Je ale přehlednější pro programátora pluginů – při pouhém překreslování menu by např. bylo nutné ukládat všechny listenery přiřazené k menu aktivního pluginu a ručně je odebírat při vybrání jiného pluginu.
46
KAPITOLA 4. IMPLEMENTACE
Stav
Aktivní
Neaktivní
0
3
1
1
2
1
2
2
4
3
3
4
4
2
4
Tabulka 4.2: Tabulka přechodů automatu pro přepínání menu
4.4.2.3
Přepínání menu
Menu pluginu je uloženo v proměnné PluginMenuState menu. Při změně menu z aktivního na neaktivní a naopak (tedy při přepnutí na jiný plugin) je původní menu uloženo do fieldu třídy Plugin: PluginMenuState inactive. Díky tomu se při příštím přepnutí stavu menu nemusí menu znovu inicializovat. Za tímto účelem jsem vytvořil jednoduchý automat pro 5 možných stavů menu (viz tabulka 4.23 ). Možné stavy menu (ve formátu <číslo stavu> ... /): • 0 ... — / — • 1 ... IdleMenu / — • 2 ... ActiveMenu / IdleMenu • 3 ... ActiveMenu / — • 4 ... IdleMenu / ActiveMenu
4.4.3 Tělo pluginu Tělo pluginu je vykreslováno na widget Composite pluginBody, který je fieldem abstraktní třídy Plugin. PluginInterface definuje metody Composite getPluginBody() a void setPluginBody(). Díky tomu, že všechny komponenty těla pluginu jsou potomkem Compositu pluginBody, je možné při přepnutí na jiný plugin celé tělo pluginu jednoduše skrýt skrytím Compositu pluginBody. Composite pluginBody všech pluginů má nastavenu vlastnost MigLayout hidemode=3. Díky tomu se pluginBody při skrytí pomocí pluginBody.setVisible(false) neúčastní layoutu. 3 Nejedná se o konečný automat, nelze proto minimalizovat jeho stavy, přestože stavy 2 a 4 mají totožné přechody.
47
4.4. PLUGINY
4.4.3.1
Líná inicializace
Pro těla pluginů je použita líná inicializace (lazy loading) – pluginBody je inicializován v abstraktní třídě Plugin, kde je nastaven jeho layout a zároveň příznak bodyInitialized na false. Až při přepnutí na daný plugin je zavolána metoda PluginInterface.setPluginBody(), která naplní pluginBody vnitřními komponenty pluginu. Volání setPluginBody() je provedeno ve třídě IdleMenu, kde je Listener pro kliknutí na menu pluginu. V těle metody Listeneru handleEvent() je provedena kontrola stavu inicializace pluginBody. Je-li bodyInitialized false, je zavolána metoda setPluginBody() a příznak je nastaven na true. 4.4.3.2
Přepínání pluginů
Při vybrání jiného pluginu je nejprve pluginBody vybraného pluginu nastaven na visible a až poté je případně volána metoda setPluginBody() (pokud tělo dosud nebylo inicializováno). Díky tomu lze při vytváření layoutu těla pluginu v metodě setPluginBody() používat metody vracející velikost komponent, která je nulová, má-li komponenta, nebo její předek (pluginBody), nastavený příznak visible na false. Tato vlastnost je využita např. u pluginu SA Notify – velikost obou zde použitých tabulek je nastavována podle velikosti dalších komponent – viz kapitola 5. Využití Compositu pluginBody jako předka všech komponent těla pluginu má i další výhody: • Lze jednoduše upravit layout rodičovské aplikace, aniž by byl narušen vnitřní layout pluginu. • Pluginy mohou používat libovolný layout manager. Vývojář pluginů není vázán na MigLayout, může nastavit libovolný vnitřní layout Compositu pluginBody. 4.4.4 Položky hledání Abstraktní třída PluginTableItem reprezentuje jednotlivé položky v tabulce probíhajících hledání. Např. u pluginu SA Notify pro hledání míst v autobusech Student Agency tedy každá instance podtřídy PluginTableItem reprezentuje jeden konkrétní autobus, v němž se plugin snaží najít volné místo. PluginTableItem implementuje interface Serializable. 4.4.4.1
Ukládání probíhajících hledání
Při vypnutí programu jsou uložena všechna probíhající hledání. Za tímto účelem definuje PluginInterface metodu s generickým návratovým typem: p u b l i c A r r a y L i s t getTableData ( ) ;
48
KAPITOLA 4. IMPLEMENTACE
Tato metoda vrátí ArrayList položek v tabulce hledání. PluginInterface dále definuje metody save() a load() pro uložení probíhajících hledání a pro jejich opětovné načtení při spuštění programu. Metoda save() je implementována v abstraktní třídě Plugin: V těle metody se zavolá getTableData() a všechny vrácené položky jsou uloženy do souboru, jehož jméno je ve fieldu třídy Plugin savedSearchesFile. Výchozí jméno souboru je „savedSearches.san“ a soubor je uložen do složky se soubory pluginu (v kapitole 4.4.1 zmíněný field pluginFilesPath). Programátor pluginu tak musí jen doplnit tělo metody getTableData() a metody load(), která načte uložené položky hledání, uložené metodou save() jako objekty PluginTableItem, a přetíží je na odpovídající podtyp (svou implementaci třídy PluginTableItem).
4.4.5 Kontrola volných míst Pozn: Nadpis této kapitoly není přesný, plugin může kontrolovat cokoli, ne pouze volná místa. Abstraktní třída CheckerThread extends Thread obsahuje metody a fieldy pro usnadnění kontroly volných míst. Obsahuje tyto fieldy:
• INTERVAL – interval, v jakém má probíhat kontrola volných míst na serveru • DEVIATION – odchylka od intervalu, ve kterém probíhá kontrola • INTERVAL_DEFAULT – výchozí interval, field má nastaven příznak final • DEVIATION_DEFAULT – výchozí odchylka, field má nastaven příznak final
Při opakovaném přistupování na servery třetích stran je nutné přístupy opakovat v dostatečně velkých intervalech, aby při masivnějším použití aplikace nedocházelo k přetížení těchto serverů. Zároveň je vhodné zvolený interval náhodně měnit, aby byla ztížena detekce robota ze strany provozovatelů serveru. K tomu slouží field DEVIATION. Metody implementované ve třídě CheckerThread slouží k nastavení intervalu kontroly, výpočtu náhodné odchylky, ukládání aktuálního času a zjišťování, kolik času zbývá do konce intervalu.
49
4.4. PLUGINY
4.4.5.1
Doporučená struktura metody Run
Doporučená struktura metody Run vlákna pluginu, které je podtřídou CheckerThread a slouží k periodické kontrole volných míst (uvedené metody jsou implementované ve třídě CheckerThread): @Override p u b l i c v o i d run ( ) { while ( ! freeSeatFound ){ setInterval (); setLastCheck ( ) ; { /∗ k o n t r o l a m í s t . . . ∗ / } s l e e p ( getSleepTime ( ) ) ; } }
setInterval() nastaví nový interval pro kontrolu míst. Interval je vypočten podle vzorce IN T ERV AL + (−DEV IAT ION + newRandom().nextInt(DEV IAT ION × 2 + 1)) Vypočtený interval je tedy náhodné číslo v intervalu INTERVAL ± DEVIATION. setLastCheck() uloží aktuální čas. getSleepTime() vrátí čas zbývající do konce intervalu, nebo 0, pokud kontrola volných míst trvala déle, než je nastavený interval. 4.4.6 Nastavení pluginů Pro snadnější vytváření dialogu Nastavení pro jednotlivé pluginy byla navrhnuta abstraktní třída PluginPreferences, která obsahuje metody pro vytvoření dialogu podobného tomu, který se otevře při volbě položky v menu aplikace Nastavení – Možnosti. PluginPreferences obsahuje metody pro vytvoření příslušného dialogu, záložkového menu (widget TabFolder), tlačítek Uložit a Storno včetně příslušných Listenerů apod. Děděním od této třídy lze tedy snadno vytvořit celý dialog, do kterého stačí doplnit obsah jednotlivých záložek. Tlačítko Uložit po stisknutí zavolá metodu save(). Na rozdíl od tlačítka Uložit u dialogu Možnosti – Nastavení jeho stisk kromě zavolání metody save() nezpůsobí zavření dialogu. Dialog je nutné zavřít v těle metody save() po uložení provedených změn v nastavení. V těle metody save() tak může být provedena kontrola správnosti nastavení (např. správně zadaný login a heslo pro přístup k serveru) a v případě chyby (prázdný login, nebo heslo) se dialog neuzavře a uživatel je na chybu upozorněn.
50
KAPITOLA 4. IMPLEMENTACE
4.4.7 Historie hledání Abstraktní třída PluginHistory je určená pro zjednodušení tvorby dialogu Historie hledání. Třída pracuje s instancemi abstraktní třídy PluginTableItem, která je nadtřídou položek hledání konkrétních pluginů. Díky tomu bylo možné implementovat některé metody pro práci s historií hledání: • addToHistory(PluginTableItem item) přidá položku do ArrayListu historyList, obsahujícího nové položky historie. • saveHistory(boolean saveHistory) uloží obsah listu historyList do souboru. Jméno souboru je uloženo ve fieldu historyFile, výchozí jméno je „history.san“. Je-li parametr saveHistory false, uloží prázdný soubor. Metoda tedy slouží i k vymazání historie. • createHistoryDialog() vytvoří jednoduchý dialog s názvem „[jméno pluginu]-historie:“, tabulkou s historií a zavíracím tlačítkem. Dále třída definuje tyto abstraktní metody: • ArrayList loadHistory() načte historii uloženou v souboru. • ArrayList getHistory() vrátí celou historii, tedy obsah listu historyList s připojenou historií, která byla uložena do souboru. 4.4.7.1
Tabulka historie
Tabulka s položkami historie je vytvořena v metodě createTable(String[ ] columns, String[ ] columnsTooltTip, int[ ] columnsWidth). Její parametry jsou názvy sloupců (columns), tooltipy názvů (columnsToolTip) a šířky jednotlivých sloupců (columnsWidth). Tedy pokud tělo pluginu obsahuje tabulku zobrazující probíhající hledání, jejíž parametry Název sloupce, Tooltip sloupce a Šířka sloupce jsou uloženy v polích, stačí tato pole předat metodě createTable(), která sama vytvoří tabulku historie. Dialog Historie používá, na rozdíl od zbytku aplikace, GridLayout. Díky tomu je možné použít kód z SWT Snippetu 77 pro automatický resize sloupců při změně velikosti okna dialogu – při použití MigLayoutu snippet ani po složitých úpravách nefungoval. Šířka sloupců tabulky je přepočítána při každé změně velikosti okna dialogu tak, aby sloupce vyplnily celou šířku dialogu a zároveň byl zachován jejich poměr stran vypočítaný ze vstupního parametru int[ ] columnsWidth.
51
4.4. PLUGINY
Obrázek 4.5: Třídní diagram pluginu 4.4.8 Přehled tříd pluginu Na obrázku 4.5 je třídní diagram typického pluginu. Každý plugin musí obsahovat tyto třídy (respektive implementovat jejich nadtřídy): • NewPlugin – třída reprezentující daný plugin, implementuje metody interfacu PluginInterface. • NewPluginMenu – obsahuje implementaci menu aktivního pluginu (tedy podobu menu, pokud je plugin právě vybrán). • NewPluginTableItem – reprezentuje jednu hledanou položku. Implementace této třídy (podtřídy PluginTableItem) povinná není, umožňuje ale využití metody safe() pro ukládání probíhajících hledání, implementované ve třídě Plugin, a také metod třídy PluginHistory pro práci s historií hledání.
52
KAPITOLA 4. IMPLEMENTACE
Dále plugin navržený na obrázku obsahuje třídy: • NewPluginPreferences – otevře dialog Nastavení pluginu. • NewPluginHistory – zobrazí historii hledání. • NewPluginChecker – samostatné vlákno, které provádí cyklické hledání volných míst. Programátor nového pluginu musí implementovat pouze třídy vyznačené na obrázku 4.5 zeleně. Všechny ostatní jsou již součástí aplikace.
4.5
Různé
Do této kapitoly řadím popisy implementace, které není vhodné zařadit do žádné z předchozích kapitol. 4.5.1 Texty v dialozích Texty ve vyskakovacích dialozích i v dialozích O programu, Nastavení – Možnosti apod. jsou vytvořeny pomocí widgetu StyledText. Pro daný účel je možné použít i widget Label, ten ale neumožňuje text jednoduše formátovat. V případech, kdy je text formátován, je proto použit StyledText. StyledText má nastaven setEditable(false) (uživatel text nemůže editovat) a výchozí kurzor widgetu je přetížen na kurzor šipka.
Obrázek 4.6: Widget StyledText – kurzor myši vedle textu „POZOR:“ I s přetíženým kurzorem myši ale uživatel na text může kliknout a v textu se objeví kurzor pro psaní (obr. 4.6). Text lze navíc i vybrat (obr. 4.7).
Obrázek 4.7: Widget StyledText – uživatel vybral text myší U většiny dialogů je proto StyledText nastaven jako disabled pomocí StyledText.setEnabled(false).
53
4.5. RŮZNÉ
Jako enabled je nastaven pouze v případech, kde může být žádoucí, aby uživatel mohl text vybrat a zkopírovat - např. dialog O Programu, položky Autor a Kontakt. Také všechny texty, jejichž součástí je URL odkaz, musí být enabled, jinak na odkaz nelze kliknout. Enabled v takovém případě musí být celý StyledText obsahující odkaz. Dalším řešením je vytvořit jeden StyledText obsahující text před odkazem, následovaný druhým StyledText, který obsahuje pouze URL odkaz. V takovém řešení ovšem nefunguje správně automatické zalamování řádek, zvolil jsem proto první možnost, kdy je enabled celý text s URL odkazem. 4.5.2 Jediná instance programu Ve třídě MainClass je uložen boolean oneInstance. Pokud je nastaven na true (výchozí nastavení pro distribuci programu), lze spustit pouze jedinou instanci aplikace. 4.5.2.1
Kontrola jediné instance programu
Před inicializací GUI je zkontrolována existence souboru „token“. Pokud soubor neexistuje, aplikace ho vytvoří a nastaví mu příznak deleteOnExit(). Při ukončení aplikace tak dojde k jeho smazání. Pokud soubor existuje, aplikace zobrazí dialog informující uživatele, že je již spuštěna jiná instance programu (obr. 4.8).
Obrázek 4.8: Dialog informující uživatele o již spuštěné instanci aplikace Uživatel může zvolit spuštění aplikace i přes existenci souboru „token“. Tím je ošetřen případ, kdy při předchozím spuštění aplikace nebyla řádně ukončena a soubor „token“ nebyl vymazán. Zvažoval jsem i další způsoby kontroly již běžící aplikace: • Uzamykání souboru „token“, např. pomocí java.nio.channels.FileLock. Soubor je v takovém případě automaticky odemčen při ukončení JVM. Stále ale může dojít k situaci, kdy z důvodu chyby soubor zůstane uzamčen (např. při pádu operačního systému). • Blokování portu – místo souboru je použit libovolný port. V tomto případě ale může docházet ke kolizi s jinou aplikací, používající daný port, případně k problémům s firewallem. Jednoduché řešení pomocí zjištění existence souboru se tedy jeví nejlepší.
54
KAPITOLA 4. IMPLEMENTACE
4.5.3 Přístupnost Velikost okna aplikace Velikost okna aplikace může uživatel libovolně měnit. Výchozí velikost okna je 800×640 px. Toto rozlišení považuji za dostatečné pro přehledný layout aplikace. Podle statistik uváděných na webu www.w3schools.com je zároveň dostatečně malé pro většinu dnes používaných rozlišení monitorů. Podle těchto statistik má pouze 1% uživatelů rozlišení 800 × 600 px, nebo menší. Obrázky a texty Aplikace obsahuje mnoho obrázků u položek menu, tlačítek, záložek dialogu Možnosti apod. Obrázky slouží k rychlejší orientaci uživatele, který je již s aplikací seznámen. Zároveň je ale u všech takto použitých obrázků uveden i text. Ten slouží nejen pro rychlejší orientaci nového uživatele, ale například i pro větší přístupnost aplikace zrakově postiženým, využívajícím čtecí zařízení. Použité ikony Snažil jsem se volit jednoduché a výrazné ikony, které by uživateli usnadnily zapamatování polohy i funkce různých prvků aplikace. V případě, že se mi nepodařilo nalézt obrázek, který by vystihoval příslušnou položku menu nebo tlačítko, snažil jsem se zvolit alespoň výraznou ikonu, kterou si uživatel s danou položkou může asociovat a nebude si ji plést s jinými ikonami. V aplikaci jsou použity následující ikony: • Ikony od Oxygen Team ze stránky http://iconarchive.com/artist/oxygen-icons.org.html, poskytované pod licencí GNU LGPL. • Ikony z kolekce Country flags (http://iconarchive.com). Jsou uvedné jako free for non-commercial use (autor neuvádí konkrétní licenci). • Ikony z kolekce Function Icon Set, zmíněné v kapitole 1.2. • Ikona kávy od uživatele Martin Berube (http://iconarchive.com) – autor uvádí License: Freeware, Commercial usage: Allowed. • Ikona autobusu z www.wpclipart.com. Podmínky použití viz pclipart.com/legal.html – v zásadě je povoleno libovolné použití. • Ikony autobusu a vlaku ze stránek www.clker.com/. Z podmínek (clker.com/disclaimer.html) opět vyplývá libovolné použití. • Obrázek autobusu ze stránek společnosti Student Agency, sekce Pro média (www.studentagency.cz/pro-media/).
55
4.5. RŮZNÉ
4.5.4 Název aplikace Zvolil jsem název aplikace SA Notify. Stejně jsem také pojmenoval plugin pro kontrolu míst ve spojích Student Agency. Tento název navazuje na původní program SA Notify, popsaný v kapitole 2.1.2. Od autora programu jsem získal písemné povolení k užití tohoto názvu. 4.5.5 Komentáře Veškeré zdrojové kódy jsou opatřeny podrobnými komentáři v anglickém jazyce. U rozsáhlejších komentářů jsem použil formátování pomocí HTML tagů pro zpřehlednění dokumentace vygenerované pomocí Javadoc.
56
Kapitola 5
Plugin SA Notify V této sekci se zabývám implementací pluginu SA Notify pro kontrolu volných míst ve spojích Student Agency.
5.1
Analýza a návrh řešení pluginu SA Notify
5.1.1 Specifikace požadavků Analýzou rešerše všech existujících řešení v kapitole 2.1 bylo stanoveno množství funkčních i nefunkčních požadavků. Plugin by měl umět: • sledovat více spojů najednou • aktualizovat seznam stanic • načítat dostupné spoje pro zvolené datum ze serveru • poradit si s více spoji v jeden čas • minimalizovat se do oznamovací oblasti (OS Windows) • automaticky zarezervovat volná místa • hledat pouze preferovaná sedadla • rozlišovat typy spojů (vlak/autobus, autobus Fun & Relax/Posilový spoj bez služeb apod.) • přehrát výrazné zvukové upozornění při úspěšném hledání • zobrazit historii hledání • měl by mít přehledné grafické rozhraní
5.1. ANALÝZA A NÁVRH ŘEŠENÍ PLUGINU SA NOTIFY
5.1.2 Případy užití Na základě specifikovaných požadavků byly stanoveny následující případy užití: 5.1.2.1
Hlavní okno pluginu
Obrázek 5.1: Případy užití hlavního okna pluginu SA Notify Na obrázku 5.1 jsou znázorněny hlavní případy užití pluginu: • Přidat spoj Uživatel zvolí tyto parametry požadovaného spojení: - Výchozí stanice - Cílová stanice - Datum - Konkrétní spoj v daný čas Po potvrzení výběru aplikace začne s kontrolováním volných míst. • Odebrat spoj Uživatel zruší hledání volného místa u vybraného spoje.
58
KAPITOLA 5. PLUGIN SA NOTIFY
• Zapnout automatickou rezervaci Uživatel zapne automatickou rezervaci. V případě nalezení volného místa aplikace volné místo ihned zarezervuje. Uživatel musí předem zadat přihlašovací údaje ke kreditové jízdence v Nastavení pluginu. • Zvolit preferovaná sedadla Uživatel může zvolit, jaká sedadla preferuje: - Jakákoli - U okna - V uličce - Vše kromě prostřední části zadní 5-sedačky (tzn. na sedadla na 5-sedačce u oken program upozorní)
Za nejdůležitější považuji volbu Vše kromě prostřední části zadní 5-sedačky. Cestovat na delší vzdálenosti na sedadle uprostřed 5-sedačky, kdy z obou stran sedí další cestující, je velmi nepohodlné. Sedadlo je relativně úzké a mezi sedadly nejsou opěrky pro ruce, je tedy silně narušen intimní prostor cestujícího. Tato sedadla bývají proto poslední obsazená a zároveň to jsou často první místa, která se uvolní v plném spoji. Je proto důležité, aby uživatel mohl vyloučit rezervaci právě těchto sedadel. • Zobrazit nastavení Aplikace zobrazí dialog s možnostmi nastavení pluginu. Více viz kapitola 5.1.2.2. • Zobrazit historii Aplikace zobrazí historii všech proběhlých hledání.
1
Každá položka obsahuje: - Výchozí stanice - Cílová stanice - Datum a čas spoje - Typ spoje (vlak, bus, posilový bus, bus Fun & Relax apod.) - Preferovaná sedadla - Stav automatické rezervace (zapnuta/vypnuta) - Rezervované sedadlo (bylo-li rezervováno) 5.1.2.2
Nastavení pluginu
• Nastavit výchozí preferovaná sedadla Uživatel zvolí výchozí preferovaná sedadla (jednotlivé možnosti viz předchozí kapitola). 1
Hledání je ukončeno a uloženo do historie okamžikem nalezení volného místa.
59
5.1. ANALÝZA A NÁVRH ŘEŠENÍ PLUGINU SA NOTIFY
Obrázek 5.2: Případy užití v dialogu Nastavení pluginu SA Notify • Aktualizovat seznam zastávek Aplikace podle webového rezervačního systému Student Agency aktualizuje seznam zastávek. Uživatel tuto možnost zvolí pouze pokud se lokální seznam aplikace stane neaktuálním. • Zvolit akci pro volné místo Uživatel zvolí výchozí akci při nalezení volného místa. Systém buď na volné místo pouze upozorní, nebo ho ihned zarezervuje. Ve druhém případě musí uživatel zadat přihlašovací údaje ke kreditové jízdence a vybrat cenový tarif. • Nastavit výchozí trasu Uživatel zvolí výchozí počáteční a cílovou stanici. Zvolené stanice budou přednastavené v dialozích pro výběr počáteční a cílové stanice. • Nezobrazovat stará hledání Systém z výpisu hledaných spojů odebere spoje, které jsou již neaktuální (proběhl čas odjezdu). Tyto spoje zůstanou uložené v historii, pokud je ukládání historie zapnuto.
60
KAPITOLA 5. PLUGIN SA NOTIFY
• Ukládat historii Zvolí-li uživatel tuto možnost, aplikace uloží všechna úspěšná hledání do historie.
5.1.3 Diagram aktivit - přidání spoje
Obrázek 5.3: Diagram aktivit - přidání spoje do vyhledávání Na obrázku 5.3 je diagram aktivit pro přidání spoje do vyhledávání. Uživatel postupně navolí všechny potřebné parametry, aplikace průběžně kontroluje jejich správnost.
61
5.1. ANALÝZA A NÁVRH ŘEŠENÍ PLUGINU SA NOTIFY
5.1.4 Analytický model V této kapitole je popsána struktura tříd pluginu. Základní struktura odpovídá té zobrazené na obrázku 4.5, byly ale přidány některé další, pomocné třídy. 5.1.4.1
Hledání volných míst
Obrázek 5.4: Třídy účastnící se hledání volných míst Na obrázku 5.4 jsou třídy přímo se účastnící hledání volných míst. SA je hlavní třída pluginu, implementující interface PluginInterface. SAChecker obsahuje metody pro parsování HTML stránek a vyhledání požadovaných dat na stránkách. SACheckerThread je vlákno provádějící kontrolu volných míst. SATableItemBean reprezentuje jednu položku hledání. SALineBean je fieldem SATableItemBean a reprezentuje konkrétní spoj. V SATableItemBean jsou uloženy obecné informace o hledání (nastavení automatické rezervace, preferovaných míst apod.), zatímco v SALineBean jsou uloženy detaily spoje čas odjezdu, typ spoje apod.
62
KAPITOLA 5. PLUGIN SA NOTIFY
DateTimeBean je pomocná třída, reprezentující datum a čas a obsahující pomocné metody např. pro zjištění dne v týdnu. Je navržena pro snadnou interakci s SWT widget DateTime. 5.1.4.2
Nastavení a historie
Obrázek 5.5: Nastavení pluginu a historie hledání
SAHistory obstarává zobrazování a ukládání historie pluginu. SAPreferences zobrazí dialog Nastavení. SAPreferencesBean pro jednodušší použití frameworku JFace Data Binding (viz 3.1.5) jsou veškeré hodnoty, které může uživatel změnit v dialogu Nastavení, reprezentovány třídou SAPreferencesBean. SAStationsUpdate slouží k aktualizování seznamu stanic. Díky implementaci interfacu UpdateInterface může uživatel aktualizaci v průběhu přerušit. 5.1.4.3
Chyby a obrázky
Plugin má vlastní třídu pro zobrazování chyb uživateli (SAErrorHandler, obrázek 5.6), která je potomkem třídy ErrorHandler.
63
5.2. IMPLEMENTACE PLUGINU
Obrázek 5.6: Třídy pro správu chyb a obrázků pluginu
Potomek třídy ImageHandler, třída SAImageHandler, slouží k inicializaci a disposingu obrázků pluginu.
5.1.5 Grafický návrh pluginu Grafický návrh je na obrázku 3.6. Při návrhu jsem se snažil, aby všechny důležité komponenty byly ihned přístupné - tedy aby uživatel nemusel například přepínat mezi dialogem pro výběr stanice a dialogem pro výběr data. V současné podobě lze velmi rychle navolit potřebné parametry a spoj přidat do vyhledávání.
5.2
Implementace pluginu
5.2.1 Rezervační systém Student Agency Společnost Student Agency svůj web pro rezervaci spojů v minulosti několikrát změnila. K poslední velké změně došlo v roce 2011. Poslední verze webu ve velké míře využívá Javascript a technologii Ajax. I pro pouhé zobrazení spojů pro zvolený den je tak nutné na server odeslat několik Ajax požadavků. Požadavky přitom obsahují i proměnné generované Javascriptem na straně klienta, které se dynamicky mění např. podle počtu přístupů na stránku. Ani získání požadovaných dat za použití HTML requestů simulujících Ajax požadavky tedy není zcela triviální. Stránky rezervačního systému bývaly v minulosti (v době, kdy byly funkční programy pro kontrolu volných míst zmíněné v kapitole 2.1) často přetížené. Lze proto předpokládat, že současná dynamická podoba webu je zčásti záměrem za účelem vyřazení robotů, které v minulosti na stránky přistupovaly.
64
KAPITOLA 5. PLUGIN SA NOTIFY
5.2.1.1
Přístup k rezervačnímu systému
Ať už je domněnka z předchozí kapitoly pravdivá či nikoli, jistě je vhodné přistupovat na stránky Student Agency tak, aby aplikace server zbytečně nezatěžovala a zároveň aby byla pokud možno snížena možnost detekce robota ze strany správce serveru. Všechny přístupy k rezervačnímu systému jsou proto prováděny pomocí knihovny Jsoup, která umožňuje snadné nastavení všech parametrů HTML requestu a jednoduchou práci s cookies. Hlavičky requestu jsou nastaveny tak, aby request simuloval prohlížeč Mozilla Firefox v. 10. 5.2.1.2
Ajax požadavky
Při odpovídání na některé Ajax požadavky vrací server HTML kód s escaped HTML tagy (vrací pouze kódy tagů). Před parsováním pomocí Jsoup je proto vrácený kód převeden na validní HTML pomocí org.apache.commons.lang.StringEscapeUtils - metoda unescape(). Apache Commons knihovny jsou poskytovány pod licencí Apache License, Version 2.0 [27]. 5.2.1.3
Interval kontroly volných míst
Pro interval kontroly volných míst, nastavovaný ve třídě SACheckerThread, jsem zvolil 70 s ± 20 s. Interval by měl být co největší kvůli zátěží serveru, při intervalu větším než 90 s už ale roste nebezpečí, že volné místo zarezervuje někdo jiný a to i bez použití této aplikace: I v době, kdy nebyl funkční žádný z programů pro kontrolu volných míst, byla uvolněná místa ve špičce během maximálně několika minut opět zarezervována. 5.2.2 Grafické rozhraní V této kapitole popisuji implementaci grafického rozhraní aplikace a provedené změny oproti grafickému návrhu. Na obrázku 5.10 je vidět srovnání GUI původního grafického návrhu a výsledné aplikace. 5.2.2.1
Výchozí a cílová stanice
Oproti návrhu bylo přidáno tlačítko pro prohození výchozí a cílové stanice cesty. Typický cestující cestuje pravidelně na jediné trase v obou směrech, potřeba tlačítka se proto objevila již při prvotním testování aplikace. 5.2.2.2
Spoje
V první verzi grafického rozhraní byl výpis dostupných spojů tvořen widgetem List, ten je i v GUI návrhu aplikace. List ale neumožňuje formátovat text (jednotlivé položky mohou být jedině typu String) nebo zobrazovat obrázky, provedl jsem proto refaktoring a použil widget Table (obr. 5.7).
65
5.2. IMPLEMENTACE PLUGINU
Výška tabulky: Tabulka se spoji musí mít pevně nastavenou výšku, jinak se automaticky nastaví tak, aby byly zobrazeny všechny položky. Výška by ale zároveň měla být pohyblivá, aby při změně velikosti okna došlo i ke změně velikosti tabulky. Byl proto přidán Shell Resize Listener, který při změně velikosti okna programu změní velikost tabulky podle nové velikosti Shellu. Vhodnější by bylo přiřadit tento Resize Listener ke Compositu pluginBody, jenž obsahuje všechny komponenty těla pluginu, nebo alespoň velikost tabulky podle pluginBody počítat. V současné implementaci by totiž při změně okolního layoutu (např. vypnutí stavového řádku) byla velikost tabulky nastavena špatně. Při přiřazení Listeneru k pluginBody byla ale odezva na změnu velikosti okna nepřirozeně pomalá a trhaná - pravděpodobně kvůli řetězení Listenerů, kdy změna velikosti okna inicializuje změnu velikosti Shellu, která teprve inicializuje změnu velikosti pluginBody pomocí MigLayoutu.
Obrázek 5.7: Tabulka pro výběr konkrétního spoje
Změny oproti návrhu: Jak je patrné z obrázku 5.7, tabulka se spoji obsahuje více informací, než v původním návrhu. Jsou to: • Datum - vždy nad skupinou spojů pro daný den, podobně jako na obr. 3.6 • Dopravní prostředek - ikonka vlaku, nebo autobusu • Čas odjezdu • Počet volných míst ve spoji • Typ spoje - ikonka znázorňující typ spoje Aplikace rozlišuje tyto typy spojů: - Fun & Relax
66
KAPITOLA 5. PLUGIN SA NOTIFY
- Posila Žlutý standard - Posila Economy
Názvy typů a ikony odpovídají těm, které jsou použity na webu Student Agency. 5.2.2.3
Tabulka hledání
Tabulka zobrazující probíhající hledání má nastaven parametr MigLayoutu endgroupy stejný jako tabulka spojů. Tím je zajištěno zarovnání jejich dolního okraje. Šířku sloupců tabulky může uživatel změnit. Nová šířka bude uložena při ukončení aplikace a použita při opětovném spuštění. Trasa: V tomto sloupci byla původně mezi výchozí a cílovou stanicí vykreslována šipka (→), tak jako je tomu v grafickém návrhu. Ovšem přestože byl použit symbol šipky z Microsoft WGL4 character set (microsoft.com/typography/otspec150/wgl4d.htm), symbol se nezobrazoval na operačním systému Windows XP. Použil jsem proto jako oddělovací znak pomlčku (–). CCombo s výběrem preferovaných míst: Widget CCombo je určen k nahrazení widgetu Combo v případech, kdy Combo nelze použít - např. jako prvek v řádku tabulky. Některé platformy nepodporují nastavení velikosti SWT.Combo (např. OS Windows), pro tabulku je proto nutné použít CCombo, které nevyužívá komponenty systému, ale je celé vykreslováno SWT knihovnou. Do tabulky je CCombo přidáno pomocí widgetu TableEditor (kapitola 5.2.2.4). CCombo má bohužel podobná omezení jako Combo: nelze nastavit obrázek (a neexistuje ImageCCombo alternativa ke třídě ImageCombo, zmíněné v kap. 4.2.5.3) ani zarovnání textu na střed, tak jako je to v GUI návrhu. Při implementaci CCombo s výběrem preferovaných míst jsem narazil na bug 358183 (viz bugs.eclipse.org): • Pokud je text delší než velikost Comba, je useknutý zleva, ne zprava. U bugu je popsaný i workaround: combo . a d d L i s t e n e r (SWT. R e s i z e , new L i s t e n e r ( ) { p u b l i c v o i d handleEvent ( f i n a l Event argEvent ) { combo . s e t T e x t ( combo . getText ( ) ) ; } });
Ten ovšem funguje jen pro widget Combo, ne pro CCombo. Vytvořil jsem proto pro CCombo Listener pro události SWT.Resize a SWT.Selection.
67
5.2. IMPLEMENTACE PLUGINU
Oba Listenery pomocí SWT třídy GC (Graphics Context) spočítají přesnou šířku CComba i nezkráceného textu vybrané položky. Následně je spočítána průměrná šířka jednoho znaku textu a je vytvořen substring o tomto počtu znaků. Vzhledem k výpočtu pomocí průměrné šířky znaku, který je rychlejší než přesný výpočet šířky jednotlivých znaků (postačí celkovou šířku textu vydělit počtem znaků) může i vytvořený substring být stále širší, než je šířka CComba. I šířka větší jen o několik pixelů přitom způsobí useknutí části prvního písmene textu. U vytvořeného substringu je proto v cyklu kontrolována šířka a snižován počet znaků, dokud není šířka textu opravdu menší, než šířka CComba. Na obrázku 5.8 je vidět CCombo se špatným zarovnáním i CCombo se zarovnáním opraveným popsanou metodou.
Obrázek 5.8: Vlevo je vidět CCombo chyba – špatné zarovnání textu, vpravo je chyba opravena
Změny v tabulce hledání oproti návrhu: • V tabulce je na začátku řádku zobrazena ikona dopravního prostředku. V současné verzi je zobrazována pouze ikona vlaku (viz obr. 5.9) - většina spojů jsou autobusy, ikona autobusu je navíc zobrazována v tabulce Spoje, není tedy třeba ji přidávat i do hlavní tabulky, kde by ubírala na přehlednosti. • Byl přidán sloupec Typ, který zobrazuje typ spoje (stejně jako v tabulce Spoje). Na obrázku 5.9 je vidět autobus typu Fun & Relax a autobus Posila Economy.
Obrázek 5.9: Tabulka hledání, probíhá hledání míst ve vlaku a autobusech Fun & Relax a Posila Economy
5.2.2.4
TableEditor
TableEditor je objekt, určený pro přidávání widgetů do tabulky. Může mu být přiřazen libovolný widget, který je umístěn nad zvolenou buňku tabulky. Jeho nevýhodou je nutnost ručního disposingu - při odebrání položky (řádku) tabulky není přiřazený TableEditor automaticky také odebrán.
68
KAPITOLA 5. PLUGIN SA NOTIFY
Widget TableEditor byl použit pro přidání CComba s výběrem preferovaných míst a tlačítka pro vypnutí/zapnutí automatické rezervace do tabulky hledání. 5.2.2.5
Barvy
Všechny barvy (SWT Color) používají línou inicializaci. Getter pro danou barvu zkontroluje, zda byla inicializována a případně zavolá setter. Ne všechny barvy jsou použity při každém spuštění programu, líná inicializace je tedy hospodárnější.
Obrázek 5.10: Srovnání GUI návrhu s GUI výsledné aplikace
69
5.2. IMPLEMENTACE PLUGINU
5.2.3 Implementace grafického rozhraní 5.2.3.1
Seznam stanic
Seznam stanic je uložen v XML souboru Stations.xml, uloženém ve složce pluginu (plugins/SA/). Soubor má následující strukturu: <s t a t i o n s −czech > <s t a t i o n > Český Krumlov , ANCK Načtení seznamu stanic ze souboru probíhá ve třídě SAPreferences. Pokud při načítání dojde k chybě (soubor je prázdný, nebo neexistuje), aplikace se pokusí připojit k serveru Student Agency a stáhnout nový seznam stanic. 5.2.3.2
Spoje
Dojde-li k chybě při načítání spojů, aplikace upozorní uživatele jen v případě, že seznam stanic není prázdný – to by znamenalo, že došlo k chybě při načítání seznamu stanic a uživatel byl již na chybu upozorněn. Třída SALineBean obsahuje informace o konkrétním spoji – především čas, typ spoje (vlak/bus), podtyp (Fun & Relax/Posila), počet volných míst, ID spoje apod. Třída obsahuje i field DateTimeBean date, reprezentující datum spoje. Díky tomu je jednoduše možné vypisovat v tabulce Spoje více dní: Server v případě, že na hledaný den je k dispozici méně než 10 spojů, vyhledá spoje i na den následující. Je tedy nutné kontrolovat, zda vypisovaný spoj je pro zvolený den, nebo den následující a případně vypsat nadpis pro další den (viz obr. 5.10). Existující řešení z kapitoly 2.1 vypisovala spoje pouze pro jeden den. To ale může být pro uživatele matoucí - například by nepoznal, zda pro zvolený den už nejedou žádné spoje, nebo došlo k neoznámené chybě při výpisu spojů. Možnost vypisování navíc umožňuje budoucí rozšíření pluginu o tlačítko Další spoje, které by vyhledalo spoje následující po posledním nalezeném. Detail spoje: Aplikace umí ze serveru získat i podrobnější údaje o spoji, které lze na webu rezervačního systému získat stisknutím tlačítka Detail spoje. Pro tento účel existuje metoda SAChecker.setLineDetails(SALineBean bean), která doplní dosud prázdné fieldy objektu SALineBean: • Čas příjezdu
70
KAPITOLA 5. PLUGIN SA NOTIFY
• Nástupiště • Spojení (Stanice, ze které spoj vyjíždí a stanice, ve které končí.) • Cena • Sedadlo
V první verzi pluginu byly detaily parsovány automaticky pro každý nalezený spoj. Server má ale velmi pomalou odezvu, získání detailů pro každý spoj trvá přibližně 1 vteřinu. Zpoždění při vypisování spojů by tedy bylo neúnosně velké. V současné verzi proto plugin detaily nezjišťuje. V budoucnu lze ale jednoduše implementovat např. rozšíření o tlačítko Zobrazit detail spoje. Aktualizace tabulky Spoje proběhne automaticky vždy, když uživatel zvolí jiné datum nebo změní výběr stanice v Combo boxech Z nebo Do. Okamžitou aktualizaci považuji za přívětivější a rychlejší než potvrzování výběru tlačítkem, jako je tomu na webu Student Agency. 5.2.3.3
Přidání spoje do vyhledávání
Všechna nastavení provedená v dialozích pro výběr spoje (stanice, datum, čas) se ukládají do SATableItemBean bean, která je fieldem třídy SA. Při stisknutí tlačítka Přidat spoj aplikace zkontroluje: • Platnost zvolených stanic – Combo boxy s výběrem stanic obsahují i nadpisy zemí (Česko, Slovensko). Ty mají hodnotu stanice (VALUE) nastavenou na -1. • Aktuálnost spoje - nelze přidat spoj, který již proběhl. Tato kontrola je spíše dvojím jištěním - při vyhledávání spojů server vrátí pouze spoje budoucí. Teoreticky by ale mohlo dojít k situaci, kdy plugin vyhledá spoje na dnešní den a uživatel výběr provede se zpožděním. Může se tedy stát, že vybere spoj, který mezitím odjel. • Platnost zvoleného času – analogicky se zvolenými stanicemi, i tabulka se spoji obsahuje nadpisy. • Zda vybraný spoj již není v tabulce hledání. Proběhne-li kontrola v pořádku, SATableItemBean bean je zkopírováno pomocí tzv. Copy konstruktoru - konstruktor třídy přijímá jako parametr instanci téže třídy a zkopíruje všechny fieldy. Je-li kopírovaný field objektem (např. SALineBean), musí být také zkopírován pomocí Copy konstruktoru. Vytvořená kopie je přidána do ArrayListu<SATableItemBean> tableData, který reprezentuje obsah tabulky hledání. Poté je nastaven příznak tableDataModified pro vlákno procházející SACheckerThread (viz 5.2.4).
71
5.2. IMPLEMENTACE PLUGINU
Diagram aktivit z obrázku 5.3 byl mírně pozměněn: Výchozí stanici, cílovou stanici a datum lze vybírat paralelně, jejich ověření probíhá až při přidávání spoje do vyhledávání. 5.2.3.4
Tabulka hledání
Tabulka hledání je aktualizována na konci každého cyklu ve vlákně SACheckerThread, kontrolujícího počet volných míst u všech položek tabulky. Díky tomu uživatel vidí vždy aktuální počet volných míst ve sledovaných spojích. Při každém naplňování tabulky (tedy i při aktualizaci volných míst) je zároveň provedena kontrola aktuálnosti všech hledaných spojů. Spoje, které nejsou aktuální (uplynul čas odjezdu), jsou z tabulky vyřazeny, nebo označeny šedou barvou řádku, pokud uživatel zvolil možnost Zobrazovat stará hledání v nastavení pluginu. Řazení položek: Kliknutím na záhlaví libovolného sloupce lze řádky tabulky podle tohoto sloupce seřadit. Opakovaným kliknutím lze měnit směr řazení. Řazení je založeno na SWT Snippetu 192. SATableItemBean obsahuje za účelem řazení metody getStringColumn(int index) a getLongColumn(int index): Listener přiřazený ke všem sloupcům zavolá metodu Collections.sort(ArrayList<SATableItemBean> data, Comparator comparator). Použitý Comparator má přetíženou metodu compare(): Metoda podle indexu (čísla sloupce, na jehož záhlaví uživatel kliknul a který je tedy řazen) zavolá buď SATableItemBean.getStringColumn(index), nebo SATableItemBean.getLongColumn(index). Tyto metody vrací String, nebo Long reprezentaci hodnoty vybraného sloupce, která je dále porovnávána ve zbytku metody compare(). Tento způsob je výhodný vzhledem k různé reprezentaci dat v jednotlivých sloupcích. Například sloupec Datum obsahuje datum ve tvaru Ww DD.MM. (Ww je zkratka dne v týdnu). Při řazení podle tohoto sloupce je proto v compare() volána metoda SATableItemBean.getLongColumn(1), která vrátí počet milisekund reprezentující dané datum (zaokrouhlené na minuty). 5.2.4 Kontrola volných míst Kontrola volných míst probíhá ve vlákně SACheckerThread ve while cyklu metody Run() (dále jen While cyklus), jehož struktura odpovídá té popsané v kapitole 4.4.5.1. Cyklus běží, dokud jsou v tabulce hledání platné spoje. Protože kontrola běží v samostatném vlákně, bylo nutné ošetřit chyby ConcurrentModificationException, způsobené paralelním modifikováním Listu tableData, obsahujícího položky tabulky hledání – obsah tabulky totiž může být kdykoli změněn uživatelem. Na začátku cyklu je proto obsah tableData zkopírován do lokálního ArrayListu a kontrola probíhá nad lokálním Listem.
72
KAPITOLA 5. PLUGIN SA NOTIFY
5.2.4.1
Kopírování položek
V průběhu procházení Listu tableData za účelem zkopírování vždy před přístupem k další položce proběhne kontrola, zda nedošlo ke změně obsahu Listu. K tomu slouží příznak tableDataModified, nastavovaný při změně obsahu tabulky hledání uživatelem. Jestliže aplikace zjistí, že byla provedena modifikace Listu, zahodí dosud zkopírovaný obsah a začne kopírování od začátku. Tímto způsobem je zajištěno ošetření proti chybám ConcurrentModificationException a zároveň ArrayList tableData nemusí být pro účely kopírování uzamčen pro modifikaci uživatelem. 5.2.4.2
Volná místa spoje
Ve While cyklu jsou kontrolována volná místa postupně pro všechny položky tabulky hledání. V seznamu spojů pro konkrétní den, který vrací server, je HTML tag
, obsahující počet volných míst, identifikován unikátním atributem ID. Tento atribut je ale generován dynamicky a pokud dojde ke změně v seznamu spojů (například se jedná o dnešní den a oproti předchozí kontrole je vráceno o jeden spoj méně, neboť čas odjezdu prvního spoje uplynul), ID se změní. Je proto nutné nalézt nejprve
obsahující atribut row:id, jehož hodnotou je unikátní, neměnící se ID pro konkrétní spoj a následně procházením DOM struktury pomocí Jsoup nalézt