|
|
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.