home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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 directo(9alled 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 "BusyBoxApp.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;
- /*(@s 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,
- * (Atrips 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[MA(BHLEN];
- 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).
- */
- {
- lon(Csep;
- 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