1/** <title>NSHelpManager</title> 2 3 <abstract>NSHelpManager is the class responsible for managing context help 4 for the application, and its mapping to the graphic elements.</abstract> 5 6 Copyright (C) 1999 Free Software Foundation, Inc. 7 8 Author: Pedro Ivo Andrade Tavares <ptavares@iname.com> 9 Date: September 1999 10 11 This file is part of the GNUstep GUI Library. 12 13 This library is free software; you can redistribute it and/or 14 modify it under the terms of the GNU Lesser General Public 15 License as published by the Free Software Foundation; either 16 version 2 of the License, or (at your option) any later version. 17 18 This library is distributed in the hope that it will be useful, 19 but WITHOUT ANY WARRANTY; without even the implied warranty of 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 Lesser General Public License for more details. 22 23 You should have received a copy of the GNU Lesser General Public 24 License along with this library; see the file COPYING.LIB. 25 If not, see <http://www.gnu.org/licenses/> or write to the 26 Free Software Foundation, 51 Franklin Street, Fifth Floor, 27 Boston, MA 02110-1301, USA. 28*/ 29 30#import <Foundation/NSArchiver.h> 31#import <Foundation/NSBundle.h> 32#import <Foundation/NSData.h> 33#import <Foundation/NSFileManager.h> 34#import <Foundation/NSMapTable.h> 35#import <Foundation/NSNotification.h> 36#import <Foundation/NSString.h> 37#import <Foundation/NSUserDefaults.h> 38#import "AppKit/NSAttributedString.h" 39#import "AppKit/NSApplication.h" 40#import "AppKit/NSWorkspace.h" 41#import "AppKit/NSFileWrapper.h" 42#import "AppKit/NSHelpManager.h" 43#import "AppKit/NSHelpPanel.h" 44#import "AppKit/NSHelpPanel.h" 45#import "AppKit/NSCursor.h" 46#import "AppKit/NSImage.h" 47#import "AppKit/NSGraphics.h" 48#import "AppKit/NSScrollView.h" 49#import "AppKit/NSTextView.h" 50#import "AppKit/NSTextStorage.h" 51 52#import "GNUstepGUI/GSHelpManagerPanel.h" 53 54@implementation NSBundle (NSHelpManager) 55 56- (NSString *) pathForHelpResource: (NSString *)fileName 57{ 58 NSFileManager *fm = [NSFileManager defaultManager]; 59 NSMutableArray *array = [NSMutableArray array]; 60 NSArray *languages; 61 NSString *rootPath = [self bundlePath]; 62 NSString *primary; 63 NSString *language; 64 NSEnumerator *enumerator; 65 66 languages = [[NSUserDefaults standardUserDefaults] 67 stringArrayForKey: @"NSLanguages"]; 68 primary = [rootPath stringByAppendingPathComponent: @"Resources"]; 69 70 enumerator = [languages objectEnumerator]; 71 72 while ((language = [enumerator nextObject])) 73 { 74 NSString *langDir = [NSString stringWithFormat: @"%@.lproj", language]; 75 76 [array addObject: [primary stringByAppendingPathComponent: langDir]]; 77 } 78 79 [array addObject: primary]; 80 81 primary = rootPath; 82 83 enumerator = [languages objectEnumerator]; 84 85 while ((language = [enumerator nextObject])) 86 { 87 NSString *langDir = [NSString stringWithFormat: @"%@.lproj", language]; 88 89 [array addObject: [primary stringByAppendingPathComponent: langDir]]; 90 } 91 92 [array addObject: primary]; 93 94 enumerator = [array objectEnumerator]; 95 96 while ((rootPath = [enumerator nextObject]) != nil) 97 { 98 NSString *helpDir; 99 NSString *helpPath; 100 BOOL isdir; 101 102 helpPath = [rootPath stringByAppendingPathComponent: fileName]; 103 104 if ([fm fileExistsAtPath: helpPath]) 105 { 106 return helpPath; 107 } 108 109 helpDir = [rootPath stringByAppendingPathComponent: @"Help"]; 110 111 if ([fm fileExistsAtPath: helpDir isDirectory: & isdir] && isdir) 112 { 113 helpPath = [helpDir stringByAppendingPathComponent: fileName]; 114 115 if ([fm fileExistsAtPath: helpPath]) 116 { 117 return helpPath; 118 } 119 } 120 } 121 122 return nil; 123} 124 125- (NSAttributedString *) contextHelpForKey: (NSString *)key 126{ 127 NSFileManager *fm = [NSFileManager defaultManager]; 128 NSString *dictPath = [self pathForResource: @"Help" ofType: @"plist"]; 129 NSDictionary *contextHelp = nil; 130 id helpFile = nil; 131 132 if (dictPath && [fm fileExistsAtPath: dictPath]) 133 { 134 contextHelp = [NSDictionary dictionaryWithContentsOfFile: dictPath]; 135 } 136 137 if (contextHelp) 138 { 139 helpFile = [contextHelp objectForKey: key]; 140 } 141 142 if (helpFile) 143 { 144 NSData *data = [helpFile objectForKey: @"NSHelpRTFContents"]; 145 return ((data != nil) ? [NSUnarchiver unarchiveObjectWithData: data] 146 : nil); 147 148 } 149 else 150 { 151 helpFile = [self pathForHelpResource: key]; 152 153 if (helpFile) 154 { 155 NSAttributedString *helpstr; 156 157 helpstr = [[NSAttributedString alloc] initWithPath: helpFile 158 documentAttributes: NULL]; 159 return TEST_AUTORELEASE (helpstr); 160 } 161 } 162 163 return nil; 164} 165 166@end 167 168@implementation NSApplication (NSHelpManager) 169 170- (void) showHelp: (id)sender 171{ 172 NSBundle *mb = [NSBundle mainBundle]; 173 NSDictionary *info = [mb infoDictionary]; 174 NSString *help = [info objectForKey: @"GSHelpContentsFile"]; 175 176 if (help == nil) 177 { 178 /* If there's no specification, we look for a files named 179 * "appname.rtfd" or "appname.rtf" 180 */ 181 help = [info objectForKey: @"NSExecutable"]; 182 } 183 184 if (help != nil) 185 { 186 NSString *file; 187 188 if ([[help pathExtension] length] == 0) 189 { 190 file = [mb pathForHelpResource: 191 [help stringByAppendingPathExtension: @"rtfd"]]; 192 193 if (file == nil) 194 { 195 file = [mb pathForHelpResource: 196 [help stringByAppendingPathExtension: @"rtf"]]; 197 } 198 } 199 else 200 { 201 file = [mb pathForHelpResource: help]; 202 } 203 204 if (file != nil) 205 { 206 BOOL result = NO; 207 NSString *ext = [file pathExtension]; 208 NSWorkspace *ws = [NSWorkspace sharedWorkspace]; 209 NSString *viewer; 210 211 viewer = [[NSUserDefaults standardUserDefaults] 212 stringForKey: @"GSHelpViewer"]; 213 214 if ([viewer isEqual: @"NSHelpPanel"] == NO) 215 { 216 if ([viewer length] == 0) 217 { 218 viewer = [ws getBestAppInRole: @"Viewer" forExtension: ext]; 219 } 220 if (viewer != nil) 221 { 222 result = [[NSWorkspace sharedWorkspace] openFile: file 223 withApplication: viewer]; 224 } 225 } 226 227 if (result == NO) 228 { 229 NSHelpPanel *panel; 230 NSTextView *tv; 231 id object = nil; 232 233 panel = [NSHelpPanel sharedHelpPanel]; 234 tv = [(NSScrollView*)[panel contentView] documentView]; 235 if (ext == nil 236 || [ext isEqualToString: @""] 237 || [ext isEqualToString: @"txt"] 238 || [ext isEqualToString: @"text"]) 239 { 240 object = [NSString stringWithContentsOfFile: file]; 241 } 242 else if ([ext isEqualToString: @"rtf"]) 243 { 244 NSData *data = [NSData dataWithContentsOfFile: file]; 245 246 object = [[NSAttributedString alloc] initWithRTF: data 247 documentAttributes: 0]; 248 AUTORELEASE (object); 249 } 250 else if ([ext isEqualToString: @"rtfd"]) 251 { 252 NSFileWrapper *wrapper; 253 254 wrapper = [[NSFileWrapper alloc] initWithPath: file]; 255 AUTORELEASE (wrapper); 256 object = [[NSAttributedString alloc] 257 initWithRTFDFileWrapper: wrapper 258 documentAttributes: 0]; 259 AUTORELEASE (object); 260 } 261 262 if (object != nil) 263 { 264 [[tv textStorage] setAttributedString: object]; 265 [tv sizeToFit]; 266 } 267 [tv setNeedsDisplay: YES]; 268 [panel makeKeyAndOrderFront: self]; 269 return; 270 } 271 } 272 } 273 274 NSBeep(); 275} 276 277- (void) activateContextHelpMode: (id)sender 278{ 279 [NSHelpManager setContextHelpModeActive: YES]; 280} 281 282@end 283 284@implementation NSHelpManager 285 286static NSHelpManager *_gnu_sharedHelpManager = nil; 287static BOOL _gnu_contextHelpActive = NO; 288static NSCursor *helpCursor = nil; 289 290 291// 292// Class methods 293// 294+ (NSHelpManager*) sharedHelpManager 295{ 296 if (!_gnu_sharedHelpManager) 297 { 298 _gnu_sharedHelpManager = [NSHelpManager alloc]; 299 [_gnu_sharedHelpManager init]; 300 } 301 return _gnu_sharedHelpManager; 302} 303 304+ (BOOL) isContextHelpModeActive 305{ 306 return _gnu_contextHelpActive; 307} 308 309+ (void) setContextHelpModeActive: (BOOL) flag 310{ 311 if (flag != _gnu_contextHelpActive) 312 { 313 _gnu_contextHelpActive = flag; 314 if (flag) 315 { 316 if (helpCursor == nil) 317 { 318 helpCursor = [[NSCursor alloc] 319 initWithImage: [NSImage imageNamed: @"common_HelpCursor"] 320 hotSpot: NSMakePoint(8, 2)]; 321 [helpCursor setOnMouseEntered: NO]; 322 [helpCursor setOnMouseExited: NO]; 323 } 324 [helpCursor push]; 325 [[NSNotificationCenter defaultCenter] 326 postNotificationName: NSContextHelpModeDidActivateNotification 327 object: [self sharedHelpManager]]; 328 } 329 else 330 { 331 [helpCursor pop]; 332 [[NSNotificationCenter defaultCenter] 333 postNotificationName: NSContextHelpModeDidDeactivateNotification 334 object: [self sharedHelpManager]]; 335 } 336 } 337} 338 339// 340// Instance methods 341// 342- (id) init 343{ 344 contextHelpTopics = NSCreateMapTable(NSObjectMapKeyCallBacks, 345 NSObjectMapValueCallBacks, 346 64); 347 return self; 348} 349 350- (NSAttributedString*) contextHelpForObject: (id)object 351{ 352 /* Help is kept on the contextHelpTopics NSMapTable, with 353 the object for it as the key. 354 355 Help is loaded on demand: 356 If it's an NSAttributedString which is stored, then it's already 357 loaded. 358 If it's nil, there's no help for this object, and that's what we return. 359 If it's an NSString, it's the path for the help, and we ask NSBundle 360 for it. */ 361 // FIXME: Check this implementation when NSResponders finally store what 362 // their context help is. 363 364 id hc = NSMapGet(contextHelpTopics, object); 365 if (hc) 366 { 367 if (![hc isKindOfClass: [NSAttributedString class]]) 368 { 369 hc = [[NSBundle mainBundle] contextHelpForKey: hc]; 370 /* We store the retrieved value, or remove the key from 371 the table if nil returns (note that it's OK if the key 372 does not exist already. */ 373 if (hc) 374 NSMapInsert(contextHelpTopics, object, hc); 375 else 376 NSMapRemove(contextHelpTopics, object); 377 } 378 } 379 return hc; 380} 381 382- (void) removeContextHelpForObject: (id)object 383{ 384 NSMapRemove(contextHelpTopics, object); 385} 386 387- (void) setContextHelp: (NSAttributedString *)help forObject: (id)object 388{ 389 NSMapInsert(contextHelpTopics, object, help); 390} 391 392/** 393 * Deprecated ... do not use. 394 * Use -setContextHelp:forObject: instead. 395 */ 396- (void) setContextHelp: (NSAttributedString*) help withObject: (id) object 397{ 398 NSMapInsert(contextHelpTopics, object, help); 399} 400 401- (BOOL) showContextHelpForObject: (id)object locationHint: (NSPoint) point 402{ 403 NSAttributedString *contextHelp = [self contextHelpForObject: object]; 404 405 if (contextHelp) 406 { 407 GSHelpManagerPanel *helpPanel; 408 409 // FIXME: We should position the window at point! 410 // runModalForWindow will centre the window. 411 helpPanel = [GSHelpManagerPanel sharedHelpManagerPanel]; 412 [helpPanel setHelpText: contextHelp]; 413 [NSApp runModalForWindow: helpPanel]; 414 return YES; 415 } 416 else 417 return NO; 418} 419 420@end 421 422