This is Help.m in view mode; [Download] [Up]
/* * Help.m, a help object to manage and display RTF help files. * The help object owns its own nib section "Help.nib" which has a * Help panel - with an NXBrowser to display the help topics, and * a scrolling text view to display the help files. The help files are * all stored as RTF text files in a directory called Help within the * app wrapper. At init time, the Help object loads the browser with * names of all the files found in the Help directory. When a name is * chosen from the browser, the help object opens a stream on that file * and read the rich text into the text object. The help object also * responds to request for context-sensitive help, by trying to find an * appropriate help file for the view that was moused down in. See the * helpForObject: method for more detailed explanation of that. * This object is a useful addition to any program, because all users * appreciate help! * * Author: Julie Zelenski, NeXT Developer Support * You may freely copy, distribute and reuse the code in this example. * NeXT disclaims any warranty of any kind, expressed or implied, as to * its fitness for any particular use. */ #import <appkit/appkit.h> #import "Help.h" #import <sys/dir.h> //for getdirentries() #import <libc.h> @implementation Help:Object - init /* For newly created help object, loads the nib section with the Help * panel which has the topics browser and a scrolling text view for * displaying help files. */ { [super init]; [[NXBundle mainBundle] getPath:helpDirectory forResource:"HelpFiles" ofType:NULL]; sprintf(noHelpFile,"%s/%s",helpDirectory,"No Help.rtf"); // Note that as a side-effect of loading Help.nib, the helpPanel outlet // gets set to point to the window. if (![NXApp loadNibSection:"Help.nib" owner:self]) { NXLogError ("Could not load Help.nib"); } return self; } - setHelpBrowser:anObject; /* Sets the helpBrowser outlet, and calls on the browser to load up. */ { helpBrowser = anObject; [helpBrowser setDelegate:self]; [helpBrowser loadColumnZero]; return self; } /* TARGET/ACTION METHODS */ - generalHelp:sender; /* This is the target/action method for the "Help" menu item. This method * will show the "general help" file. */ { [self showHelpFile:"General Help"]; return self; } - browserHit:sender /* This is the target/action method from the help topics browser. When * a help topic is selected, this method will show the help file for that * topic. */ { [self showHelpFile:[[[sender matrixInColumn:0] selectedCell] stringValue]]; return self; } - print:sender; /* This method is called by the Print menu cell in the main menu. It will * print the current help file. */ { [[helpScrollView docView] printPSCode:sender]; return self; } /* HELP METHODS */ - helpForWindow:window; /* If this window is the main menu, it will ask to display the help file * for "Main Menu." (I didn't want to call the Main Menu help file "BusyBox * Menu") Else it gets the help file for <window title> <window name> * "Info Panel" or "Document Menu" for example. */ { char filename[MAXPATHLEN]; if (window == [NXApp mainMenu]) sprintf(filename,"Main Menu"); else sprintf(filename,"%s %s",[window title],[window name]); [self showHelpFile:filename]; return self; } - helpForView:view atPoint:(NXPoint *)aPt; /* Gives help for the specified view, which was hit with a Control-mouseDown * at aPt. Gives feedback to the user which view was hit by framing the * bounds of the view with a gray rectangle. This is done with instance * drawing and erased after the helpfile is read and displayed. If the view * is a matrix, the method makes the effort to find the particular cell which * was hit and to only frame that cell. * This method calls on the method helpForObject which will figure out which * help file to display. */ { int row,column; NXRect b; NXPoint p; id cell = nil; [view getBounds:&b]; if ([view isKindOf:[Matrix class]]) { p = *aPt; [view convertPoint:&p fromView:nil]; if (cell = [view getRow:&row andCol:&column forPoint:&p]) [view getCellFrame:&b at:row :column]; } [view lockFocus]; PSnewinstance(); PSsetinstance(YES); PSsetgray(NX_DKGRAY); NXFrameRectWithWidth(&b,1.0); [[view window] flushWindow]; PSsetinstance(NO); if (cell) [self helpForObject:cell]; else if ([[view window] contentView] == view) [self helpForWindow:[view window]]; else [self helpForObject:view]; PSnewinstance(); [view unlockFocus]; return self; } - helpForObject:object; /* The method tries to cons together a file name that represents help * for the given object. It makes no assumptions about the tags or * titles of the views, rather it employs a general strategy. For many * objects, the name is simply used ("ScrollView","TextField"). For Cells, * it strips Cell from the name, for our purposes, TextFieldCell is * the same as a TextField. For Buttons (and ButtonCells), it uses icon + name * (radio Button, popUp Button). For MenuCells, it figures out where it is * submenu or a item. Items display help for their parent menu, (using * helpForWindow: method), submenus use title + menu (Format Menu, Find Menu). * What is neat about this general scheme is that all views, windows, menu * respond to the control-click, not just the ones I created. Bring up the * Print panel and control-click on the Resolution popup, and you will see * the help file for popups! Control-click on the Save Panel, you get help for * Save Panel! This is probably not the way that a real application would * implement help. It happens to work for mine, because I want the same * help file to be displayed for every popup list, the same file for every * button. In your app, different button have various functions, and you * would want to display different help files for different buttons. You * will probably devise a scheme using unique tags or titles, a more specific * way to determine what help file to display. */ { char filename[MAXPATHLEN]; char *suffix; int len; sprintf(filename,"%s",[object name]); if ([object isKindOf:[Button class]] || [object isKindOf:[Cell class]]) { if ([object icon]) sprintf(filename,"%s %s",[object icon],[object name]); if ([object isKindOf:[Cell class]]) { len = strlen(filename); suffix = filename + (len-4)*sizeof(char); if (strcmp("Cell",suffix)==0) { filename[len-4] = '\0'; } } } if ([object isKindOf:[MenuCell class]]) { if ([object icon] && (strcmp([object icon],"menuArrow")==0)) sprintf(filename,"%s %s",[object title],"Menu"); else { return [self helpForWindow:[[object controlView] window]]; } } [self showHelpFile:filename]; return self; } - showHelpFile:(const char*)filename; /* Tries to open a stream for the specified RTF text file in the Help * directory so the text object can readRichText. Also selects the * filename in the browser of help topics. If the filename doesn't exist, * it will select and display the "no help" file. It also brings the * help panel to the front. */ { NXStream *stream; char helpFile[MAXPATHLEN]; static NXPoint origin = {0.0,0.0}; if (![self browser:helpBrowser selectCell:filename inColumn:0]) [self browser:helpBrowser selectCell:"No Help" inColumn:0]; sprintf(helpFile,"%s/%s.rtf",helpDirectory,filename); if ((stream = NXMapFile(helpFile,NX_READONLY)) == NULL) stream = NXMapFile(noHelpFile,NX_READONLY); if (stream != NULL) { [helpPanel disableFlushWindow]; [[helpScrollView docView] readRichText:stream]; [[helpScrollView docView] scrollPoint:&origin]; [[helpPanel reenableFlushWindow] flushWindow]; NXCloseMemory(stream,NX_FREEBUFFER); } [helpPanel orderFront:self]; return self; } /* BROWSER DELEGATE METHODS */ #define CHUNK 127 static char **addFile(const char *file, int length, char **list, int count) /* Adds the specified filename to the list of filenames. It allocates * more memory in chunks as needed. */ { char *suffix; if (!list) list = (char **)malloc(CHUNK*sizeof(char *)); if (suffix = rindex(file,'.')) *suffix = '\0'; /* strip rtf suffix */ list[count] = (char *)malloc((length+1)*sizeof(char)); strcpy(list[count], file); count++; if (!(count% CHUNK)) { list = (char **)realloc(list,(((count/CHUNK)+1)*CHUNK)*sizeof(char *)); } list[count] = NULL; return list; } static void freeList(char **list) /* Frees the array of filenames */ { char **strings; if (list) { strings = list; while (*strings) free(*strings++); free(list); } } static BOOL isOk(const char *s) /* checks to make sure the filename is not NULL and to verify that it is * not a "dot"--hidden file. */ { return (!s[0] || s[0] == '.') ? NO : YES; } static int caseInsensitiveCompare(const void *arg1, const void *arg2) /* Compares the two arguments without regard for case using strcasecmp(). */ { char *string1, *string2; string1 = *((char **)arg1); string2 = *((char **)arg2); return strcasecmp(string1,string2); } static char **fileList; - (int)browser:sender fillMatrix:matrix inColumn:(int)column /* This delegate method goes out to the help directory and gets a list * of all the files in that directory. It creates a list of file names * for the static variable fileList, and will load the filenames into the * browser on demand (lazy loading). */ { long basep; char *buf; struct direct *dp; char **list = NULL; int cc, fd, fileCount = 0; char dirbuf[8192]; if ((fd = open(helpDirectory, O_RDONLY, 0644)) > 0) { cc = getdirentries(fd, (buf = dirbuf), 8192, &basep); while (cc) { dp = (struct direct *)buf; if (isOk(dp->d_name)) { list = addFile(dp->d_name, dp->d_namlen, list, fileCount++); } buf += dp->d_reclen; if (buf >= dirbuf + cc) { cc = getdirentries(fd, (buf = dirbuf), 8192, &basep); } } close(fd); if (list) qsort(list,fileCount,sizeof(char *),caseInsensitiveCompare); } freeList(fileList); fileList = list; return fileCount; } - browser:sender loadCell:cell atRow:(int)row inColumn:(int)column /* This delegate method loads the cell for a given row. The stringValue * for that row comes from the fileList. */ { if (fileList) { [cell setStringValueNoCopy:fileList[row]]; [cell setLeaf:YES]; } return self; } - (BOOL)browser:sender selectCell:(const char *)title inColumn:(int)column /* This delegate method selects the cell with the given title. If it finds * a cell with that title, it verifies that it has a file entry in the * fileList, forces the loading of the cell, selects it (highlights) and * scrolls the browser so the cell is visible. It returns a boolean value * which indicates whether the cell was found. */ { int row; id matrix; if (title) { matrix = [sender matrixInColumn:column]; if (!fileList) return NO; for (row = [matrix cellCount]-1; row >= 0; row--) { if (fileList[row] && !strcmp(title, fileList[row])) { [sender getLoadedCellAtRow:row inColumn:column]; [matrix selectCellAt:row :0]; [matrix scrollCellToVisible:row :0]; return YES; } } } return NO; } /* WINDOW DELEGATE METHODS */ - windowWillResize:sender toSize:(NXSize *)frameSize; /* This method constrains the Help Panel to a reasonable minimum size * when the user resizes the panel. */ { frameSize->width = MAX(frameSize->width,400.0); frameSize->height = MAX(frameSize->height,350.0); return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.