//
//  MPT_AUAppDelegate.m
//  AUCoderDemo
//
//  Created by Andrew Salamon on 5/31/06.
//  Copyright 2006 Machine Perception Laboratory, University of California San Diego. All rights reserved.
//

#import <Carbon/Carbon.h>

#import <QTKit/QTMovie.h>

#import "MPT_AUAppDelegate.h"
#import "MPT_CERT.h"
#import "MPT_Pair.h"
#import "MPT_FaceView.h"
#import "MPT_Face.h"
#import "MPT_AUView.h"
#import "MPT_ResolveAlias.h"
#import "MPT_RimageAdaptor.h"

#import "MPT_Preferences.h"
#import "MPT_PluginController.h"
#import "MPT_PluginStepTransformer.h"

#import "MPT_DataRecorder.h"
#import "MPT_SplitViewConstrainer.h"
#import "MPT_DataLoader.h"

#import "TestController.h"

#ifdef DEMO
#import "MPT_Demo.h"
#endif

@interface MPT_AUAppDelegate(PrivateMethods)
- (void)handleResults:(NSImage *)image;
- (BOOL)loading;
- (void)setLoading:(BOOL)value;

- (void)captureFaces:(NSArray *)faces inImage:(NSImage *)image;

- (void)processMovieAtPath:(NSString *)path;
- (void)processFolderStep;
- (void)setIsProcessing:(BOOL)ignored;
@end

@implementation MPT_AUAppDelegate
+ (void)initialize
{
	// Register our plugin step name transformer
	MPT_PluginStepTransformer *transformer = [[[MPT_PluginStepTransformer alloc] init] autorelease];
	[NSValueTransformer setValueTransformer:transformer forName:@"MPT_PluginStepTransformer"];
}

- (void)awakeFromNib
{	
	// Restrict the image view half of the splitter to at least 400 pixels wide,
	// and the AUView side to at least 216 pixels wide (one AUView wide).
	[splitViewDelegate setMinLeftPane:400];
	[splitViewDelegate setMinRightPane:216];
	
	pluginController = [MPT_PluginController defaultController];
	[pluginController setTileView:tileView];
	[pluginController initPlugins];

	cert = [[MPT_CERT alloc] init];
	[cert pluginStateHasChanged:nil]; // The pluginController already called this, but since cert didn't exist, yet, we need to recall it
	[pluginController setCert:cert];

	videoController = [[TestController alloc] init];
	[imageView resetFaceAndEyes];
	[imageView setFaceType:[[NSUserDefaults standardUserDefaults] integerForKey:@"FaceDisplayType"]];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(faceDisplayTypeChanged:) name:faceDisplayTypeNotification object:nil];
	
	[self setFindFaces:[cert findFaces]];
	[self setLoading:YES];
	[self setLinearCombo:[cert linearCombo]];
	[self setVideoWidth:[videoController videoWidth]];
	[self setVideoHeight:[videoController videoHeight]];
	nextFace = 1;
	[self setLastCapture:[NSDate distantPast]];
	
    [self setEyefinderOffset:[cert eyefinderOffset]];
    [self setEyefinderDropoff:[cert eyefinderDropoff]];
	
	rimageTypes = [[NSArray alloc] initWithObjects:@"rimage", @"rimage_bin", @"rimage_xml", nil];
	movieTypes = [[QTMovie movieFileTypes:QTIncludeCommonTypes] retain];
	imageTypes = [[NSImage imageFileTypes] retain];
	allTypes = [rimageTypes arrayByAddingObjectsFromArray:movieTypes];
	allTypes = [[allTypes arrayByAddingObjectsFromArray:imageTypes] retain];
	
#ifdef DEMO
	MPT_Demo *demo = [MPT_Demo sharedDemo];
	if( [demo isDemo] )
	{
		if( [demo hasDemoEnded] )
		{
			[demo notifyUserDemoEnded];
			[NSApp terminate:self];
		}
	}
	else
	{
		if( ![demo startDemo] )
		{
			[demo notifyUserDemoNotAllowed];
			[NSApp terminate:self];
		}
	}
	
	// If we were already started and are still in the demo period,
	// or if we successfully started a demo period then notify the user how much time they have left.
	[demo notifyUserDemoLeft];
#else
	// If this isn't a demo version of CERT, then show the data window.
	[dataWindow orderBack:self];
#endif
	
	[dataRecorder setCert:cert];
}

- (void)dealloc
{
	[cert release];
	[videoController release];
	[rimageTypes release];
	[movieTypes release];
	[imageTypes release];
	[allTypes release];
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[super dealloc];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
	BOOL optKeyDown = ((GetCurrentKeyModifiers() & (optionKey |  rightOptionKey)) != 0) ? YES : NO;

	[[self window] makeKeyAndOrderFront:self];
	
	// Don't load the kernels if the option key is down.
	if( !optKeyDown )
	{
		MPT_DataLoader *loader = [[MPT_DataLoader alloc] initWithDelegate:self]; // deallocated in loaderFinished:.
		[progress setMinValue:1.0];
		[progress setMaxValue:[loader weightsFileCount]];
		[progress setDoubleValue:1.0];
		[progress setIndeterminate:NO];
		[NSThread detachNewThreadSelector:@selector(loadAllWeights) toTarget:loader withObject:nil];
	}
	else
	{
		[self setLoading:NO];
	}
}

// Callbacks for handling progress and errors from the weights loader
- (void)incrementWeightsProgress
{
	[dataLoading incrementBy:1.0];
	[dataLoading displayIfNeeded];
}
- (void)loaderFinished:(MPT_DataLoader *)loader
{
	BOOL hadErrors = NO;
	NSEnumerator *errorEnum = [[loader errors] objectEnumerator];
	id<MPT_PluginProtocol> plugin;
	while( plugin = [errorEnum nextObject] )
	{
		NSString *pluginName = [plugin localizedName];
		NSString *msg = [NSString stringWithFormat:@"Unable to load all '%@' weights files.", pluginName];
		
		[plugin setEnabled:NO];

		[[NSAlert alertWithMessageText:msg
						 defaultButton:@"OK"
					   alternateButton:nil
						   otherButton:nil
			 informativeTextWithFormat:@"The '%@' plugin will not work correctly and has been disabled.", pluginName] runModal];
		hadErrors = YES;
	}

	if( hadErrors )
		[tileView setNeedsDisplay:YES];
	
	[self setLoading:NO];
	
	[loader release];
}

- (NSWindow *)window
{
	return window;
}

- (IBAction)interrupt:(id)sender
{
	interrupt = YES;
	[pluginController performSelector:@selector(resetViews) withObject:nil afterDelay:0.1];
}

- (IBAction)chooseSource:(id)sender
{
	NSOpenPanel *panel = [NSOpenPanel openPanel];
	[panel setCanChooseDirectories:YES];
	[panel setAllowsMultipleSelection:YES];
	[panel setCanChooseFiles:YES];
	[panel setMessage:@"Choose files and/or directories to process"];
	[panel setAccessoryView:openPanelAccessory];
	
	[cert setInputIsFaces:NO];
	
	[panel beginSheetForDirectory:nil
							 file:nil
							types:allTypes
				   modalForWindow:[self window]
					modalDelegate:self
				   didEndSelector:@selector(openSourcePanelDidEnd:returnCode:contextInfo:)
					  contextInfo:nil];
}

- (void)openSourcePanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode  contextInfo:(void *)contextInfo
{
	if( returnCode == NSOKButton )
	{
		NSArray *filenames = [panel filenames];
		if( [filenames count] > 0 )
		{
			NSString *path = [filenames objectAtIndex:0];
			if( [path length] > 0 )
			{
				interrupt = NO;
				sourcePaths = [[panel filenames] retain];
				sourceEnum = [[sourcePaths objectEnumerator] retain];
				[self setIsProcessing:YES];
				[self performSelector:@selector(processSourceStep) withObject:nil afterDelay:0.01];		
			}
		}
	}
}

- (void)processSourceStep
{
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSString *sourcePath;
	
	if( !interrupt && (sourcePath = [sourceEnum nextObject]) )
	{
		sourcePath = [[[sourcePath stringByStandardizingPath] stringByResolvingSymlinksInPath] stringByResolvingAliases];
		NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:sourcePath traverseLink:YES];
		
		if( [[fileAttributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory] )
		{
			folderPath = [sourcePath retain];
			imagesInFolder = [[[NSFileManager defaultManager] directoryContentsAtPath:sourcePath] retain];
			imageFolderEnum = [[imagesInFolder objectEnumerator] retain];
			[self processFolderStep];
			// processFolderStep will return to this method when it's done.
		}
		else if( [[sourcePath pathExtension] hasPrefix:@"rimage"] )
		{
			[cert setDoingSequence:NO];
			[self imageChanged:sourcePath];
			[self performSelector:@selector(processSourceStep) withObject:nil afterDelay:0.01];		
		}
		else if( [imageTypes containsObject:[sourcePath pathExtension]] )
		{
			NSImage *image = [[NSImage alloc] initWithContentsOfFile:sourcePath];
			if( image )
			{
				[cert setDoingSequence:YES];
				[self imageChanged:[MPT_Pair pairWith:image and:sourcePath]];
				[image release];
			}
			[self performSelector:@selector(processSourceStep) withObject:nil afterDelay:0.01];		
		}
		else if( [movieTypes containsObject:[sourcePath pathExtension]] )
		{
			[self processMovieAtPath:sourcePath];		
			// processMovieAtPath will return to this method when it's done.
		}
		else
		{
			NSLog( @"Skipping file of unknown type: %@", sourcePath );
		}
	}
	else
	{
		[sourcePaths release];
		[sourceEnum release];
		sourcePaths = nil;
		sourceEnum = nil;
		[self setIsProcessing:NO];
	}
}

- (void)processFolderStep
{
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSString *imageName;

	if( !interrupt && (imageName = [imageFolderEnum nextObject]) )
	{
		NSString *imagePath = [[[[folderPath stringByAppendingPathComponent:imageName] stringByStandardizingPath] stringByResolvingSymlinksInPath] stringByResolvingAliases];
		NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:imagePath traverseLink:YES];

		if( [[fileAttributes objectForKey:NSFileType] isEqualToString:NSFileTypeRegular]
			|| [[fileAttributes objectForKey:NSFileType] isEqualToString:NSFileTypeSymbolicLink] )
		{
			NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath];
			if( image )
			{
				[cert setDoingSequence:YES];
				[self imageChanged:[MPT_Pair pairWith:image and:imagePath]];
				[image release];
			}
			else
			{
				if( [[imagePath pathExtension] isEqualToString:@"rimage"] )
				{
					[cert setDoingSequence:YES];
					[self imageChanged:imagePath];
				}
			}			
		}
		[self performSelector:@selector(processFolderStep) withObject:nil afterDelay:0.01];
	}
	else
	{
		[folderPath release];
		[imagesInFolder release];
		[imageFolderEnum release];
		folderPath = nil;
		imagesInFolder = nil;
		imageFolderEnum = nil;
		
		// We finished with this folder, but there may be more files to do, so...
		[self processSourceStep];
	}
}

- (void)processMovieAtPath:(NSString *)path
{
	NSError *error = nil;
	movie = [QTMovie movieWithFile:path error:&error];
	
	if( error )
	{
		NSAlert *alert = [NSAlert alertWithError:error];
		[alert runModal];
		movie = nil;
		return;
	}
	else
	{
		moviePath = [path retain];
		[movie retain];
	}

	[movie gotoEnd];
	endTime = [movie currentTime];
	[movie gotoBeginning];
	movieFrame = 1;
	[cert setDoingSequence:YES];
	[self imageChanged:[MPT_Pair pairWith:[movie currentFrameImage] and:[NSString stringWithFormat:@"%@:%d", moviePath, movieFrame]]];
	[self performSelector:@selector(processMovieStep) withObject:nil afterDelay:0.0];
}
- (void)processMovieStep
{
	[movie stepForward];
	++movieFrame;
	
	QTTime currentTime = [movie currentTime];
	if( interrupt || (NSOrderedDescending != QTTimeCompare( endTime, currentTime )) )
	{
		[movie release];
		movie = nil;
		[moviePath release];
		moviePath = nil;
		// return to the source loop in case there are more items to process.
		[self performSelector:@selector(processSourceStep) withObject:nil afterDelay:0.01];
		return;
	}

	[self imageChanged:[MPT_Pair pairWith:[movie currentFrameImage] and:[NSString stringWithFormat:@"%@:%d", moviePath, movieFrame]]];
	[self performSelector:@selector(processMovieStep) withObject:nil afterDelay:0.0];
}

- (BOOL)displayGray
{
    return [cert displayGray];
}

- (BOOL)doFilters
{
    return [cert doFilters];
}

- (void)setDoFilters:(BOOL)value
{
	// If we need ot add the expression filters back in, here is where we might need to update the minimum and maximum
	// values for displaying AU data.
	// e.g. unfiltered: [AU1 setMin:0.0]; [AU1 setMax:1.0];   vs filtered: [AU1 setMin:-0.5]; [AU1 setMax:0.5];
}

- (BOOL)captureFaces
{
	return [[NSUserDefaults standardUserDefaults] boolForKey:@"captureFaces"];
}

- (BOOL)linearCombo
{
    return [cert linearCombo];
}
- (void)setLinearCombo:(BOOL)value
{
	[cert setLinearCombo:value];
}

- (NSDate *)lastCapture
{
    return lastCapture;
}

- (void)setLastCapture:(NSDate *)value
{
    if( lastCapture != value )
    {
        [lastCapture release];
        lastCapture = [value retain];
    }
}

- (BOOL)multipleRotations
{
    return [cert multipleRotations];
}

- (BOOL)findFaces
{
	return [cert findFaces];
}
- (void)setFindFaces:(BOOL)value
{
	[cert setFindFaces:value];
	if( !value )
		[imageView resetFaceAndEyes];
}

- (float)videoWidth {
    return [videoController videoWidth];
}

- (void)setVideoWidth:(float)value {
	[videoController setVideoWidth:value];
}

- (float)videoHeight {
	return [videoController videoHeight];
}

- (void)setVideoHeight:(float)value {
	[videoController setVideoHeight:value];
}

- (BOOL)loading
{
    return loading;
}

- (void)setLoading:(BOOL)value
{
    if (loading != value)
	{
        loading = value;
		[self setIsProcessing:value];
    }
}

- (float)eyefinderOffset
{
    return [cert eyefinderOffset];
}

- (void)setEyefinderOffset:(float)value
{
    [cert setEyefinderOffset:value];
}

- (float)eyefinderDropoff
{
    return [cert eyefinderDropoff];
}

- (void)setEyefinderDropoff:(float)value
{
    [cert setEyefinderDropoff:value];
}

- (IBAction)restoreEyefinderDefaults:(id)sender
{
	[self setEyefinderOffset:[cert defaultEyefinderOffset]];
	[self setEyefinderDropoff:[cert defaultEyefinderDropoff]];
}

- (BOOL)isProcessing;
{
	if( loading ) return NO;
	if( sourceEnum ) return NO;
	if( videoTimer ) return NO;
    return YES;
}
- (void)setIsProcessing:(BOOL)ignored
{
	
}

- (MPT_CERT *)cert
{
    return cert;
}

- (IBAction)imageChanged:(id)sender
{
	BOOL haveRImage = NO;
	NSImage *image = NULL;
	NSString *imagePath = NULL;
	if( [sender isKindOfClass:[NSImage class]] )
	{
		image = (NSImage *)sender;
	}
	else if( [sender isKindOfClass:[MPT_Pair class]] )
	{
		MPT_Pair *pair = (MPT_Pair *)sender; 
		image = [pair first];
		imagePath = [pair second];
	}
	else if( [sender isKindOfClass:[MPT_FaceView class]] )
	{	
		image = [imageView image];
		imagePath = [sender lastDropPath];
	}
	else if( [sender isKindOfClass:[NSString class]] )
	{
		haveRImage = YES;
		image = NULL;
		imagePath = sender;
	}
	else
		return;
	
	if( imagePath )
	{
		[imageView setLastDropPath:imagePath];
		[fileNameField setStringValue:imagePath];
	}
	
	[leftField setStringValue:@""];
	[rightField setStringValue:@""];
	[imageView resetFaceAndEyes];
	
	[progress startAnimation:self];

	if( haveRImage )
		[cert processRImageFile:imagePath];
	else
		[cert processImage:image];
	
	if( [cert displayGray] || haveRImage )
	{
		[self handleResults:[cert grayImage]];
	}
	else
	{
		[self handleResults:image];
	}
	[progress stopAnimation:self];
}

- (void)handleResults:(NSImage *)image
{
	// Get results for all plugins and update their views.
	NSDictionary *pluginResults = [cert pluginResults];
	NSEnumerator *pluginEnum = [[pluginController plugins] objectEnumerator];
	id<MPT_PluginProtocol> plugin;
	while( plugin = [pluginEnum nextObject] )
	{
		NSEnumerator *labelEnum = [[plugin labels] objectEnumerator];
		NSString *label;
		while( label = [labelEnum nextObject] )
		{
			MPT_AUView *view = [plugin viewForLabel:label];
			if( [plugin enabled] )
			{
				NSNumber *resNum = [pluginResults objectForKey:[plugin pluginID]];
				if( !resNum )
				{
					[view addNoValue];
				}
				else if( [resNum isKindOfClass:[NSNumber class]] )
				{
					double res = [resNum doubleValue];
					[view addValue:res];
				}
				else if( [resNum isKindOfClass:[NSDictionary class]] )
				{
					double res = [[(NSDictionary *)resNum objectForKey:label] doubleValue];
					[view addValue:res];
				}
				else
				{
					NSLog( @"Unknown type in plugin results" );
					[view addNoValue];
				}
			}
			else
			{
				[view addNoValue];
			}
		}
	}
		
	if( [cert left].x > -1 )
		[leftField setStringValue:NSStringFromPoint( [cert left] )];
	if( [cert right].x > -1 )
		[rightField setStringValue:NSStringFromPoint( [cert right] )];
	[imageView setFaces:[cert faces]];
	
	NSImage *gray = NULL; // [cert grayImage];
	if( gray )
		[imageView setImage:gray];
	else
		[imageView setImage:image];	
	
	[dataRecorder recordDataPoint];
	
	if( [self captureFaces] )
	{
		if( [[cert faces] count] > 0 )
		{
			// Capture images at most once per second
			NSTimeInterval diff = [[self lastCapture] timeIntervalSinceNow];
			if( diff < -1.0 )
			{
				[self captureFaces:[cert faces] inImage:image];
				[self setLastCapture:[NSDate date]];
			}
		}
	}
}

- (IBAction)startVideo:(id)sender
{
	if( videoTimer )
	{
		[self stopVideo:self];
	}
	else
	{
		interrupt = NO;
		videoTimer = YES;
		[self setIsProcessing:YES];
		[fileNameField setStringValue:@""];
		[progress startAnimation:self];
		[imageView resetFaceAndEyes];
		[videoController start];
		[cert setDoingSequence:YES];
		if( ![videoController cameraStarted] )
		{
			[progress stopAnimation:self];
			[videoController stop];
			videoTimer = NO;
			[self setIsProcessing:NO];
			NSRunAlertPanel( @"Video Error", @"No video source was found, or an error was encountered while trying to access the video.", @"OK", nil, nil );
		}
		else
		{
			[NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(videoTimerFired:) userInfo:NULL repeats:NO];
		}
	}
}

- (IBAction)stopVideo:(id)sender
{
	[progress stopAnimation:self];
	[videoController stop];
	videoTimer = NO;
	[self setIsProcessing:NO];
	[cert setDoingSequence:NO];
}

- (void)videoTimerFired:(NSTimer *)timer
{
	if( interrupt )
		[self stopVideo:self];
	
	if( videoTimer )
	{
		NSImage *vImage = (NSImage *)[videoController lastImage];
		MPT_RimageAdaptor *adaptor = [videoController lastRimage];
		NSError *error = [videoController lastError];
		
		if( error )
		{
			// If the error code is -2211, then suggest the user unplug and replug the camera.
			NSAlert *theAlert = [NSAlert alertWithError:error];
			int button = [theAlert runModal];
			if( button != NSAlertFirstButtonReturn )
			{
				// handle
			}
			[self startVideo:self];
		}
		
		if( vImage )
		{
			[vImage retain];
			if( [cert findFaces] )
			{
				if( adaptor )
					[cert processRImageAdaptor:adaptor];
				else
					[cert processImage:vImage];

				if( [cert displayGray] )
					[self handleResults:[cert grayImage]];
				else
					[self handleResults:vImage];
			}
			else
			{
				[imageView setImage:vImage];	
			}
			[frameRateField setFloatValue:[videoController frameRate]];
			[vImage release];
		}
		[NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(videoTimerFired:) userInfo:NULL repeats:NO];
	}
	else
	{
		[progress stopAnimation:self];
	}
}

- (void)captureFaces:(NSArray *)faces inImage:(NSImage *)image
{
	unsigned int captureNumFaces = 20;
	NSString *directory = @"~/Sites/Faces"; // preference
	NSString *filename = [NSString stringWithFormat:@"CapturedFace%d.jpg", nextFace];
	NSString *dest = [[directory stringByAppendingPathComponent:filename] stringByStandardizingPath];
	
	NSBitmapImageRep *imageRep = NULL;
	
	BOOL captureWithFaceBox = NO;
	if( captureWithFaceBox )
	{
		MPT_Face *firstFace = [faces objectAtIndex:0];
		NSRect faceRect = [firstFace face];
		image = [[image copy] autorelease];
		NSSize size = [image size]; 
		[image lockFocus];
		[[NSColor blueColor] setFill];
		[[NSColor blueColor] setStroke];
		faceRect.origin.y = size.height - faceRect.origin.y - faceRect.size.height;
		NSFrameRect(faceRect);
		imageRep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:
			NSMakeRect(0,0,size.width,size.height)] autorelease];
		[image unlockFocus];
	}
	else
	{
		NSArray *reps = [image representations];
		NSEnumerator *repEnum = [reps objectEnumerator];
		NSBitmapImageRep *locRep = NULL;
		while( (locRep = [repEnum  nextObject]) && ![locRep isKindOfClass:[NSBitmapImageRep class]] )
		{
			
		}

		if( [locRep isKindOfClass:[NSBitmapImageRep class]] )
			imageRep = locRep;
	}
	
	if( imageRep )
	{
		// save as JPEG
		NSDictionary *properties =
        [NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithFloat:0.85],
			NSImageCompressionFactor, NULL];    
		
		NSData *bitmapData = [imageRep representationUsingType:NSJPEGFileType properties:properties];
		
		if( bitmapData )
		{
			if( [bitmapData writeToFile:dest atomically:YES] )
			{
				++nextFace;
				if( nextFace > captureNumFaces )
					nextFace = 1;
			}
		}
	}
}

- (IBAction)showPreferences:(id)sender
{
	if( !preferences )
	{
		preferences = [[MPT_Preferences alloc] initWithWindowNibName:@"MPTPrefs"];
		[preferences showWindow:sender];
	}
	else
	{
		[[preferences window] makeKeyAndOrderFront:sender];
	}
}

- (void)displayAboutPanel:(id)sender
{
	NSBundle *certBundle = [NSBundle bundleWithIdentifier:@"edu.ucsd.mplab.CERTFramework"];
	NSString *mptVersion = [certBundle objectForInfoDictionaryKey:@"MPTVersion"];
	NSString *certVersion = [certBundle objectForInfoDictionaryKey:@"CERTVersion"];

//	int rev = getCertRevision();
////	char *revDate = getCertRevisionDate();
//	const char *mptRev = getMPTRevisionString();
	NSString *demoString = @"";
#ifdef DEMO
	demoString = @" Demo";
#endif
	NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"build %@::%@%@", mptVersion, certVersion, demoString], @"Version", nil]; // , @"1.0", @"ApplicationVersion", nil
	[NSApp orderFrontStandardAboutPanelWithOptions:options];
//	[NSApp orderFrontStandardAboutPanel:sender];
}

- (void)faceDisplayTypeChanged:(NSNotification *)notification
{
	[imageView setFaceType:[[NSUserDefaults standardUserDefaults] integerForKey:@"FaceDisplayType"]];
}
@end
