
/* Controller.m
 * 
 *
 * This subclass of Object handles all the user interface actions.
 * 
 *
 * 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 "Controller.h"
#import "Localization.h"	// Localization routines
#import "ClockView.h"		// Clock display routines

#import <appkit/Matrix.h>
#import <appkit/Cell.h>
#import <appkit/NXBrowser.h>
#import <appkit/NXBrowserCell.h>
#import <appkit/ScrollView.h>
#import <appkit/Text.h>
#import <appkit/Panel.h>


#import <sys/dir.h> //for getdirentries()
#import <libc.h> 
#import <string.h>


/* Static Functions to be defined later	*/

static char **addFile(const char *file, int length, char **list, int count); 	
static void freeList(char **list);
static BOOL isOk(const char *s);
static int caseInsensitiveCompare(void *arg1, void *arg2);
static char **fileList;

#define MAX_TIME_CHARS 100
#define FILE_NOT_FOUND_MSG LocalString("File %s not found.", NULL, "The message the user receives if the given file is not found. This is normally an internal error")
		
@implementation Controller


/* Register some meaningful default values for the system, in case the user starts with
 * a virgin one. The following defaults are mostly used for strftime().
 */
+ initialize
{
	
	
    static   NXDefaultsVector AskMeDefaults = {
	{"NXDateAndTime", "%a %b %d %H:%M:%S %Z %Y"},
	{"NXDate", "%a %b %d %Y"},
	{"NXTime", "%H:%M:%S %Z"},
	{"NXShortDays", "Sun Mon Tue Wed Thu Fri Sat"},
	{"NXLongDays", "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
},
	{"NXShortMonths", "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"
},
	{"NXLongMonths", "January February March April May June July August September October November December"},
	{NULL}
    };

    NXRegisterDefaults("AskMe", AskMeDefaults);
    
	return self;
}


/* appDidInit: does miscellaneous initialization */
- appDidInit:sender
{
	
    char timeBuffer[MAX_TIME_CHARS];
	time_t curtime;
	const char *menuTitle, *welcomeMsg;
	NXRect	fieldRect;
	
	
		/* Save window frame for later resizing */
	[myWindow getFrame:&windowFrame];
	
		/* Find the AskMeText directory	*/	
	sprintf(&textDirectory[0],"%s",findLocalDir());

		/* Change the menu title */
	menuTitle = LocalStringFromTable("Init", "Ask Me", NULL,
		 "main menu title");
	[[NXApp mainMenu] setTitle:menuTitle];

		/* Set up the split view	*/
	[self initSplitView];
	
		/* Set up the text browser and bring up welcome message	*/
	[myBrowser setDelegate:self];
	[myBrowser loadColumnZero];
	welcomeMsg = LocalStringFromTable("Init", "Welcome", NULL,
		 "welcome message");
	[self showTextFile:welcomeMsg:0:textDirectory];
	
	/* Show current date and time - Note: date and time are not
	 * localized yet (ie they are still English strings, or they follow
	 * the GLOBAL settings of the language preference you chose last).
	 * One could possibly perform a translation
	 * before displaying here.
	 */
	
	curtime = time(0);
	LocalDate(timeBuffer, MAX_TIME_CHARS, &curtime);
	[dateField setStringValue:&timeBuffer[0]];
	
	/* Set up a timed entry to update the time periodically */
	[timeOfDayField getFrame:&fieldRect];
	timeOfDayField = [ [ClockView alloc]initFrame:&fieldRect];
	
	return self;
}

- appWillTerminate: sender
{
    
	/* remove the timed entry */
	[timeOfDayField stopTimedEntry];
	    
	return self;
}




/* splitview support */

- initSplitView
{
	[mySplitView setDelegate:self];
	
  	[mySplitView addSubview:myBrowser];
	[mySplitView addSubview:myScrollView];
	[mySplitView display];
	
	return self;
}





/* Adjust the subviews inside the splitview when the window resizes.  
 * Make sure that the upper view doesn't get too small.
 */

- splitView:sender 
    resizeSubviews:(const NXSize *)oldSize
{
    NXRect lower, upper;
    float delta;
    
    [[sender window] disableDisplay];
    [sender adjustSubviews];
    
    [myBrowser getFrame:&upper];
    [myScrollView getFrame:&lower];
    if (upper.size.height < 100.0) {
        delta = 100.0 - upper.size.height;
        upper.size.height=100.0;
        lower.size.height-=delta;
        [myBrowser setFrame:&upper];
        [myScrollView setFrame:&lower];
        }
        
    [[sender window] reenableDisplay];
    [[sender window] display];

    return self;
}


/* Constrain the y coordinate limits of the splitview divider  */

- splitView:sender getMinY:(NXCoord *)minY maxY:(NXCoord *)maxY
  ofSubviewAt:(int)offset
{
   NXRect    rect;
   
	offset = 0; 
	[mySplitView getBounds:&rect];
    *minY = 100.0;
    *maxY = rect.size.height - 100.0;
	if ( *maxY < 100.0 ) *maxY = 100.0;
    return self;

}

/* Browser support */

- showTextFile:(const char *)filename:(int)column:(const char*)directoryname
{
	NXStream *stream;
   char textFile[MAXPATHLEN];
   static NXPoint origin = {0.0,0.0};
 

    if ( ! [self browser:myBrowser selectCell:filename inColumn:column]  )	{
  		NXRunAlertPanel(NULL, FILE_NOT_FOUND_MSG, NULL, NULL,
		NULL,filename);
		return self;
	}
    sprintf(textFile,"%s/%s",directoryname,filename);
    if ((stream = NXMapFile(textFile,NX_READONLY)) == NULL)	{
        NXRunAlertPanel(NULL, FILE_NOT_FOUND_MSG, NULL, NULL,
		NULL,filename);
		return self;
	}

    if (stream != NULL) {
   		[myWindow disableFlushWindow];
    	[[myScrollView docView] readRichText:stream]; 
		[[myScrollView docView] scrollPoint:&origin];
		[[myWindow reenableFlushWindow] flushWindow];
    	NXCloseMemory(stream,NX_FREEBUFFER);
    }
    [myWindow orderFront:self];
    return self;
}


/* This is the target/action method from the AskMe browser.  When
 * a topic is selected, this method will show the text file for that
 * topic.
 */

- browserHit: sender
{
	[self showTextFile:[[[sender matrixInColumn:0] selectedCell] 						stringValue]:0:textDirectory ];
    return self;

}


/*	BROWSER DELEGATE METHODS
 */


- (int)browser:sender fillMatrix:matrix inColumn:(int)column
/* This delegate method goes out to the text 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(textDirectory, 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;
}


/* INTERNAL ROUTINES TO HANDLE FILE OPERATIONS */

#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(void *arg1, 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);
}






/* Methods to load separate nib sections: help panel, info panel	*/

- help:sender
{
	if (helpPanel == NULL) {
     	helpPanel = LoadLocalNib("Help.nib", self);
  	}
	return[helpPanel makeKeyAndOrderFront:sender];
  
}

- info:sender
{
	
	if (infoPanel == NULL) {
    	infoPanel = LoadLocalNib("Info.nib", self);
  	}
	return [infoPanel makeKeyAndOrderFront:sender];
   
  
}


/* window support */

- windowWillResize:sender toSize:(NXSize *)frameSize
{
	/* Limit the height resizing to the initial height to preserve 
	 * data in splitview. Limit its width to max twice its original width.
	 */
    if (frameSize->width < 490.0) {
        frameSize->width = 490.0;
    }
	if (frameSize->width > 1000.0) {
		frameSize->width = 1000.0;
	}
	
	if (frameSize->height < windowFrame.size.height ) {
        frameSize->height = windowFrame.size.height;
    }
    
    return self;
}

- windowWillClose: sender
{
	
    if (sender == infoPanel)
        infoPanel = NULL;
	else if (sender == helpPanel)
		helpPanel = NULL;
    return self;
}



@end