/*****************************************************************************
 *
 *	Help.h
 *	
 *	Author:		Denise Howard (not really) Julie Zelenski
 *	
 *	Desc:		This is a stripped down version of the Help object from
 *				the NeXT BusyBox example code.
 *
 *      Modified by Tsutomu_Eguchi@ns.saga-med.ac.jp	  April 1998
 *	                support localization
 *					
 *****************************************************************************/
/* 
 * Help.m, a help object to manage and display RTF and RTFD 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 and RTFD 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. *
 */
 
#import "Help.h"
@implementation Help:Object

static Help	*theHelpInstance = NULL;

+ new
{
	if (!theHelpInstance) {
		theHelpInstance = [[self alloc] init];
	}
	return (id)theHelpInstance;
}

- 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.  Gets the appDirectory from NXApp, finds help 
 * directory in app wrapper.
 */
{
    [[NXBundle mainBundle] getPath:helpDirectory forResource:"HelpOpener" ofType:""]; //TE
//   sprintf(helpDirectory, "%s/%s", [[NXBundle mainBundle] directory], 
//		"HelpOpener");
    sprintf(noHelpFile, "%s/%s", helpDirectory, "NoHelp.rtf");
    [NXApp loadNibSection:"Help.nib" owner:self];

    [helpBrowser setDelegate:self];
	[helpBrowser setTarget:self];
	[helpBrowser acceptArrowKeys:YES andSendActionMessages:YES];
    [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"];
    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;
}

/* HELP METHODS */


- showHelpFile:(const char*)filename;
/* Tries to open a stream for the specified RTF or RTFD 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 "nohelp" file.  It also brings the
 * help panel to the front.
 */
{
   NXStream *stream;
   char tFileName[MAXPATHLEN];
   char helpFile[MAXPATHLEN];
   char *p;
   static NXPoint origin = {0.0,0.0};
   
   if (!filename)
		return self;

	// change ' ' to '_'
	
	strcpy(tFileName, filename);
	for (p=(char*)tFileName; *p!='\0'; p++)
		if (*p==' ')
			*p='_';

    if (![self browser:helpBrowser selectCell:filename inColumn:0])
        [self browser:helpBrowser selectCell:"NoHelp" inColumn:0];
    sprintf(helpFile, "%s/%s.rtf", helpDirectory, tFileName);
    if ((stream = NXMapFile(helpFile, NX_READONLY)) == NULL) {
    	sprintf(helpFile, "%s/%s.rtfd", helpDirectory, tFileName);
		[[helpScrollView docView] openRTFDFrom:helpFile];
	} else {
    	[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 */

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;
	char *p;
    
    if (!list) list = (char **)malloc(CHUNK*sizeof(char *));
    if ((suffix = rindex(file,'.')) != NULL) 
        *suffix  = '\0'; 	/* strip rtf or rtfd suffix */

		// change ' ' to '_'

	for (p=(char*)file; *p!='\0'; p++)
		if (*p=='_')
			*p=' ';

    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)
{
	BOOL		result;
	char		*p;
	
    result = (!s[0] || s[0] == '.') ? NO : YES;
	
	if (result) {
		result = NO;
		if ((p = rindex(s, '.')) != NULL) {
			if ((!strcmp(p+1, "rtf")) || (!strcmp(p+1, "rtfd")))
				if (!strstr(s, "NoHelp"))
					result = YES;
		}
	}
	
	return result;
}

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;
}

@end
