//
//  SRRecorderCell.m
//  ShortcutRecorder
//
//  Copyright 2006-2007 Contributors. All rights reserved.
//
//  License: BSD
//
//  Contributors:
//      David Dauer
//      Jesper
//      Jamie Kirkpatrick

#import "SRRecorderCell.h"
#import "SRRecorderControl.h"
#import "SRKeyCodeTransformer.h"
#import "SRValidator.h"

@interface SRRecorderCell (Private)
- (void)_privateInit;
- (void)_createGradient;
- (void)_setJustChanged;
- (void)_startRecordingTransition;
- (void)_endRecordingTransition;
- (void)_transitionTick;
- (void)_startRecording;
- (void)_endRecording;

- (BOOL)_effectiveIsAnimating;
- (BOOL)_supportsAnimation;

- (NSString *)_defaultsKeyForAutosaveName:(NSString *)name;
- (void)_saveKeyCombo;
- (void)_loadKeyCombo;

- (NSRect)_removeButtonRectForFrame:(NSRect)cellFrame;
- (NSRect)_snapbackRectForFrame:(NSRect)cellFrame;

- (NSUInteger)_filteredCocoaFlags:(NSUInteger)flags;
- (NSUInteger)_filteredCocoaToCarbonFlags:(NSUInteger)cocoaFlags;
- (BOOL)_validModifierFlags:(NSUInteger)flags;

- (BOOL)_isEmpty;
@end

#pragma mark -

@implementation SRRecorderCell

- (id)init
{
    self = [super init];
	
	[self _privateInit];
	
    return self;
}

- (void)dealloc
{
    [validator release];
	
	[keyCharsIgnoringModifiers release];
	[keyChars release];
    
	[recordingGradient release];
	[autosaveName release];
	
	[cancelCharacterSet release];
	
	[super dealloc];
}

#pragma mark *** Coding Support ***

- (id)initWithCoder:(NSCoder *)aDecoder
{
	self = [super initWithCoder: aDecoder];
	
	[self _privateInit];

	if ([aDecoder allowsKeyedCoding]) {
		autosaveName = [[aDecoder decodeObjectForKey: @"autosaveName"] retain];
		
		keyCombo.code = [[aDecoder decodeObjectForKey: @"keyComboCode"] shortValue];
		keyCombo.flags = [[aDecoder decodeObjectForKey: @"keyComboFlags"] unsignedIntegerValue];
		
		if ([aDecoder containsValueForKey:@"keyChars"]) {
			hasKeyChars = YES;
			keyChars = (NSString *)[aDecoder decodeObjectForKey: @"keyChars"];
			keyCharsIgnoringModifiers = (NSString *)[aDecoder decodeObjectForKey: @"keyCharsIgnoringModifiers"];
		}

		allowedFlags = [[aDecoder decodeObjectForKey: @"allowedFlags"] unsignedIntegerValue];
		requiredFlags = [[aDecoder decodeObjectForKey: @"requiredFlags"] unsignedIntegerValue];
		
		allowsKeyOnly = [[aDecoder decodeObjectForKey:@"allowsKeyOnly"] boolValue];
		escapeKeysRecord = [[aDecoder decodeObjectForKey:@"escapeKeysRecord"] boolValue];
		isAnimating = [[aDecoder decodeObjectForKey:@"isAnimating"] boolValue];
		
		style = [[aDecoder decodeObjectForKey:@"style"] shortValue];
	} else {
		autosaveName = [[aDecoder decodeObject] retain];
		
		keyCombo.code = [[aDecoder decodeObject] shortValue];
		keyCombo.flags = [[aDecoder decodeObject] unsignedIntegerValue];
		
		allowedFlags = [[aDecoder decodeObject] unsignedIntegerValue];
		requiredFlags = [[aDecoder decodeObject] unsignedIntegerValue];
	}
	
	allowedFlags |= NSFunctionKeyMask;

	[self _loadKeyCombo];

	return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
	[super encodeWithCoder: aCoder];
	
	if ([aCoder allowsKeyedCoding]) {
		[aCoder encodeObject:[self autosaveName] forKey:@"autosaveName"];
		[aCoder encodeObject:[NSNumber numberWithShort: keyCombo.code] forKey:@"keyComboCode"];
		[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:keyCombo.flags] forKey:@"keyComboFlags"];
	
		[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:allowedFlags] forKey:@"allowedFlags"];
		[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:requiredFlags] forKey:@"requiredFlags"];
		
		if (hasKeyChars) {
			[aCoder encodeObject:keyChars forKey:@"keyChars"];
			[aCoder encodeObject:keyCharsIgnoringModifiers forKey:@"keyCharsIgnoringModifiers"];
		}
		
		[aCoder encodeObject:[NSNumber numberWithBool: allowsKeyOnly] forKey:@"allowsKeyOnly"];
		[aCoder encodeObject:[NSNumber numberWithBool: escapeKeysRecord] forKey:@"escapeKeysRecord"];
		
		[aCoder encodeObject:[NSNumber numberWithBool: isAnimating] forKey:@"isAnimating"];
		[aCoder encodeObject:[NSNumber numberWithShort:style] forKey:@"style"];
	} else {
		// Unkeyed archiving and encoding is deprecated and unsupported. Use keyed archiving and encoding.
		[aCoder encodeObject: [self autosaveName]];
		[aCoder encodeObject: [NSNumber numberWithShort: keyCombo.code]];
		[aCoder encodeObject: [NSNumber numberWithUnsignedInteger: keyCombo.flags]];
		
		[aCoder encodeObject: [NSNumber numberWithUnsignedInteger:allowedFlags]];
		[aCoder encodeObject: [NSNumber numberWithUnsignedInteger:requiredFlags]];
	}
}

- (id)copyWithZone:(NSZone *)zone
{
    SRRecorderCell *cell;
    cell = (SRRecorderCell *)[super copyWithZone: zone];
	
	cell->recordingGradient = [recordingGradient retain];
	cell->autosaveName = [autosaveName retain];

	cell->isRecording = isRecording;
	cell->mouseInsideTrackingArea = mouseInsideTrackingArea;
	cell->mouseDown = mouseDown;

	cell->removeTrackingRectTag = removeTrackingRectTag;
	cell->snapbackTrackingRectTag = snapbackTrackingRectTag;

	cell->keyCombo = keyCombo;

	cell->allowedFlags = allowedFlags;
	cell->requiredFlags = requiredFlags;
	cell->recordingFlags = recordingFlags;
	
	cell->allowsKeyOnly = allowsKeyOnly;
	cell->escapeKeysRecord = escapeKeysRecord;
	
	cell->isAnimating = isAnimating;
	
	cell->style = style;

	cell->cancelCharacterSet = [cancelCharacterSet retain];
    
	cell->delegate = delegate;
	
    return cell;
}

#pragma mark *** Drawing ***

+ (BOOL)styleSupportsAnimation:(SRRecorderStyle)style {
	return (style == SRGreyStyle);
}

- (BOOL)animates {
	return isAnimating;
}

- (void)setAnimates:(BOOL)an {
	isAnimating = an;
}

- (SRRecorderStyle)style {
	return style;
}

- (void)setStyle:(SRRecorderStyle)nStyle {
	switch (nStyle) {
		case SRGreyStyle:
			style = SRGreyStyle;
			break;
		case SRGradientBorderStyle:
		default:
			style = SRGradientBorderStyle;
			break;
	}
}

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
	CGFloat radius = 0;

	if (style == SRGradientBorderStyle) {
		
		NSRect whiteRect = cellFrame;
		NSBezierPath *roundedRect;
		
	// Draw gradient when in recording mode
		if (isRecording)
		{
			radius = NSHeight(cellFrame) / 2.0f;
			roundedRect = [NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius:radius yRadius:radius];
			
		// Fill background with gradient
			[[NSGraphicsContext currentContext] saveGraphicsState];
			[roundedRect addClip];
			[recordingGradient drawInRect:cellFrame angle:90.0f];
			[[NSGraphicsContext currentContext] restoreGraphicsState];
			
		// Highlight if inside or down
			if (mouseInsideTrackingArea)
			{
				[[[NSColor blackColor] colorWithAlphaComponent: (mouseDown ? 0.4f : 0.2f)] set];
				[roundedRect fill];
			}
			
		// Draw snapback image
			NSImage *snapBackArrow = SRResIndImage(@"SRSnapback");	
			[snapBackArrow dissolveToPoint:[self _snapbackRectForFrame: cellFrame].origin fraction:1.0f];
			
		// Because of the gradient and snapback image, the white rounded rect will be smaller
			whiteRect = NSInsetRect(cellFrame, 9.5f, 2.0f);
			whiteRect.origin.x -= 7.5f;
		}
		
	// Draw white rounded box
		radius = NSHeight(whiteRect) / 2.0f;
		roundedRect = [NSBezierPath bezierPathWithRoundedRect:whiteRect xRadius:radius yRadius:radius];
		[[NSGraphicsContext currentContext] saveGraphicsState];
		[roundedRect addClip];
		[[NSColor whiteColor] set];
		[NSBezierPath fillRect: whiteRect];
		
	// Draw border and remove badge if needed
		if (!isRecording)
		{
			[[NSColor windowFrameColor] set];
			[roundedRect stroke];
			
		// If key combination is set and valid, draw remove image
			if (![self _isEmpty] && [self isEnabled])
			{
				NSString *removeImageName = [NSString stringWithFormat: @"SRRemoveShortcut%@", (mouseInsideTrackingArea ? (mouseDown ? @"Pressed" : @"Rollover") : (mouseDown ? @"Rollover" : @""))];
				NSImage *removeImage = SRResIndImage(removeImageName);
				[removeImage dissolveToPoint:[self _removeButtonRectForFrame: cellFrame].origin fraction:1.0f];
			}
		}
		
		[[NSGraphicsContext currentContext] restoreGraphicsState];
		
	// Draw text
		NSMutableParagraphStyle *mpstyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
		[mpstyle setLineBreakMode: NSLineBreakByTruncatingTail];
		[mpstyle setAlignment: NSCenterTextAlignment];
		
	// Only the KeyCombo should be black and in a bigger font size
		BOOL recordingOrEmpty = (isRecording || [self _isEmpty]);
		NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: mpstyle, NSParagraphStyleAttributeName,
			[NSFont systemFontOfSize: (recordingOrEmpty ? [NSFont labelFontSize] : [NSFont smallSystemFontSize])], NSFontAttributeName,
			(recordingOrEmpty ? [NSColor disabledControlTextColor] : [NSColor blackColor]), NSForegroundColorAttributeName, 
			nil];
		
		NSString *displayString;
		
		if (isRecording)
		{
		// Recording, but no modifier keys down
			if (![self _validModifierFlags: recordingFlags])
			{
				if (mouseInsideTrackingArea)
				{
				// Mouse over snapback
					displayString = SRLoc(@"Use old shortcut");
				}
				else
				{
				// Mouse elsewhere
					displayString = SRLoc(@"Type shortcut");
				}
			}
			else
			{
			// Display currently pressed modifier keys
				displayString = SRStringForCocoaModifierFlags( recordingFlags );
				
			// Fall back on 'Type shortcut' if we don't have modifier flags to display; this will happen for the fn key depressed
				if (![displayString length])
				{
					displayString = SRLoc(@"Type shortcut");
				}
			}
		}
		else
		{
		// Not recording...
			if ([self _isEmpty])
			{
				displayString = SRLoc(@"Click to record shortcut");
			}
			else
			{
			// Display current key combination
				displayString = [self keyComboString];
			}
		}
		
	// Calculate rect in which to draw the text in...
		NSRect textRect = cellFrame;
		textRect.size.width -= 6;
		textRect.size.width -= ((!isRecording && [self _isEmpty]) ? 6 : (isRecording ? [self _snapbackRectForFrame: cellFrame].size.width : [self _removeButtonRectForFrame: cellFrame].size.width) + 6);
		textRect.origin.x += 6;
		textRect.origin.y = -(NSMidY(cellFrame) - [displayString sizeWithAttributes: attributes].height/2);
		
	// Finally draw it
		[displayString drawInRect:textRect withAttributes:attributes];
		
    // draw a focus ring...?
		if ( [self showsFirstResponder] )
		{
			[NSGraphicsContext saveGraphicsState];
			NSSetFocusRingStyle(NSFocusRingOnly);
			radius = NSHeight(cellFrame) / 2.0f;
			[[NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius:radius yRadius:radius] fill];
			[NSGraphicsContext restoreGraphicsState];
		}
		
	} else {
		
//	NSRect rawCellFrame = cellFrame;
		cellFrame = NSInsetRect(cellFrame,0.5f,0.5f);
		
		NSRect whiteRect = cellFrame;
		NSBezierPath *roundedRect;
		
		BOOL isVaguelyRecording = isRecording;
		CGFloat xanim = 0.0f;
		
		if (isAnimatingNow) {
//		NSLog(@"tp: %f; xanim: %f", transitionProgress, xanim);
			xanim = (SRAnimationEaseInOut(transitionProgress));
//		NSLog(@"tp: %f; xanim: %f", transitionProgress, xanim);
		}
		
		CGFloat alphaRecording = 1.0f; CGFloat alphaView = 1.0f;
		if (isAnimatingNow && !isAnimatingTowardsRecording) { alphaRecording = 1.0f - xanim; alphaView = xanim; }
		if (isAnimatingNow && isAnimatingTowardsRecording) { alphaView = 1.0f - xanim; alphaRecording = xanim; }
		
		if (isAnimatingNow) {
			//NSLog(@"animation step: %f, effective: %f, alpha recording: %f, view: %f", transitionProgress, xanim, alphaRecording, alphaView);
		}
		
		if (isAnimatingNow && isAnimatingTowardsRecording) {
			isVaguelyRecording = YES;
		}
		
//	NSAffineTransform *transitionMovement = [NSAffineTransform transform];
		NSAffineTransform *viewportMovement = [NSAffineTransform transform];
	// Draw gradient when in recording mode
		if (isVaguelyRecording)
		{
			if (isAnimatingNow) {
//			[transitionMovement translateXBy:(isAnimatingTowardsRecording ? -(NSWidth(cellFrame)*(1.0-xanim)) : +(NSWidth(cellFrame)*xanim)) yBy:0.0];
				if (SRAnimationAxisIsY) {
//				[viewportMovement translateXBy:0.0 yBy:(isAnimatingTowardsRecording ? -(NSHeight(cellFrame)*(xanim)) : -(NSHeight(cellFrame)*(1.0-xanim)))];
					[viewportMovement translateXBy:0.0f yBy:(isAnimatingTowardsRecording ? NSHeight(cellFrame)*(xanim) : NSHeight(cellFrame)*(1.0f-xanim))];
				} else {
					[viewportMovement translateXBy:(isAnimatingTowardsRecording ? -(NSWidth(cellFrame)*(xanim)) : -(NSWidth(cellFrame)*(1.0f-xanim))) yBy:0.0f];
				}
			} else {
				if (SRAnimationAxisIsY) {
					[viewportMovement translateXBy:0.0f yBy:NSHeight(cellFrame)];				
				} else {
					[viewportMovement translateXBy:-(NSWidth(cellFrame)) yBy:0.0f];
				}
			}
		}
		
		
	// Draw white rounded box
		radius = NSHeight(whiteRect) / 2.0f;
		roundedRect = [NSBezierPath bezierPathWithRoundedRect:whiteRect xRadius:radius yRadius:radius];
		[[NSColor whiteColor] set];
		[[NSGraphicsContext currentContext] saveGraphicsState];
		[roundedRect fill];
		[[NSColor windowFrameColor] set];
		[roundedRect stroke];
		[roundedRect addClip];
		
//	if (isVaguelyRecording) 
		{
			NSRect snapBackRect = SRAnimationOffsetRect([self _snapbackRectForFrame: cellFrame],cellFrame);
//		NSLog(@"snapbackrect: %@; offset: %@", NSStringFromRect([self _snapbackRectForFrame: cellFrame]), NSStringFromRect(snapBackRect));
			NSPoint correctedSnapBackOrigin = [viewportMovement transformPoint:snapBackRect.origin];
			
			NSRect correctedSnapBackRect = snapBackRect;
//		correctedSnapBackRect.origin.y = NSMinY(whiteRect);
			correctedSnapBackRect.size.height = NSHeight(whiteRect);
			correctedSnapBackRect.size.width *= 1.3f;
			correctedSnapBackRect.origin.y -= 5.0f;
			correctedSnapBackRect.origin.x -= 1.5f;
			
			correctedSnapBackOrigin.x -= 0.5f;
			
			correctedSnapBackRect.origin = [viewportMovement transformPoint:correctedSnapBackRect.origin];
			
			NSBezierPath *snapBackButton = [NSBezierPath bezierPathWithRect:correctedSnapBackRect];
			[[[[NSColor windowFrameColor] shadowWithLevel:0.2f] colorWithAlphaComponent:alphaRecording] set];
			[snapBackButton stroke];
//		NSLog(@"stroked along path of %@", NSStringFromRect(correctedSnapBackRect));

			NSGradient *gradient = nil;
			if (mouseDown && mouseInsideTrackingArea) {
				gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.60f alpha:alphaRecording]
														 endingColor:[NSColor colorWithCalibratedWhite:0.75f alpha:alphaRecording]];
			}
			else {
				gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.75f alpha:alphaRecording]
														 endingColor:[NSColor colorWithCalibratedWhite:0.90f alpha:alphaRecording]];
			}
			CGFloat insetAmount = -([snapBackButton lineWidth]/2.0f);
			[gradient drawInRect:NSInsetRect(correctedSnapBackRect, insetAmount, insetAmount) angle:90.0f];
			[gradient release];

			/*
		// Highlight if inside or down
			 if (mouseInsideTrackingArea)
			 {
				 [[[NSColor blackColor] colorWithAlphaComponent: alphaRecording*(mouseDown ? 0.15 : 0.1)] set];
				 [snapBackButton fill];
			 }*/
			
		// Draw snapback image
			NSImage *snapBackArrow = SRResIndImage(@"SRSnapback");
			[snapBackArrow dissolveToPoint:correctedSnapBackOrigin fraction:1.0f*alphaRecording];
		}
		
	// Draw border and remove badge if needed
		/*	if (!isVaguelyRecording)
		{
			*/	
		// If key combination is set and valid, draw remove image
		if (![self _isEmpty] && [self isEnabled])
		{
			NSString *removeImageName = [NSString stringWithFormat: @"SRRemoveShortcut%@", (mouseInsideTrackingArea ? (mouseDown ? @"Pressed" : @"Rollover") : (mouseDown ? @"Rollover" : @""))];
			NSImage *removeImage = SRResIndImage(removeImageName);
			[removeImage dissolveToPoint:[viewportMovement transformPoint:([self _removeButtonRectForFrame: cellFrame].origin)] fraction:alphaView];
			//NSLog(@"drew removeImage with alpha %f", alphaView);
		}
//	}
		
		
		
	// Draw text
		NSMutableParagraphStyle *mpstyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
		[mpstyle setLineBreakMode: NSLineBreakByTruncatingTail];
		[mpstyle setAlignment: NSCenterTextAlignment];
		
		CGFloat alphaCombo = alphaView;
		CGFloat alphaRecordingText = alphaRecording;
		if (comboJustChanged) {
			alphaCombo = 1.0f;
			alphaRecordingText = 0.0f;//(alphaRecordingText/2.0);
		}
		
		
		NSString *displayString;
		
		{
	// Only the KeyCombo should be black and in a bigger font size
			BOOL recordingOrEmpty = (isVaguelyRecording || [self _isEmpty]);
			NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: mpstyle, NSParagraphStyleAttributeName,
				[NSFont systemFontOfSize: (recordingOrEmpty ? [NSFont labelFontSize] : [NSFont smallSystemFontSize])], NSFontAttributeName,
				[(recordingOrEmpty ? [NSColor disabledControlTextColor] : [NSColor blackColor]) colorWithAlphaComponent:alphaRecordingText], NSForegroundColorAttributeName, 
				nil];
		// Recording, but no modifier keys down
			if (![self _validModifierFlags: recordingFlags])
			{
				if (mouseInsideTrackingArea)
				{
				// Mouse over snapback
					displayString = SRLoc(@"Use old shortcut");
				}
				else
				{
				// Mouse elsewhere
					displayString = SRLoc(@"Type shortcut");
				}
			}
			else
			{
			// Display currently pressed modifier keys
				displayString = SRStringForCocoaModifierFlags( recordingFlags );
				
			// Fall back on 'Type shortcut' if we don't have modifier flags to display; this will happen for the fn key depressed
				if (![displayString length])
				{
					displayString = SRLoc(@"Type shortcut");
				}
			}
	// Calculate rect in which to draw the text in...
			NSRect textRect = SRAnimationOffsetRect(cellFrame,cellFrame);
			//NSLog(@"draw record text in rect (preadjusted): %@", NSStringFromRect(textRect));
			textRect.origin.y -= 3.0f;
			textRect.origin = [viewportMovement transformPoint:textRect.origin];
			//NSLog(@"draw record text in rect: %@", NSStringFromRect(textRect));
			
			
			
	// Finally draw it
			[displayString drawInRect:textRect withAttributes:attributes];
		}
		
		
		{
	// Only the KeyCombo should be black and in a bigger font size
			NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: mpstyle, NSParagraphStyleAttributeName,
				[NSFont systemFontOfSize: ([self _isEmpty] ? [NSFont labelFontSize] : [NSFont smallSystemFontSize])], NSFontAttributeName,
				[([self _isEmpty] ? [NSColor disabledControlTextColor] : [NSColor blackColor]) colorWithAlphaComponent:alphaCombo], NSForegroundColorAttributeName, 
				nil];
		// Not recording...
			if ([self _isEmpty])
			{
				displayString = SRLoc(@"Click to record shortcut");
			}
			else
			{
			// Display current key combination
				displayString = [self keyComboString];
			}
	// Calculate rect in which to draw the text in...
			NSRect textRect = cellFrame;
			/*		textRect.size.width -= 6;
			textRect.size.width -= (([self _removeButtonRectForFrame: cellFrame].size.width) + 6);
//		textRect.origin.x += 6;*/
//NSFont *f = [attributes objectForKey:NSFontAttributeName];
//double lineHeight = [[[NSLayoutManager alloc] init] defaultLineHeightForFont:f];
//		textRect.size.height = lineHeight;
			if (!comboJustChanged) {
				//NSLog(@"draw view text in rect (pre-adjusted): %@", NSStringFromRect(textRect));
				textRect.origin = [viewportMovement transformPoint:textRect.origin];
			}
			textRect.origin.y = NSMinY(textRect)-3.0f;// - ((lineHeight/2.0)+([f descender]/2.0));
				
				//NSLog(@"draw view text in rect: %@", NSStringFromRect(textRect));
				
	// Finally draw it
				[displayString drawInRect:textRect withAttributes:attributes];
		}
		
		[[NSGraphicsContext currentContext] restoreGraphicsState];
		
    // draw a focus ring...?
		
		if ( [self showsFirstResponder] )
		{
			[NSGraphicsContext saveGraphicsState];
			NSSetFocusRingStyle(NSFocusRingOnly);
			radius = NSHeight(cellFrame) / 2.0f;
			[[NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius:radius yRadius:radius] fill];
			[NSGraphicsContext restoreGraphicsState];
		}
		
	}
}

#pragma mark *** Mouse Tracking ***

- (void)resetTrackingRects
{	
	SRRecorderControl *controlView = (SRRecorderControl *)[self controlView];
	NSRect cellFrame = [controlView bounds];
	NSPoint mouseLocation = [controlView convertPoint:[[NSApp currentEvent] locationInWindow] fromView:nil];

	// We're not to be tracked if we're not enabled
	if (![self isEnabled])
	{
		if (removeTrackingRectTag != 0) [controlView removeTrackingRect: removeTrackingRectTag];
		if (snapbackTrackingRectTag != 0) [controlView removeTrackingRect: snapbackTrackingRectTag];
		
		return;
	}
	
	// We're either in recording or normal display mode
	if (!isRecording)
	{
		// Create and register tracking rect for the remove badge if shortcut is not empty
		NSRect removeButtonRect = [self _removeButtonRectForFrame: cellFrame];
		BOOL mouseInside = [controlView mouse:mouseLocation inRect:removeButtonRect];
		
		if (removeTrackingRectTag != 0) [controlView removeTrackingRect: removeTrackingRectTag];
		removeTrackingRectTag = [controlView addTrackingRect:removeButtonRect owner:self userData:nil assumeInside:mouseInside];
		
		if (mouseInsideTrackingArea != mouseInside) mouseInsideTrackingArea = mouseInside;
	}
	else
	{
		// Create and register tracking rect for the snapback badge if we're in recording mode
		NSRect snapbackRect = [self _snapbackRectForFrame: cellFrame];
		BOOL mouseInside = [controlView mouse:mouseLocation inRect:snapbackRect];

		if (snapbackTrackingRectTag != 0) [controlView removeTrackingRect: snapbackTrackingRectTag];
		snapbackTrackingRectTag = [controlView addTrackingRect:snapbackRect owner:self userData:nil assumeInside:mouseInside];	
		
		if (mouseInsideTrackingArea != mouseInside) mouseInsideTrackingArea = mouseInside;
	}
}

- (void)mouseEntered:(NSEvent *)theEvent
{
	NSView *view = [self controlView];

	if ([[view window] isKeyWindow] || [view acceptsFirstMouse: theEvent])
	{
		mouseInsideTrackingArea = YES;
		[view display];
	}
}

- (void)mouseExited:(NSEvent*)theEvent
{
	NSView *view = [self controlView];
	
	if ([[view window] isKeyWindow] || [view acceptsFirstMouse: theEvent])
	{
		mouseInsideTrackingArea = NO;
		[view display];
	}
}

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(SRRecorderControl *)controlView untilMouseUp:(BOOL)flag
{		
	NSEvent *currentEvent = theEvent;
	NSPoint mouseLocation;
	
	NSRect trackingRect = (isRecording ? [self _snapbackRectForFrame: cellFrame] : [self _removeButtonRectForFrame: cellFrame]);
	NSRect leftRect = cellFrame;

	// Determine the area without any badge
	if (!NSEqualRects(trackingRect,NSZeroRect)) leftRect.size.width -= NSWidth(trackingRect) + 4;
		
	do {
        mouseLocation = [controlView convertPoint: [currentEvent locationInWindow] fromView:nil];
		
		switch ([currentEvent type])
		{
			case NSLeftMouseDown:
			{
				// Check if mouse is over remove/snapback image
				if ([controlView mouse:mouseLocation inRect:trackingRect])
				{
					mouseDown = YES;
					[controlView setNeedsDisplayInRect: cellFrame];
				}
				
				break;
			}
			case NSLeftMouseDragged:
			{				
				// Recheck if mouse is still over the image while dragging 
				mouseInsideTrackingArea = [controlView mouse:mouseLocation inRect:trackingRect];
				[controlView setNeedsDisplayInRect: cellFrame];
				
				break;
			}
			default: // NSLeftMouseUp
			{
				mouseDown = NO;
				mouseInsideTrackingArea = [controlView mouse:mouseLocation inRect:trackingRect];

				if (mouseInsideTrackingArea)
				{
					if (isRecording)
					{
						// Mouse was over snapback, just redraw
                        [self _endRecordingTransition];
					}
					else
					{
						// Mouse was over the remove image, reset all
						[self setKeyCombo: SRMakeKeyCombo(ShortcutRecorderEmptyCode, ShortcutRecorderEmptyFlags)];
					}
				}
				else if ([controlView mouse:mouseLocation inRect:leftRect] && !isRecording)
				{
					if ([self isEnabled]) 
					{
                        [self _startRecordingTransition];
					}
					/* maybe beep if not editable?
					 else
					{
						NSBeep();
					}
					 */
				}
				
				// Any click inside will make us firstResponder
				if ([self isEnabled]) [[controlView window] makeFirstResponder: controlView];

				// Reset tracking rects and redisplay
				[self resetTrackingRects];
				[controlView setNeedsDisplayInRect: cellFrame];
				
				return YES;
			}
		}
		
    } while ((currentEvent = [[controlView window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask) untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES]));
	
    return YES;
}

#pragma mark *** Delegate ***

- (id)delegate
{
	return delegate;
}

- (void)setDelegate:(id)aDelegate
{
	delegate = aDelegate;
}

#pragma mark *** Responder Control ***

- (BOOL) becomeFirstResponder;
{
    // reset tracking rects and redisplay
    [self resetTrackingRects];
    [[self controlView] display];
    
    return YES;
}

- (BOOL)resignFirstResponder;
{
	if (isRecording) {
		[self _endRecordingTransition];
	}
    
    [self resetTrackingRects];
    [[self controlView] display];
    return YES;
}

#pragma mark *** Key Combination Control ***

- (BOOL) performKeyEquivalent:(NSEvent *)theEvent
{	
	NSUInteger flags = [self _filteredCocoaFlags: [theEvent modifierFlags]];
	NSNumber *keyCodeNumber = [NSNumber numberWithUnsignedShort: [theEvent keyCode]];
	BOOL snapback = [cancelCharacterSet containsObject: keyCodeNumber];
	BOOL validModifiers = [self _validModifierFlags: (snapback) ? [theEvent modifierFlags] : flags]; // Snapback key shouldn't interfer with required flags!
    
    // Special case for the space key when we aren't recording...
    if (!isRecording && [[theEvent characters] isEqualToString:@" "]) {
        [self _startRecordingTransition];
        return YES;
    }
	
	// Do something as long as we're in recording mode and a modifier key or cancel key is pressed
	if (isRecording && (validModifiers || snapback)) {
		if (!snapback || validModifiers) {
			BOOL goAhead = YES;
			
			// Special case: if a snapback key has been entered AND modifiers are deemed valid...
			if (snapback && validModifiers) {
				// ...AND we're set to allow plain keys
				if (allowsKeyOnly) {
					// ...AND modifiers are empty, or empty save for the Function key
					// (needed, since forward delete is fn+delete on laptops)
					if (flags == ShortcutRecorderEmptyFlags || flags == (ShortcutRecorderEmptyFlags | NSFunctionKeyMask)) {
						// ...check for behavior in escapeKeysRecord.
						if (!escapeKeysRecord) {
							goAhead = NO;
						}
					}
				}
			}
			
			if (goAhead) {
				
				NSString *character = [[theEvent charactersIgnoringModifiers] uppercaseString];
				
			// accents like "¬¥" or "`" will be ignored since we don't get a keycode
				if ([character length]) {
					NSError *error = nil;
					
				// Check if key combination is already used or not allowed by the delegate
					if ( [validator isKeyCode:[theEvent keyCode] 
								andFlagsTaken:[self _filteredCocoaToCarbonFlags:flags]
										error:&error] ) {
                    // display the error...
						NSAlert *alert = [NSAlert alertWithNonRecoverableError:error];
						[alert setAlertStyle:NSCriticalAlertStyle];
						[alert runModal];
						
					// Recheck pressed modifier keys
						[self flagsChanged: [NSApp currentEvent]];
						
						return YES;
					} else {
					// All ok, set new combination
						keyCombo.flags = flags;
						keyCombo.code = [theEvent keyCode];
						
						hasKeyChars = YES;
						keyChars = [[theEvent characters] retain];
						keyCharsIgnoringModifiers = [[theEvent charactersIgnoringModifiers] retain];
//						NSLog(@"keychars: %@, ignoringmods: %@", keyChars, keyCharsIgnoringModifiers);
//						NSLog(@"calculated keychars: %@, ignoring: %@", SRStringForKeyCode(keyCombo.code), SRCharacterForKeyCodeAndCocoaFlags(keyCombo.code,keyCombo.flags));
						
					// Notify delegate
						if (delegate != nil && [delegate respondsToSelector: @selector(shortcutRecorderCell:keyComboDidChange:)])
							[delegate shortcutRecorderCell:self keyComboDidChange:keyCombo];
						
					// Save if needed
						[self _saveKeyCombo];
						
						[self _setJustChanged];
					}
				} else {
				// invalid character
					NSBeep();
				}
			}
		}
		
		// reset values and redisplay
		recordingFlags = ShortcutRecorderEmptyFlags;
        
        [self _endRecordingTransition];
		
		[self resetTrackingRects];
		[[self controlView] display];
		
		return YES;
	} else {
		//Start recording when the spacebar is pressed while the control is first responder
		if (([[[self controlView] window] firstResponder] == [self controlView]) &&
			([[theEvent characters] length] && [[theEvent characters] characterAtIndex:0] == 32) &&
			([self isEnabled]))
		{
			[self _startRecordingTransition];
		}
	}
	
	return NO;
}

- (void)flagsChanged:(NSEvent *)theEvent
{
	if (isRecording)
	{
		recordingFlags = [self _filteredCocoaFlags: [theEvent modifierFlags]];
		[[self controlView] display];
	}
}

#pragma mark -

- (NSUInteger)allowedFlags
{
	return allowedFlags;
}

- (void)setAllowedFlags:(NSUInteger)flags
{
	allowedFlags = flags;
	
	// filter new flags and change keycombo if not recording
	if (isRecording)
	{
		recordingFlags = [self _filteredCocoaFlags: [[NSApp currentEvent] modifierFlags]];;
	}
	else
	{
		NSUInteger originalFlags = keyCombo.flags;
		keyCombo.flags = [self _filteredCocoaFlags: keyCombo.flags];
		
		if (keyCombo.flags != originalFlags && keyCombo.code > ShortcutRecorderEmptyCode)
		{
			// Notify delegate if keyCombo changed
			if (delegate != nil && [delegate respondsToSelector: @selector(shortcutRecorderCell:keyComboDidChange:)])
				[delegate shortcutRecorderCell:self keyComboDidChange:keyCombo];
			
			// Save if needed
			[self _saveKeyCombo];
		}
	}
	
	[[self controlView] display];
}

- (BOOL)allowsKeyOnly {
	return allowsKeyOnly;
}

- (BOOL)escapeKeysRecord {
	return escapeKeysRecord;
}

- (void)setAllowsKeyOnly:(BOOL)nAllowsKeyOnly escapeKeysRecord:(BOOL)nEscapeKeysRecord {
	allowsKeyOnly = nAllowsKeyOnly;
	escapeKeysRecord = nEscapeKeysRecord;
}

- (NSUInteger)requiredFlags
{
	return requiredFlags;
}

- (void)setRequiredFlags:(NSUInteger)flags
{
	requiredFlags = flags;
	
	// filter new flags and change keycombo if not recording
	if (isRecording)
	{
		recordingFlags = [self _filteredCocoaFlags: [[NSApp currentEvent] modifierFlags]];
	}
	else
	{
		NSUInteger originalFlags = keyCombo.flags;
		keyCombo.flags = [self _filteredCocoaFlags: keyCombo.flags];
		
		if (keyCombo.flags != originalFlags && keyCombo.code > ShortcutRecorderEmptyCode)
		{
			// Notify delegate if keyCombo changed
			if (delegate != nil && [delegate respondsToSelector: @selector(shortcutRecorderCell:keyComboDidChange:)])
				[delegate shortcutRecorderCell:self keyComboDidChange:keyCombo];
			
			// Save if needed
			[self _saveKeyCombo];
		}
	}
	
	[[self controlView] display];
}

- (KeyCombo)keyCombo
{
	return keyCombo;
}

- (void)setKeyCombo:(KeyCombo)aKeyCombo
{
	keyCombo = aKeyCombo;
	keyCombo.flags = [self _filteredCocoaFlags: aKeyCombo.flags];
	
	hasKeyChars = NO;

	// Notify delegate
	if (delegate != nil && [delegate respondsToSelector: @selector(shortcutRecorderCell:keyComboDidChange:)])
		[delegate shortcutRecorderCell:self keyComboDidChange:keyCombo];
	
	// Save if needed
	[self _saveKeyCombo];
	
	[[self controlView] display];
}

- (BOOL)canCaptureGlobalHotKeys
{
	return globalHotKeys;
}

- (void)setCanCaptureGlobalHotKeys:(BOOL)inState
{
	globalHotKeys = inState;
}

#pragma mark *** Autosave Control ***

- (NSString *)autosaveName
{
	return autosaveName;
}

- (void)setAutosaveName:(NSString *)aName
{
	if (aName != autosaveName)
	{
		[autosaveName release];
		autosaveName = [aName copy];
	}
}

#pragma mark -

- (NSString *)keyComboString
{
	if ([self _isEmpty]) return nil;
	
	return [NSString stringWithFormat: @"%@%@",
        SRStringForCocoaModifierFlags( keyCombo.flags ),
        SRStringForKeyCode( keyCombo.code )];
}

- (NSString *)keyChars {
	if (!hasKeyChars) return SRStringForKeyCode(keyCombo.code);
	return keyChars;
}

- (NSString *)keyCharsIgnoringModifiers {
	if (!hasKeyChars) return SRCharacterForKeyCodeAndCocoaFlags(keyCombo.code,keyCombo.flags);
	return keyCharsIgnoringModifiers;
}

@end

#pragma mark -

@implementation SRRecorderCell (Private)

- (void)_privateInit
{
    // init the validator object...
    validator = [[SRValidator alloc] initWithDelegate:self];
    
	// Allow all modifier keys by default, nothing is required
	allowedFlags = ShortcutRecorderAllFlags;
	requiredFlags = ShortcutRecorderEmptyFlags;
	recordingFlags = ShortcutRecorderEmptyFlags;
	
	// Create clean KeyCombo
	keyCombo.flags = ShortcutRecorderEmptyFlags;
	keyCombo.code = ShortcutRecorderEmptyCode;
	
	keyChars = nil;
	keyCharsIgnoringModifiers = nil;
	hasKeyChars = NO;
	
	// These keys will cancel the recoding mode if not pressed with any modifier
	cancelCharacterSet = [[NSSet alloc] initWithObjects: [NSNumber numberWithInteger:ShortcutRecorderEscapeKey], 
		[NSNumber numberWithInteger:ShortcutRecorderBackspaceKey], [NSNumber numberWithInteger:ShortcutRecorderDeleteKey], nil];
		
	NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
	[notificationCenter addObserver:self selector:@selector(_createGradient) name:NSSystemColorsDidChangeNotification object:nil]; // recreate gradient if needed
	[self _createGradient];

	[self _loadKeyCombo];
}

- (void)_createGradient
{
	NSColor *gradientStartColor = [[[NSColor alternateSelectedControlColor] shadowWithLevel: 0.2f] colorWithAlphaComponent: 0.9f];
	NSColor *gradientEndColor = [[[NSColor alternateSelectedControlColor] highlightWithLevel: 0.2f] colorWithAlphaComponent: 0.9f];
	
	recordingGradient = [[NSGradient alloc] initWithStartingColor:gradientStartColor endingColor:gradientEndColor];
}

- (void)_setJustChanged {
	comboJustChanged = YES;
}

- (BOOL)_effectiveIsAnimating {
	return (isAnimating && [self _supportsAnimation]);
}

- (BOOL)_supportsAnimation {
	return [[self class] styleSupportsAnimation:style];
}

- (void)_startRecordingTransition {
	if ([self _effectiveIsAnimating]) {
		isAnimatingTowardsRecording = YES;
		isAnimatingNow = YES;
		transitionProgress = 0.0f;
		[[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(_transitionTick) object:nil];
		[self performSelector:@selector(_transitionTick) withObject:nil afterDelay:(SRTransitionDuration/SRTransitionFrames)];
//	NSLog(@"start recording-transition");
	} else {
		[self _startRecording];
	}
}

- (void)_endRecordingTransition {
	if ([self _effectiveIsAnimating]) {
		isAnimatingTowardsRecording = NO;
		isAnimatingNow = YES;
		transitionProgress = 0.0f;
		[[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(_transitionTick) object:nil];
		[self performSelector:@selector(_transitionTick) withObject:nil afterDelay:(SRTransitionDuration/SRTransitionFrames)];
//	NSLog(@"end recording-transition");
	} else {
		[self _endRecording];
	}
}

- (void)_transitionTick {
	transitionProgress += (1.0f/SRTransitionFrames);
//	NSLog(@"transition tick: %f", transitionProgress);
	if (transitionProgress >= 0.998f) {
//		NSLog(@"transition deemed complete");
		isAnimatingNow = NO;
		transitionProgress = 0.0f;
		if (isAnimatingTowardsRecording) {
			[self _startRecording];
		} else {
			[self _endRecording];
		}
	} else {
//		NSLog(@"more to do");
		[[self controlView] setNeedsDisplay:YES];
		[[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(_transitionTick) object:nil];
		[self performSelector:@selector(_transitionTick) withObject:nil afterDelay:(SRTransitionDuration/SRTransitionFrames)];
	}
}

- (void)_startRecording;
{
    // Jump into recording mode if mouse was inside the control but not over any image
    isRecording = YES;
    
    // Reset recording flags and determine which are required
    recordingFlags = [self _filteredCocoaFlags: ShortcutRecorderEmptyFlags];
    
/*	[self setFocusRingType:NSFocusRingTypeNone];
	[[self controlView] setFocusRingType:NSFocusRingTypeNone];*/	
	[[self controlView] setNeedsDisplay:YES];
	
    // invalidate the focus ring rect...
    NSView *controlView = [self controlView];
    [controlView setKeyboardFocusRingNeedsDisplayInRect:[controlView bounds]];

    if (globalHotKeys) hotKeyModeToken = PushSymbolicHotKeyMode(kHIHotKeyModeAllDisabled);
}

- (void)_endRecording;
{
    isRecording = NO;
	comboJustChanged = NO;

/*	[self setFocusRingType:NSFocusRingTypeNone];
	[[self controlView] setFocusRingType:NSFocusRingTypeNone];*/	
	[[self controlView] setNeedsDisplay:YES];
	
    // invalidate the focus ring rect...
    NSView *controlView = [self controlView];
    [controlView setKeyboardFocusRingNeedsDisplayInRect:[controlView bounds]];
	
	if (globalHotKeys) PopSymbolicHotKeyMode(hotKeyModeToken);
}

#pragma mark *** Autosave ***

- (NSString *)_defaultsKeyForAutosaveName:(NSString *)name
{
	return [NSString stringWithFormat: @"ShortcutRecorder %@", name];
}

- (void)_saveKeyCombo
{
	NSString *defaultsKey = [self autosaveName];

	if (defaultsKey != nil && [defaultsKey length])
	{
		id values = [[NSUserDefaultsController sharedUserDefaultsController] values];
		
		NSDictionary *defaultsValue = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithShort: keyCombo.code], @"keyCode",
			[NSNumber numberWithUnsignedInteger: keyCombo.flags], @"modifierFlags", // cocoa
			[NSNumber numberWithUnsignedInteger:SRCocoaToCarbonFlags(keyCombo.flags)], @"modifiers", // carbon, for compatibility with PTKeyCombo
			nil];
		
		if (hasKeyChars) {
			
			NSMutableDictionary *mutableDefaultsValue = [[defaultsValue mutableCopy] autorelease];
			[mutableDefaultsValue setObject:keyChars forKey:@"keyChars"];
			[mutableDefaultsValue setObject:keyCharsIgnoringModifiers forKey:@"keyCharsIgnoringModifiers"];
			
			defaultsValue = mutableDefaultsValue;
		}
		
		[values setValue:defaultsValue forKey:[self _defaultsKeyForAutosaveName: defaultsKey]];
	}
}

- (void)_loadKeyCombo
{
	NSString *defaultsKey = [self autosaveName];

	if (defaultsKey != nil && [defaultsKey length])
	{
		id values = [[NSUserDefaultsController sharedUserDefaultsController] values];
		NSDictionary *savedCombo = [values valueForKey: [self _defaultsKeyForAutosaveName: defaultsKey]];
		
		NSInteger keyCode = [[savedCombo valueForKey: @"keyCode"] shortValue];
		NSUInteger flags;
		if ((nil == [savedCombo valueForKey:@"modifierFlags"]) && (nil != [savedCombo valueForKey:@"modifiers"])) { // carbon, for compatibility with PTKeyCombo
			flags = SRCarbonToCocoaFlags([[savedCombo valueForKey: @"modifiers"] unsignedIntegerValue]);
		} else { // cocoa
			flags = [[savedCombo valueForKey: @"modifierFlags"] unsignedIntegerValue];
		}
		
		keyCombo.flags = [self _filteredCocoaFlags: flags];
		keyCombo.code = keyCode;
		
		NSString *kc = [savedCombo valueForKey: @"keyChars"];
		hasKeyChars = (nil != kc);
		if (kc) {
			keyCharsIgnoringModifiers = [[savedCombo valueForKey: @"keyCharsIgnoringModifiers"] retain];
			keyChars = [kc retain];
		}
		
		// Notify delegate
		if (delegate != nil && [delegate respondsToSelector: @selector(shortcutRecorderCell:keyComboDidChange:)])
			[delegate shortcutRecorderCell:self keyComboDidChange:keyCombo];
		
		[[self controlView] display];
	}
}

#pragma mark *** Drawing Helpers ***

- (NSRect)_removeButtonRectForFrame:(NSRect)cellFrame
{	
	if ([self _isEmpty] || ![self isEnabled]) return NSZeroRect;
	
	NSRect removeButtonRect;
	NSImage *removeImage = SRResIndImage(@"SRRemoveShortcut");
	
	removeButtonRect.origin = NSMakePoint(NSMaxX(cellFrame) - [removeImage size].width - 4, (NSMaxY(cellFrame) - [removeImage size].height)/2);
	removeButtonRect.size = [removeImage size];

	return removeButtonRect;
}

- (NSRect)_snapbackRectForFrame:(NSRect)cellFrame
{	
//	if (!isRecording) return NSZeroRect;

	NSRect snapbackRect;
	NSImage *snapbackImage = SRResIndImage(@"SRSnapback");
	
	snapbackRect.origin = NSMakePoint(NSMaxX(cellFrame) - [snapbackImage size].width - 2, (NSMaxY(cellFrame) - [snapbackImage size].height)/2 + 1);
	snapbackRect.size = [snapbackImage size];

	return snapbackRect;
}

#pragma mark *** Filters ***

- (NSUInteger)_filteredCocoaFlags:(NSUInteger)flags
{
	NSUInteger filteredFlags = ShortcutRecorderEmptyFlags;
	NSUInteger a = allowedFlags;
	NSUInteger m = requiredFlags;

	if (m & NSCommandKeyMask) filteredFlags |= NSCommandKeyMask;
	else if ((flags & NSCommandKeyMask) && (a & NSCommandKeyMask)) filteredFlags |= NSCommandKeyMask;
	
	if (m & NSAlternateKeyMask) filteredFlags |= NSAlternateKeyMask;
	else if ((flags & NSAlternateKeyMask) && (a & NSAlternateKeyMask)) filteredFlags |= NSAlternateKeyMask;
	
	if ((m & NSControlKeyMask)) filteredFlags |= NSControlKeyMask;
	else if ((flags & NSControlKeyMask) && (a & NSControlKeyMask)) filteredFlags |= NSControlKeyMask;
	
	if ((m & NSShiftKeyMask)) filteredFlags |= NSShiftKeyMask;
	else if ((flags & NSShiftKeyMask) && (a & NSShiftKeyMask)) filteredFlags |= NSShiftKeyMask;
	
	if ((m & NSFunctionKeyMask)) filteredFlags |= NSFunctionKeyMask;
	else if ((flags & NSFunctionKeyMask) && (a & NSFunctionKeyMask)) filteredFlags |= NSFunctionKeyMask;
	
	return filteredFlags;
}

- (BOOL)_validModifierFlags:(NSUInteger)flags
{
	return (allowsKeyOnly ? YES : (((flags & NSCommandKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSControlKeyMask) || (flags & NSShiftKeyMask) || (flags & NSFunctionKeyMask)) ? YES : NO));	
}

#pragma mark -

- (NSUInteger)_filteredCocoaToCarbonFlags:(NSUInteger)cocoaFlags
{
	NSUInteger carbonFlags = ShortcutRecorderEmptyFlags;
	NSUInteger filteredFlags = [self _filteredCocoaFlags: cocoaFlags];
	
	if (filteredFlags & NSCommandKeyMask) carbonFlags |= cmdKey;
	if (filteredFlags & NSAlternateKeyMask) carbonFlags |= optionKey;
	if (filteredFlags & NSControlKeyMask) carbonFlags |= controlKey;
	if (filteredFlags & NSShiftKeyMask) carbonFlags |= shiftKey;
	
	// I couldn't find out the equivalent constant in Carbon, but apparently it must use the same one as Cocoa. -AK
	if (filteredFlags & NSFunctionKeyMask) carbonFlags |= NSFunctionKeyMask;
	
	return carbonFlags;
}

#pragma mark *** Internal Check ***

- (BOOL)_isEmpty
{
	return ( ![self _validModifierFlags: keyCombo.flags] || !SRStringForKeyCode( keyCombo.code ) );
}

#pragma mark *** Delegate pass-through ***

- (BOOL) shortcutValidator:(SRValidator *)validator isKeyCode:(NSInteger)keyCode andFlagsTaken:(NSUInteger)flags reason:(NSString **)aReason;
{
    SEL selector = @selector( shortcutRecorderCell:isKeyCode:andFlagsTaken:reason: );
    if ( ( delegate ) && ( [delegate respondsToSelector:selector] ) )
    {
        return [delegate shortcutRecorderCell:self isKeyCode:keyCode andFlagsTaken:flags reason:aReason];
    }
    return NO;
}

@end