|
|
1.1 root 1: /*
2: * Help.m, a help object to manage and display RTF help files.
3: * The help object owns its own nib section "Help.nib" which has a
4: * Help panel - with an NXBrowser to display the help topics, and
5: * a scrolling text view to display the help files. The help files are
6: * all stored as RTF text files in a directory called Help within the
7: * app wrapper. At init time, the Help object loads the browser with
8: * names of all the files found in the Help directory. When a name is
9: * chosen from the browser, the help object opens a stream on that file
10: * and read the rich text into the text object. The help object also
11: * responds to request for context-sensitive help, by trying to find an
12: * appropriate help file for the view that was moused down in. See the
13: * helpForObject: method for more detailed explanation of that.
14: * This object is a useful addition to any program, because all users
15: * appreciate help!
16: *
17: * Author: Julie Zelenski, NeXT Developer Support
18: * You may freely copy, distribute and reuse the code in this example.
19: * NeXT disclaims any warranty of any kind, expressed or implied, as to
20: * its fitness for any particular use.
21: */
22:
23: #import <appkit/appkit.h>
24: #import "Help.h"
25: #import "BusyBoxApp.h"
26: #import <sys/dir.h> //for getdirentries()
27: #import <libc.h>
28:
29:
30: @implementation Help:Object
31:
32:
33: - init
34: /* For newly created help object, loads the nib section with the Help
35: * panel which has the topics browser and a scrolling text view for
36: * displaying help files.
37: */
38: {
39: [super init];
40: [[NXBundle mainBundle] getPath:helpDirectory forResource:"HelpFiles" ofType:NULL];
41: sprintf(noHelpFile,"%s/%s",helpDirectory,"No Help.rtf");
42: // Note that as a side-effect of loading Help.nib, the helpPanel outlet
43: // gets set to point to the window.
44: if (![NXApp loadNibSection:"Help.nib" owner:self]) {
45: NXLogError ("Could not load Help.nib");
46: }
47: return self;
48: }
49:
50: - setHelpBrowser:anObject;
51: /* Sets the helpBrowser outlet, and calls on the browser to load up.
52: */
53: {
54: helpBrowser = anObject;
55: [helpBrowser setDelegate:self];
56: [helpBrowser loadColumnZero];
57: return self;
58: }
59:
60: /* TARGET/ACTION METHODS */
61:
62: - generalHelp:sender;
63: /* This is the target/action method for the "Help" menu item. This method
64: * will show the "general help" file.
65: */
66: {
67: [self showHelpFile:"General Help"];
68: return self;
69: }
70:
71: - browserHit:sender
72: /* This is the target/action method from the help topics browser. When
73: * a help topic is selected, this method will show the help file for that
74: * topic.
75: */
76: {
77: [self showHelpFile:[[[sender matrixInColumn:0] selectedCell] stringValue]];
78: return self;
79: }
80:
81:
82: - print:sender;
83: /* This method is called by the Print menu cell in the main menu. It will
84: * print the current help file.
85: */
86: {
87: [[helpScrollView docView] printPSCode:sender];
88: return self;
89: }
90:
91:
92: /* HELP METHODS */
93:
94: - helpForWindow:window;
95: /* If this window is the main menu, it will ask to display the help file
96: * for "Main Menu." (I didn't want to call the Main Menu help file "BusyBox
97: * Menu") Else it gets the help file for <window title> <window name>
98: * "Info Panel" or "Document Menu" for example.
99: */
100: {
101: char filename[MAXPATHLEN];
102:
103: if (window == [NXApp mainMenu])
104: sprintf(filename,"Main Menu");
105: else
106: sprintf(filename,"%s %s",[window title],[window name]);
107: [self showHelpFile:filename];
108: return self;
109: }
110:
111: - helpForView:view atPoint:(NXPoint *)aPt;
112: /* Gives help for the specified view, which was hit with a Control-mouseDown
113: * at aPt. Gives feedback to the user which view was hit by framing the
114: * bounds of the view with a gray rectangle. This is done with instance
115: * drawing and erased after the helpfile is read and displayed. If the view
116: * is a matrix, the method makes the effort to find the particular cell which
117: * was hit and to only frame that cell.
118: * This method calls on the method helpForObject which will figure out which
119: * help file to display.
120: */
121: {
122: int row,column;
123: NXRect b;
124: NXPoint p;
125: id cell = nil;
126:
127: [view getBounds:&b];
128: if ([view isKindOf:[Matrix class]]) {
129: p = *aPt;
130: [view convertPoint:&p fromView:nil];
131: if (cell = [view getRow:&row andCol:&column forPoint:&p])
132: [view getCellFrame:&b at:row :column];
133: }
134: [view lockFocus];
135: PSnewinstance();
136: PSsetinstance(YES);
137: PSsetgray(NX_DKGRAY);
138: NXFrameRectWithWidth(&b,1.0);
139: [[view window] flushWindow];
140: PSsetinstance(NO);
141: if (cell)
142: [self helpForObject:cell];
143: else if ([[view window] contentView] == view)
144: [self helpForWindow:[view window]];
145: else
146: [self helpForObject:view];
147: PSnewinstance();
148: [view unlockFocus];
149: return self;
150: }
151:
152:
153: - helpForObject:object;
154: /* The method tries to cons together a file name that represents help
155: * for the given object. It makes no assumptions about the tags or
156: * titles of the views, rather it employs a general strategy. For many
157: * objects, the name is simply used ("ScrollView","TextField"). For Cells,
158: * it strips Cell from the name, for our purposes, TextFieldCell is
159: * the same as a TextField. For Buttons (and ButtonCells), it uses icon + name
160: * (radio Button, popUp Button). For MenuCells, it figures out where it is
161: * submenu or a item. Items display help for their parent menu, (using
162: * helpForWindow: method), submenus use title + menu (Format Menu, Find Menu).
163: * What is neat about this general scheme is that all views, windows, menu
164: * respond to the control-click, not just the ones I created. Bring up the
165: * Print panel and control-click on the Resolution popup, and you will see
166: * the help file for popups! Control-click on the Save Panel, you get help for
167: * Save Panel! This is probably not the way that a real application would
168: * implement help. It happens to work for mine, because I want the same
169: * help file to be displayed for every popup list, the same file for every
170: * button. In your app, different button have various functions, and you
171: * would want to display different help files for different buttons. You
172: * will probably devise a scheme using unique tags or titles, a more specific
173: * way to determine what help file to display.
174: */
175: {
176: char filename[MAXPATHLEN];
177: char *suffix;
178: int len;
179:
180: sprintf(filename,"%s",[object name]);
181: if ([object isKindOf:[Button class]] || [object isKindOf:[Cell class]]) {
182: if ([object icon])
183: sprintf(filename,"%s %s",[object icon],[object name]);
184: if ([object isKindOf:[Cell class]]) {
185: len = strlen(filename);
186: suffix = filename + (len-4)*sizeof(char);
187: if (strcmp("Cell",suffix)==0) {
188: filename[len-4] = '\0';
189: }
190: }
191: }
192: if ([object isKindOf:[MenuCell class]]) {
193: if ([object icon] && (strcmp([object icon],"menuArrow")==0))
194: sprintf(filename,"%s %s",[object title],"Menu");
195: else {
196: return [self helpForWindow:[[object controlView] window]];
197: }
198: }
199: [self showHelpFile:filename];
200: return self;
201: }
202:
203: - showHelpFile:(const char*)filename;
204: /* Tries to open a stream for the specified RTF text file in the Help
205: * directory so the text object can readRichText. Also selects the
206: * filename in the browser of help topics. If the filename doesn't exist,
207: * it will select and display the "no help" file. It also brings the
208: * help panel to the front.
209: */
210: {
211: NXStream *stream;
212: char helpFile[MAXPATHLEN];
213: static NXPoint origin = {0.0,0.0};
214:
215: if (![self browser:helpBrowser selectCell:filename inColumn:0])
216: [self browser:helpBrowser selectCell:"No Help" inColumn:0];
217: sprintf(helpFile,"%s/%s.rtf",helpDirectory,filename);
218: if ((stream = NXMapFile(helpFile,NX_READONLY)) == NULL)
219: stream = NXMapFile(noHelpFile,NX_READONLY);
220: if (stream != NULL) {
221: [helpPanel disableFlushWindow];
222: [[helpScrollView docView] readRichText:stream];
223: [[helpScrollView docView] scrollPoint:&origin];
224: [[helpPanel reenableFlushWindow] flushWindow];
225: NXCloseMemory(stream,NX_FREEBUFFER);
226: }
227: [helpPanel orderFront:self];
228: return self;
229: }
230:
231:
232: /* BROWSER DELEGATE METHODS */
233:
234:
235: #define CHUNK 127
236: static char **addFile(const char *file, int length, char **list, int count)
237: /* Adds the specified filename to the list of filenames. It allocates
238: * more memory in chunks as needed.
239: */
240: {
241: char *suffix;
242:
243: if (!list) list = (char **)malloc(CHUNK*sizeof(char *));
244: if (suffix = rindex(file,'.'))
245: *suffix = '\0'; /* strip rtf suffix */
246: list[count] = (char *)malloc((length+1)*sizeof(char));
247: strcpy(list[count], file);
248: count++;
249: if (!(count% CHUNK)) {
250: list = (char **)realloc(list,(((count/CHUNK)+1)*CHUNK)*sizeof(char *));
251: }
252: list[count] = NULL;
253: return list;
254: }
255:
256: static void freeList(char **list)
257: /* Frees the array of filenames
258: */
259: {
260: char **strings;
261:
262: if (list) {
263: strings = list;
264: while (*strings) free(*strings++);
265: free(list);
266: }
267: }
268:
269: static BOOL isOk(const char *s)
270: /* checks to make sure the filename is not NULL and to verify that it is
271: * not a "dot"--hidden file.
272: */
273: {
274: return (!s[0] || s[0] == '.') ? NO : YES;
275: }
276:
277: static int caseInsensitiveCompare(const void *arg1, const void *arg2)
278: /* Compares the two arguments without regard for case using strcasecmp().
279: */
280: {
281: char *string1, *string2;
282:
283: string1 = *((char **)arg1);
284: string2 = *((char **)arg2);
285: return strcasecmp(string1,string2);
286: }
287:
288: static char **fileList;
289:
290: - (int)browser:sender fillMatrix:matrix inColumn:(int)column
291: /* This delegate method goes out to the help directory and gets a list
292: * of all the files in that directory. It creates a list of file names
293: * for the static variable fileList, and will load the filenames into the
294: * browser on demand (lazy loading).
295: */
296: {
297: long basep;
298: char *buf;
299: struct direct *dp;
300: char **list = NULL;
301: int cc, fd, fileCount = 0;
302: char dirbuf[8192];
303:
304: if ((fd = open(helpDirectory, O_RDONLY, 0644)) > 0) {
305: cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
306: while (cc) {
307: dp = (struct direct *)buf;
308: if (isOk(dp->d_name)) {
309: list = addFile(dp->d_name, dp->d_namlen, list, fileCount++);
310: }
311: buf += dp->d_reclen;
312: if (buf >= dirbuf + cc) {
313: cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
314: }
315: }
316: close(fd);
317: if (list) qsort(list,fileCount,sizeof(char *),caseInsensitiveCompare);
318: }
319: freeList(fileList);
320: fileList = list;
321: return fileCount;
322: }
323:
324: - browser:sender loadCell:cell atRow:(int)row inColumn:(int)column
325: /* This delegate method loads the cell for a given row. The stringValue
326: * for that row comes from the fileList.
327: */
328: {
329: if (fileList) {
330: [cell setStringValueNoCopy:fileList[row]];
331: [cell setLeaf:YES];
332: }
333: return self;
334: }
335:
336:
337: - (BOOL)browser:sender selectCell:(const char *)title inColumn:(int)column
338: /* This delegate method selects the cell with the given title. If it finds
339: * a cell with that title, it verifies that it has a file entry in the
340: * fileList, forces the loading of the cell, selects it (highlights) and
341: * scrolls the browser so the cell is visible. It returns a boolean value
342: * which indicates whether the cell was found.
343: */
344: {
345: int row;
346: id matrix;
347:
348: if (title) {
349: matrix = [sender matrixInColumn:column];
350: if (!fileList) return NO;
351: for (row = [matrix cellCount]-1; row >= 0; row--) {
352: if (fileList[row] && !strcmp(title, fileList[row])) {
353: [sender getLoadedCellAtRow:row inColumn:column];
354: [matrix selectCellAt:row :0];
355: [matrix scrollCellToVisible:row :0];
356: return YES;
357: }
358: }
359: }
360: return NO;
361: }
362:
363:
364: /* WINDOW DELEGATE METHODS */
365:
366: - windowWillResize:sender toSize:(NXSize *)frameSize;
367: /* This method constrains the Help Panel to a reasonable minimum size
368: * when the user resizes the panel.
369: */
370: {
371: frameSize->width = MAX(frameSize->width,400.0);
372: frameSize->height = MAX(frameSize->height,350.0);
373: return self;
374: }
375:
376:
377: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.