Annotation of Examples/AppKit/BusyBox/Help.m, revision 1.1.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.