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