Annotation of Examples/AppKit/BusyBox/Help.m, revision 1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.