V minulΘ Φßsti naÜeho volnΘho serißlu o programovßnφ v API Cocoa jsme dokonΦili p°ehledn² popis InterfaceBuilderu a slφbili si trochu slo₧it∞jÜφ p°φklad. Dnes si jej tedy ukß₧eme, spolu s vyu₧itφm specißlnφch "pseudoobjekt∙", jako je First Responder nebo File's Owner: p°ipravφme velmi jednoduch² editor, kter² dokß₧e vyu₧φvat dopl≥ovßnφ podle slovnφku.
Editor
Nejprve p°ipravφme samotn² editor. Dφky flexibilit∞ a sφle standardnφch knihovnφch slu₧eb Cocoa to je prßce na pßr minut a na n∞kolik programov²ch °ßdk∙. My se zde samoz°ejm∞ podrobn∞ soust°edφme na prßci v InterfaceBuilderu a ostatnφ Φinnosti popφÜeme jen natolik, aby bylo jasnΘ, co se vlastn∞ d∞je.
Nejprve spustφme ProjectBuilder a vy₧ßdßme si vytvo°enφ novΘho projektu typu Cocoa Document-based Application. ProjectBuilder nßm uÜet°φ spoustu prßce tφm, ₧e automaticky vytvo°φ zßkladnφ kostru aplikace a umφstφ do nφ pot°ebnΘ soubory - vΦetn∞ dvou NIB: MainMenu.nib obsahuje hlavnφ menu a je centrßlnφm NIB celΘ aplikace; MyDocument.nib obsahuje GUI vztahujφcφ se k dokumentu, p°edevÜφm tedy okno, v n∞m₧ bude dokument zobrazovßn.
Malß odboΦka: ProΦ jsou NIB navr₧eny prßv∞ takto? To je jednoduchΘ. Hlavnφ NIB se zavede hned p°i spuÜt∞nφ aplikace; dokumentov² NIB se naproti tomu zavede v₧dy, kdy₧ otvφrßme nov² dokument. Tak vlastn∞ dostaneme ·pln∞ "zadarmo" to, ₧e pro ka₧d² dokument se vytvo°φ jeho vlastnφ sada GUI objekt∙.
File's Owner pro dokumentov² NIB je v₧dy instance specißlnφ t°φdy, kterß reprezentuje controller dokumentu (p°ipome≥me strukturu MVC, o kterΘ jsme se bavili v p°edminulΘm dφlu - model reprezentuje data, view jsou prvky u₧ivatelskΘho rozhranφ ulo₧enΘ v NIB, a controller je logika aplikace, je₧ svazuje model a view dohromady). Tuto t°φdu pro nßs ProjectBuilder u₧ takΘ p°ipravil a nazval ji MyDocument - samoz°ejm∞ ji m∙₧eme podle libosti p°ejmenovat. Proto₧e budeme v dokumentu pot°ebovat textov² editor, p°idßme do interface t°φdy nov² outlet text - to vidφme na obr. 1, spolu s oknem ProjectBuilderu. PovÜimn∞te si v levΘ Φßsti okna seznamu soubor∙, je₧ jsou souΦßstφ projektu: vedle zdrojov²ch text∙ t°φdy MyDocument zde vidφme oba NIB a pßr dalÜφch pomocn²ch soubor∙. Jen pro zajφmavost - nap°φklad soubor Credits.rtf usnad≥uje tvorbu standardnφho panelu "o aplikaci": souΦßstφ menu v MainMenu.nib u₧ je odpovφdajφcφ p°φkaz, nastaven² tak, ₧e otev°e standardnφ panel, v n∞m₧ se zobrazφ ikona aplikace, jejφ jmΘno a verze, a v samostatnΘm poli obsah formßtovanΘho textovΘho souboru Credits.rtf. ÄßdnΘ programovßnφ nenφ zapot°ebφ...
Zp∞t k naÜφ prßci. Po p°idßnφ outletu text otev°eme poklepßnφm MyDocument.nib a v InterfaceBuilderu do jeho okna p°etßhneme z palety p°ipraven² objekt t°φdy NSTextView (obr. 2). Za povÜimnutφ stojφ modrΘ Φßry, jimi₧ InterfaceBuilder automaticky vyznaΦφ ideßlnφ polohu objektu v okn∞, aby vzhled aplikace odpovφdal standard∙m Mac OS X. Samoz°ejm∞ ₧e m∙₧eme tuto "nßpov∞du" ignorovat a objekt umφstit kamkoli, ale za normßlnφch okolnostφ je to v²znamnß pomoc.
Z okna ProjectBuilderu vhodφme soubor MyDocument.h do okna InterfaceBuilderu (aby v∞d∞l o novΘm outletu). Pak textov² objekt roztßhneme p°es celΘ okno, urΦφme jeho atributy pomocφ inspektoru a technikou znßmou u₧ z minulΘho dφlu jej navß₧eme na outlet text ve File's Owneru (obr. 3). Jak u₧ vφme, vlastnφkem dokumentovΘho NIB je v₧dy instance t°φdy, je₧ je controller danΘho typu dokument∙, v naÜem p°φpad∞ tedy prßv∞ MyDocument.
Pro dokonΦenφ editoru u₧ staΦφ p°idat jen Üest programov²ch °ßdk∙, v nich₧ urΦφme zp∙sob, jak²m se budou dokumenty uklßdat na disk a naΦφtat z disku. Prvnφm je pomocnß prom∞nnß loaded typu NSString, kterou p°idßme mezi prom∞nnΘ instance MyDocument. Zb²vajφcφch p∞t doplnφme do p°ipraven²ch metod ve zdrojovΘm souboru MyDocument.m (p°idanΘ °ßdky jsou oznaΦeny tuΦn∞):
if (loaded) [text setString:[loaded autorelease]];
}
Podrobnostem t°φdy NSDocument, ji₧ zde vlastn∞ vyu₧φvßme (podφvejte se na obr. 1, kde je vid∞t, ₧e MyDocument je jejφ podt°φda), se budeme v∞novat pozd∞ji; zatφm proto jen telegraficky:
* Metoda dataReprezentationOfType: p°ipravφ data pro ulo₧enφ na disk; my prost∞ °ekneme naÜemu textovΘmu editoru (outlet text), aby nßm dal sv∙j obsah (zprßva string). Od n∞j si pak vy₧ßdßme reprezentaci Unicode. To je vÜe, co je zapot°ebφ pro uklßdßnφ.
* Prvnφm krokem naΦφtßnφ souboru z disku je zpracovßnφ naΦten²ch dat. O to se starß metoda loadDataReprezentation:ofType: - v nφ nejprve data zkusφme interpretovat jako Unicode a p°evΘst na textov² °et∞zec (initWithData:encoding:), kter² doΦasn∞ ulo₧φme do prom∞nnΘ loaded. Pokud se to poda°φ (loaded!=nil), vrßtφ metoda YES, jφm₧ indikuje, ₧e naΦtenφ dat se poda°ilo.
* Je-li tomu tak, zavede se (automaticky) dokumentov² NIB (p°itom se mj. korektn∞ inicializuje outlet text) a zavolß se poslednφ metoda windowControllerDidLoadNib:. V nφ u₧ jen vlo₧φme text z prom∞nnΘ loaded do textovΘho editoru (setString:) a uvolnφme jej (autorelease).
Tφm jsme dokonΦili prakticky kompletnφ textov² editor (jedinß ze zßkladnφch slu₧eb, je₧ zatφm nefunguje, je vyhledßvßnφ; jinak dφky skv∞le navr₧enΘmu objektovΘmu systΘmu Cocoa funguje vÜe, od prßce se soubory a se schrßnkou p°es korektor pravopisu a₧ po Services).
Slovnφk
Nynφ k editoru p°idßme slovnφk pro automatickΘ dopl≥ovßnφ. Nejprve si p°ipravφme model slovnφku, tj. implementaci jeho slu₧eb - p°idßme do projektu soubory Dict.h a Dict.m, a zaΦneme tφm, ₧e v souboru Dict.h navrhneme interface, tj. seznam slu₧eb. Vzhledem k tomu, ₧e jde o velmi jednoduchou ·lohu, bude i rozhranφ trivißlnφ - ani nebudeme p°ipravovat instanci, sestavφme rozhranφ jen z metod t°φdy:
@interface Dict : NSObject
+(NSString*)wordForPrefix:(NSString*)prefix; // najφt slovo pro dan² prefix
+(void)addWord:(NSString*)word; // p°idat do slovnφku novΘ slovo
+(void)removeWord:(NSString*)word; // odebrat ze slovnφku slovo
@end
KonkrΘtnφ implementace nenφ pro nßÜ p°φklad d∙le₧itß; vzhledem k tomu, ₧e vΦetn∞ uklßdßnφ slovnφku do aplikaΦnφch p°edvoleb nezabere ani t°icet programov²ch °ßdk∙, umφstili jsme ji pro p°φpadnΘ zßjemce do vlo₧enΘho boxu. Klidn∞ jej vÜak m∙₧ete ignorovat - z hlediska toho, Φφm se v tomto Φlßnku zab²vßme, nenφ nikterak d∙le₧it².
Vazba slovnφku na GUI
AΦkoli slovnφk jako takov² mßme hotov², je t°eba jeÜt∞ napsat pßr °ßdk∙, kterΘ zajistφ jeho vazbu na grafickΘ rozhranφ. Asi budeme chtφt, aby se automaticky hledalo slovo k prefixu, kter² jsme prßv∞ napsali - je tedy zapot°ebφ se v objektu text podφvat, kde je kurzor, najφt (ΦßsteΦnΘ) slovo vlevo od n∞j a to pou₧φt jako hledan² prefix... Krom∞ toho musφme do menu p°idat p°φkazy Complete a Add Word a n∞jak²m zp∙sobem je p°ipojit k odpovφdajφcφm slu₧bßm.
Na to prßv∞ vyu₧ijeme First Responder. Otev°eme hlavnφ NIB, pomocφ inspektoru t°φd do First Responderu p°idßme slu₧by dictWordAdd: a dictWordComplete: a do menu - standardnφm "natahßnφm" z palety - p°idßme novΘ podmenu s pat°iΦn²mi p°φkazy. Pak u₧ jen "nadrßtujeme" polo₧ky menu na odpovφdajφcφ slu₧by First Responderu; spojenφ polo₧ky Add Word se slu₧bou dictWordAdd: ilustruje obr. 4.
Uv∞domme si, co jsme prßv∞ za°φdili: kdykoli u₧ivatel vybere polo₧ku menu Add Word, najde systΘm view (ve smyslu MVC) objekt, ve kterΘm je prßv∞ kurzor (tj. First Responder), a pokusφ se mu poslat zprßvu dictWordAdd:. Pokud to nenφ mo₧nΘ (proto₧e objekt, ve kterΘm je kurzor, takovΘ zprßv∞ nerozumφ), zkouÜφ systΘm postupn∞ dalÜφ objekty - p°edevÜφm jeho controller. V naÜem p°φpad∞ se tedy systΘm pokusφ nejprve p°edat zprßvu objektu NSTextView, kter² jsme umφstili do okna v InterfaceBuilderu; pokud se to nepoda°φ, p°edß zprßvu odpovφdajφcφmu objektu MyDocument.
Pro dokonΦenφ editoru tedy staΦφ implementovat metody dictWordAdd: a dictWordComplete: ve t°φd∞ MyDocument. Mo₧nostφ je samoz°ejm∞ °ada; my jsme zvolili nekomplikovanou implementaci, vyu₧φvajφcφ pomocnΘ metody _currentWord, kterß nalezne vhodnΘ slovo:
Metoda je pom∞rn∞ jednoduchß. Nejprve zjistφme, je-li ji₧ n∞jak² text oznaΦen. Nenφ-li tomu tak (selection.length==0), pokusφme se najφt slovo vlevo od kurzoru: vy₧ßdßme si od textovΘho systΘmu Cocoa (textStorage) vyhledßnφ slova, kterΘ by bylo oznaΦeno, kdybychom myÜφ poklepali vlevo vedle kurzoru (doubleClickAtIndex: - pro "vlevo" je tam selection.location-). NalezenΘ slovo oznaΦφme (setSelectedRange:). Pak ji₧ jen vrßtφme oznaΦenΘ slovo, je-li jakΘ - a¥ ji₧ jsme jej oznaΦili programov∞, nebo bylo oznaΦeno u₧ivatelem (substringWithRange:).
Implementace vlastnφch akcφ dictWordAdd: a dictWordComplete: je pak u₧ trivißlnφ a m∞la by b²t srozumitelnß i bez bli₧Üφho vysv∞tlenφ:
-(void)dictWordComplete:sender {
NSString *s=[self _currentWord];
if (s && (s=[Dict wordForPrefix:s])) // mßm prefix && slovo ze slovnφku
To je vÜechno - m∙₧eme aplikaci "zbuildovat" a vyzkouÜet. Editor normßln∞ funguje; libovolnΘ slovo m∙₧eme p°idat do slovnφku tak, ₧e jej oznaΦφme a vyvolßme p°φkaz "Add Word" z menu. Pak staΦφ jen napsat prefix n∞kterΘho ze slov ze slovnφku, vyvolat p°φkaz Complete a slovo se korektn∞ doplnφ.
Dφky tomu, ₧e jsme p°φkazy v hlavnφm NIB navßzali na First Responder, funguje vÜe korektn∞ v kterΘmkoli dokumentovΘm okn∞, v n∞m₧ je prßv∞ kurzor, a¥ jich je najednou otev°eno sebevφc - prost°ednictvφm First Responderu se zprßvy dictWordAdd: a dictWordComplete: poÜlou v₧dy pat°iΦnΘ instanci t°φdy MyDocument.
First Responder a Cocoa toho um∞jφ jeÜt∞ vφc!
Dopl≥ovßnφ nßm tedy funguje korektn∞ ve vÜech dokumentech. Ale co jinde? Dejme tomu, ₧e n∞kdo otev°e standardnφ panel korektoru pravopisu a pokusφ se pou₧φt dopl≥ovßnφ podle slovnφku v jeho textovΘm poli (obr. 5) - mßme problΘm, tam to nefunguje! TotΘ₧ platφ pro textovß pole panel∙ pro otvφrßnφ a uklßdßnφ soubor∙. Podobn² problΘm takΘ nastane, pokud aplikaci rozÜφ°φme o dalÜφ panely, t°eba o panel pro vyhledßvßnφ nebo panel p°edvoleb...
Je v∙bec mo₧nΘ za°φdit, aby slu₧by Add Word a Complete fungovaly kdekoli, kde zrovna vklßdßme text (i kdyby to nßhodou bylo textovΘ pole, kterΘ je souΦßstφ standardnφho systΘmovΘho k≤du, jako nap°. panel pro otvφrßnφ soubor∙ nebo panel korektoru)? Inu, v klasick²ch API postaven²ch na statick²ch jazycφch jako C++ nebo Java by to asi byl ne°eÜiteln² problΘm. V Cocoa je to ale dφky dynamickΘ podstat∞ Objective C a dφky skv∞lΘmu designu First Responderu snadnΘ - dokß₧eme to za dv∞ minutky!
P°edevÜφm si znovu uv∞domφme, co jsme si °φkali v minulΘm odstavci: "... kdykoli u₧ivatel vybere polo₧ku menu Add Word, najde systΘm view (ve smyslu MVC) objekt, ve kterΘm je prßv∞ kurzor (tj. First Responder), a pokusφ se mu poslat zprßvu dictWordAdd:..." Pokud tedy dokß₧eme za°φdit, aby standardnφ systΘmov² view objekt NSTextView - kdekoli je pou₧it - rozum∞l zprßvßm dictWordAdd: a dictWordComplete:, mßme vyhrßno.
To ale v Objective C nenφ nic t∞₧kΘho - vzpome≥me si na t°etφ dφl naÜeho serißlu, ve kterΘm jsme si ukazovali mo₧nosti jazyka Objective C. Tehdy jsme si takΘ ukßzali kategorie, kterΘ umo₧≥ujφ p°idßvat dalÜφ, novΘ slu₧by k ji₧ existujφcφm t°φdßm. ┌pln∞ proto staΦφ p°ipravit kategorii t°φdy NSTextView, pomocφ p°φkaz∙ Cut a Paste do nφ p°enΘst implementaci metod _currentWord, dictWordAdd: a dictWordComplete: ze t°φdy MyDocument a p°φkazem "Find" nahradit odkazy na outlet text odkazem na sebe sama (self):
To je celΘ - staΦφ aplikaci znovu "zbuildovat" a vÜe funguje: dopl≥ovat m∙₧eme kdekoli, v kterΘmkoli panelu, v jakΘmkoli textovΘm poli. Samoz°ejm∞ ₧e stßle funguje dopl≥ovßnφ i v dokumentech - i ty vyu₧φvajφ standardnφ t°φdy NSTextView.
To je pro dneÜek vÜe...
... p°φÜt∞ se pustφme do p°ehledu t°φd Application Kitu.