home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 March / Chip_2002-03_cd1.bin / obsahy / Chip_txt / txt / 139-141.txt < prev    next >
Text File  |  2002-02-03  |  14KB  |  131 lines

  1.  
  2. Programovßnφ v prost°edφ Cocoa (15)
  3. GUI a InterfaceBuilder - p°φklad
  4. 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.
  5.  
  6. Editor
  7. 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.
  8. 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.
  9. 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∙.
  10. 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φ...
  11. 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.
  12. 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.
  13. 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∞):
  14. - (NSData *)dataReprezentationOfType:(NSString *)aType {
  15.   return [[text string] dataUsingEncoding:NSUnicodeStringEncoding];
  16. }
  17. - (BOOL)loadDataReprezentation:(NSData *)data ofType:(NSString *)aType {
  18.   loaded=[[NSString alloc] initWithData:data encoding:NSUnicodeStringEncoding];
  19.   return loaded!=nil;
  20. }
  21. - (void)windowControllerDidLoadNib:(NSWindowController *) aController {
  22.   [super windowControllerDidLoadNib:aController];
  23.   if (loaded) [text setString:[loaded autorelease]];
  24. }
  25. 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:
  26. * 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φ.
  27. * 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. 
  28. * 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).
  29. 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).
  30.  
  31. Slovnφk
  32. 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:
  33. @interface Dict : NSObject
  34. +(NSString*)wordForPrefix:(NSString*)prefix; // najφt slovo pro dan² prefix
  35. +(void)addWord:(NSString*)word; // p°idat do slovnφku novΘ slovo
  36. +(void)removeWord:(NSString*)word; // odebrat ze slovnφku slovo
  37. @end
  38. 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².
  39.  
  40. Vazba slovnφku na GUI
  41. 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.
  42. 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.
  43. 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.
  44. 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:
  45. -(NSString*)_currentWord {
  46.   NSRange selection=[text selectedRange];
  47.   if (selection.length==0) {
  48.     if (selection.location>0) selection.location-;
  49.     selection=[[text textStorage] doubleClickAtIndex:selection.location];
  50.     [text setSelectedRange:selection];
  51.   }
  52.   if (selection.length==0) return nil;
  53.   return [[text string] substringWithRange:selection];
  54. }
  55. 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:).
  56. 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φ:
  57. -(void)dictWordComplete:sender {
  58.   NSString *s=[self _currentWord];
  59.   if (s && (s=[Dict wordForPrefix:s])) // mßm prefix && slovo ze slovnφku
  60.     [text replaceCharactersInRange:[text selectedRange] withString:s];
  61. }
  62. -(void)dictWordAdd:sender {
  63.   NSString *s=[self _currentWord];
  64.   if (s) [Dict addWord:s];
  65. }
  66. 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φ.
  67. 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.
  68.  
  69. First Responder a Cocoa toho um∞jφ jeÜt∞ vφc!
  70. 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...
  71. 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!
  72. 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.
  73. 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):
  74. @implementation NSTextView (MyWordCompletionCategory)
  75. -(NSString*)_currentWord {
  76.   NSRange selection=[self selectedRange];
  77.   if (selection.length==0) {
  78.     if (selection.location>0) selection.location-;
  79.     selection=[[self textStorage] doubleClickAtIndex:selection.location];
  80.     [self setSelectedRange:selection];
  81.   }
  82.   if (selection.length==0) return nil;
  83.   return [[self string] substringWithRange:selection];
  84. }
  85. -(void)dictWordComplete:sender {
  86.   NSString *s=[self _currentWord];
  87.   if (s && (s=[Dict wordForPrefix:s])) // mßm prefix && slovo ze slovnφku
  88.     [self replaceCharactersInRange:[self selectedRange] withString:s];
  89. }
  90. -(void)dictWordAdd:sender {
  91.   NSString *s=[self _currentWord];
  92.   if (s) [Dict addWord:s];
  93. }
  94. @end
  95. 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.
  96. To je pro dneÜek vÜe...
  97. ... p°φÜt∞ se pustφme do p°ehledu t°φd Application Kitu.
  98.  
  99. Ond°ej ╚ada
  100.  
  101. Implementace t°φdy Dict
  102. @implementation Dict
  103. static NSMutableSet *_set=nil;
  104. +(void)_aboutToQuit {
  105.   [[NSUserDefaults standardUserDefaults] setObject:[_set allObjects] forKey:@"Dictionary"];
  106. }
  107. +(NSMutableSet*)_sharedSet {
  108.   if (!_set) {
  109.     NSUserDefaults *df=[NSUserDefaults standardUserDefaults];
  110.     _set=[[NSMutableSet setWithArray:[df arrayForKey:@"Dictionary"]] retain];
  111.     // zajistφme, aby se slovnφk ulo₧il p°ed ukonΦenφm aplikace:
  112.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_aboutToQuit) name:NSApplicationWillTerminateNotification object:NSApp];
  113.   }
  114.   return _set;
  115. }
  116. +(void)addWord:(NSString*)word {
  117.   [[self _sharedSet] addObject:word];
  118. }
  119. +(void)removeWord:(NSString*)word {
  120.   [[self _sharedSet] removeObject:word];
  121. }
  122. +(NSString*)wordForPrefix:(NSString*)prefix {
  123.   NSEnumerator *en=[[self _sharedSet] objectEnumerator];
  124.   NSString *s;
  125.   while (s=[en nextObject])
  126.     if ([s hasPrefix:prefix]) return s;
  127.   return nil;
  128. }
  129. @end
  130.  
  131.