//
//  $Id$
//
//  SPFavoritesPreferencePane.m
//  sequel-pro
//
//  Created by Stuart Connolly (stuconnolly.com) on October 31, 2010
//  Copyright (c) 2010 Stuart Connolly. All rights reserved.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  More info at <http://code.google.com/p/sequel-pro/>

#import "SPFavoritesPreferencePane.h"
#import "SPFavoriteTextFieldCell.h"
#import "SPPreferenceController.h"
#import "SPKeychain.h"
#import <BWToolkitFramework/BWToolkitFramework.h>
#import "SPGeneralPreferencePane.h"

@interface SPFavoritesPreferencePane (PrivateAPI)

- (void)_sortFavorites;
- (void)_updateFavoritePasswordsFromField:(NSControl *)passwordControl;

@end

@interface NSSavePanel (NSSavePanel_unpublishedUntilSnowLeopardAPI)
- (void)setShowsHiddenFiles:(BOOL)flag;
@end

@implementation SPFavoritesPreferencePane

#pragma mark -
#pragma mark Intialisation

/**
 * Init.
 */
- (id)init
{
	if ((self = [super init])) {
		
		keychain = [[SPKeychain alloc] init];
		
		favoriteType = 0;
		reverseFavoritesSort = NO;
		favoriteNameFieldWasTouched = YES;
		
		previousSortItem = SPFavoritesSortNameItem;
	}
	
	return self;
}

/**
 * Initialise the UI, specifically the favourites table view and sort the favourites if required.
 */
- (void)awakeFromNib
{
	// Set sort items
	currentSortItem = [prefs integerForKey:SPFavoritesSortedBy];
	reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse];
	
	// Replace column's NSTextFieldCell with custom SWProfileTextFieldCell
	[[[favoritesTableView tableColumns] objectAtIndex:0] setDataCell:tableCell];
	
	[favoritesTableView registerForDraggedTypes:[NSArray arrayWithObject:SPFavoritesPasteboardDragType]];
	
	[favoritesTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
	[favoritesTableView reloadData];
	
	[tableCell setImage:[NSImage imageNamed:@"database"]];
	
	// Set the button bar delegate 
	[splitViewButtonBar setSplitViewDelegate:self];
	
	// Hide the tabs on the favorites tab view - left visible in IB for easy use
	[favoritesTabView setTabViewType:NSNoTabsNoBorder];
	
	// Sort favorites if a sort type has been selected
	if (currentSortItem != SPFavoritesSortUnsorted) [self _sortFavorites];
}

#pragma mark -
#pragma mark IBAction methods

/**
 * Adds a new connection favorite.
 */
- (IBAction)addFavorite:(id)sender
{
	NSNumber *favoriteid = [NSNumber numberWithInteger:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]];
	
	// Create default favorite
	NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:NSLocalizedString(@"New Favorite", @"new favorite name"), [NSNumber numberWithInteger:0], @"", @"", @"", @"", [NSNumber numberWithInt:NSOffState], [NSNumber numberWithInt:NSOffState], [NSNumber numberWithInt:NSOffState], [NSNumber numberWithInt:NSOffState], @"", @"", @"", [NSNumber numberWithInt:NSOffState], @"", @"", favoriteid, nil] 
																	   forKeys:[NSArray arrayWithObjects:@"name", @"type", @"host", @"socket", @"user", @"port", @"useSSL", @"sslKeyFileLocationEnabled", @"sslCertificateFileLocationEnabled", @"sslCACertFileLocationEnabled", @"database", @"sshHost", @"sshUser", @"sshKeyLocationEnabled", @"sshKeyLocation", @"sshPort", @"id", nil]];
	
	[favoritesController addObject:favorite];
	[favoritesController setSelectedObjects:[NSArray arrayWithObject:favorite]];
	
	[favoritesTableView reloadData];
	[favoritesTableView scrollRowToVisible:[favoritesTableView selectedRow]];
	
	[[(SPPreferenceController *)[[[self view] window] delegate] generalPreferencePane] updateDefaultFavoritePopup];
	
	favoriteNameFieldWasTouched = NO;
	
	[[[self view] window] makeFirstResponder:favoriteHostTextField];
}

/**
 * Removes the selected connection favorite.
 */
- (IBAction)removeFavorite:(id)sender
{
	if ([favoritesTableView numberOfSelectedRows] == 1) {
		NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete favorite '%@'?", @"delete database message"), [favoritesController valueForKeyPath:@"selection.name"]]
										 defaultButton:NSLocalizedString(@"Delete", @"delete button") 
									   alternateButton:NSLocalizedString(@"Cancel", @"cancel button") 
										   otherButton:nil 
							 informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the favorite '%@'? This operation cannot be undone.", @"delete database informative message"), [favoritesController valueForKeyPath:@"selection.name"]]];
		
		NSArray *buttons = [alert buttons];
		
		// Change the alert's cancel button to have the key equivalent of return
		[[buttons objectAtIndex:0] setKeyEquivalent:@"d"];
		[[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask];
		[[buttons objectAtIndex:1] setKeyEquivalent:@"\r"];
		
		[alert setAlertStyle:NSCriticalAlertStyle];
		
		[alert beginSheetModalForWindow:[[self view] window] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeFavorite"];
	}
}

/**
 * Duplicates the selected connection favorite.
 */
- (IBAction)duplicateFavorite:(id)sender
{
	if ([favoritesTableView numberOfSelectedRows] == 1) {
		
		NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithDictionary:[[favoritesController arrangedObjects] objectAtIndex:[favoritesTableView selectedRow]]];
		NSNumber *favoriteid = [NSNumber numberWithInteger:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]];
		NSInteger duplicatedFavoriteType = [[favorite objectForKey:@"type"] integerValue];
		
		// Select the keychain passwords for duplication
		NSString *keychainName       = [keychain nameForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
		NSString *keychainAccount    = [keychain accountForUser:[favorite objectForKey:@"user"] host:((duplicatedFavoriteType == SPSocketConnection)?@"localhost":[favorite objectForKey:@"host"]) database:[favorite objectForKey:@"database"]];
		NSString *password           = [keychain getPasswordForName:keychainName account:keychainAccount];
		NSString *keychainSSHName    = [keychain nameForSSHForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
		NSString *keychainSSHAccount = [keychain accountForSSHUser:[favorite objectForKey:@"sshUser"] sshHost:[favorite objectForKey:@"sshHost"]];
		NSString *sshPassword         = [keychain getPasswordForName:keychainSSHName account:keychainSSHAccount];
		
		// Update the unique ID
		[favorite setObject:favoriteid forKey:@"id"];
		
		// Alter the name for clarity
		[favorite setObject:[NSString stringWithFormat:NSLocalizedString(@"%@ Copy", @"Initial favourite name after duplicating a previous favourite"), [favorite objectForKey:@"name"]] forKey:@"name"];
		
		// Create new keychain items if appropriate
		if (password && [password length]) {
			keychainName = [keychain nameForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
			[keychain addPassword:password forName:keychainName account:keychainAccount];
		}
		
		if (sshPassword && [sshPassword length]) {
			keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
			[keychain addPassword:sshPassword forName:keychainSSHName account:keychainSSHAccount];
		}
		
		password = nil, sshPassword = nil;
		
		[favoritesController addObject:favorite];
		[favoritesController setSelectedObjects:[NSArray arrayWithObject:favorite]];
		
		[favoritesTableView reloadData];
		[favoritesTableView scrollRowToVisible:[favoritesTableView selectedRow]];
		
		[[(SPPreferenceController *)[[[self view] window] delegate] generalPreferencePane] updateDefaultFavoritePopup];
		
		[[[self view] window] makeFirstResponder:favoriteNameTextField];
	}
}

/**
 * Sorts the favorites table view based on the selected sort by item
 */
- (IBAction)sortFavorites:(id)sender
{	
	previousSortItem = currentSortItem;
	currentSortItem  = [[sender menu] indexOfItem:sender];
	
	[prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy];
	
	// Perform sorting
	[self _sortFavorites];
	
	if ((NSInteger)previousSortItem > -1) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState];
	
	[[[sender menu] itemAtIndex:currentSortItem] setState:NSOnState];	
}

/**
 * Reverses the favorites table view sorting based on the selected criteria
 */
- (IBAction)reverseFavoritesSortOrder:(id)sender
{	
	reverseFavoritesSort = (![sender state]);
	
	[prefs setBool:reverseFavoritesSort forKey:SPFavoritesSortedInReverse];
	
	// Perform re-sorting
	[self _sortFavorites];
	
	[sender setState:reverseFavoritesSort]; 
}

/**
 * Makes the selected favorite the default.
 */
- (IBAction)makeSelectedFavoriteDefault:(id)sender
{
	// Minus 2 from index to account for the 'Last Used' and separator items
	[prefs setInteger:[favoritesTableView selectedRow] forKey:SPDefaultFavorite];
	
	[favoritesTableView reloadData];
	
	[[(SPPreferenceController *)[[[self view] window] delegate] generalPreferencePane] updateDefaultFavoritePopup];
}

/**
 * Update the favorite host when the type changes.
 */
- (IBAction)favoriteTypeDidChange:(id)sender
{
	// If not socket and host is localhost, clear.
	if (([sender indexOfSelectedItem] != 1) && [[favoritesController valueForKeyPath:@"selection.host"] isEqualToString:@"localhost"])
	{
		[favoritesController setValue:@"" forKeyPath:@"selection.host"];
	}
	
	favoriteType = [sender indexOfSelectedItem];
	
	// Update the name for a new added favorite if not touched by the user
	if(!favoriteNameFieldWasTouched) {
		[favoriteNameTextField setStringValue:[NSString stringWithFormat:@"%@@%@", 
											   ([favoritesController valueForKeyPath:@"selection.user"]) ? [favoritesController valueForKeyPath:@"selection.user"] : @"", 
											   (([sender indexOfSelectedItem] == 1) ? @"localhost" :
												(([favoritesController valueForKeyPath:@"selection.host"]) ? [favoritesController valueForKeyPath:@"selection.host"] : @""))
											   ]];
		
		[favoritesController setValue:[favoriteNameTextField stringValue] forKeyPath:@"selection.name"];
	}
	
	// Request a password refresh to keep keychain references in synch with the favorites
	[self _updateFavoritePasswordsFromField:nil];
}

/**
 * Opens the SSH/SSL key selection window, ready to select a key file.
 */
- (IBAction)chooseKeyLocation:(id)sender
{
	NSString *directoryPath = nil;
	NSString *filePath = nil;
	NSArray *permittedFileTypes = nil;
	keySelectionPanel = [NSOpenPanel openPanel];
	[keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]];
	
	// Switch details by sender.
	// First, SSH keys:
	if (sender == sshSSHKeyButton) {
		
		// If the custom key location is currently disabled - after the button
		// action - leave it disabled and return without showing the sheet.
		if (![favoritesController valueForKeyPath:@"selection.sshKeyLocationEnabled"]) {
			return;
		}
		
		// Otherwise open a panel at the last or default location
		NSString *sshKeyLocation = [favoritesController valueForKeyPath:@"selection.sshKeyLocation"];
		if (sshKeyLocation && [sshKeyLocation length]) {
			filePath = [sshKeyLocation lastPathComponent];
			directoryPath = [sshKeyLocation stringByDeletingLastPathComponent];
		}
		
		permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"", nil];
		
		[keySelectionPanel setAccessoryView:sshKeyLocationHelp];
		
		// SSL key file location:
	} 
	else if (sender == standardSSLKeyFileButton || sender == socketSSLKeyFileButton) {
		if ([sender state] == NSOffState) {
			[favoritesController setValue:nil forKeyPath:@"selection.sslKeyFileLocation"];
			return;
		}
		
		permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"key", @"", nil];
		[keySelectionPanel setAccessoryView:sslKeyFileLocationHelp];
		
		// SSL certificate file location:
	} 
	else if (sender == standardSSLCertificateButton || sender == socketSSLCertificateButton) {
		if ([sender state] == NSOffState) {
			[favoritesController setValue:nil forKeyPath:@"selection.sslCertificateFileLocation"];
			return;
		}
		
		permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil];
		[keySelectionPanel setAccessoryView:sslCertificateLocationHelp];
		
		// SSL CA certificate file location:
	} 
	else if (sender == standardSSLCACertButton || sender == socketSSLCACertButton) {
		if ([sender state] == NSOffState) {
			[favoritesController setValue:nil forKeyPath:@"selection.sslCACertFileLocation"];
			return;
		}
		
		permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil];
		[keySelectionPanel setAccessoryView:sslCACertLocationHelp];
	}
	
	[keySelectionPanel beginSheetForDirectory:directoryPath
										 file:filePath
										types:permittedFileTypes
							   modalForWindow:[[self view] window]
								modalDelegate:self
							   didEndSelector:@selector(chooseKeyLocationSheetDidEnd:returnCode:contextInfo:)
								  contextInfo:sender];
}

/**
 * Toggle hidden file visiblity in response to accessory view changes
 */
- (IBAction)updateKeyLocationFileVisibility:(id)sender
{
	[keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]];
}

#pragma mark -
#pragma mark Public API

/**
 * Selects the specified favorite(s) in the favorites list.
 */
- (void)selectFavorites:(NSArray *)favorites
{
	[favoritesController setSelectedObjects:favorites];
	[favoritesTableView scrollRowToVisible:[favoritesController selectionIndex]];
}

#pragma mark -
#pragma mark TableView datasource methods

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{	
	return [[favoritesController arrangedObjects] count];
}

- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
{
	return [[[favoritesController arrangedObjects] objectAtIndex:rowIndex] objectForKey:[tableColumn identifier]];
}

#pragma mark -
#pragma mark TableView drag & drop delegate methods

- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard
{
	if ([rows count] == 1) {
		[pboard declareTypes:[NSArray arrayWithObject:SPFavoritesPasteboardDragType] owner:nil];
		[pboard setString:[[NSNumber numberWithInteger:[rows firstIndex]] stringValue] forType:SPFavoritesPasteboardDragType];
		
		return YES;
	} 
	else {
		return NO;
	}
}

- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation
{
	NSInteger originalRow;
	NSArray *pboardTypes = [[info draggingPasteboard] types];
	
	if (([pboardTypes count] > 1) && (row != -1)) {
		if (([pboardTypes containsObject:SPFavoritesPasteboardDragType]) && (operation == NSTableViewDropAbove)) {
			originalRow = [[[info draggingPasteboard] stringForType:SPFavoritesPasteboardDragType] integerValue];
			
			if ((row != originalRow) && (row != (originalRow + 1))) {
				return NSDragOperationMove;
			}
		}
	}
	
	return NSDragOperationNone;
}

- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id <NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
	NSInteger originalRow;
	NSInteger destinationRow;
	NSInteger lastFavoriteIndexCached;
	NSMutableDictionary *draggedRow;
	
	// Disable all automatic sorting
	currentSortItem = -1;
	reverseFavoritesSort = NO;
	
	[prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy];
	[prefs setBool:NO forKey:SPFavoritesSortedInReverse];
	
	// Remove sort descriptors
	[favoritesController setSortDescriptors:[NSArray array]];
	
	// Uncheck sort by menu items
	for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray])
	{
		[menuItem setState:NSOffState];
	}
	
	originalRow = [[[info draggingPasteboard] stringForType:SPFavoritesPasteboardDragType] integerValue];
	destinationRow = row;
	
	if (destinationRow > originalRow) {
		destinationRow--;
	}
	
	draggedRow = [NSMutableDictionary dictionaryWithDictionary:[[favoritesController arrangedObjects] objectAtIndex:originalRow]];
	
	// Before deleting this favorite, we need to save the current index.
	// because removeObjectAtArrangedObjectIndex will set prefs LastFavoriteIndex to 0
	lastFavoriteIndexCached = [prefs integerForKey:SPLastFavoriteIndex];
	
	[favoritesController removeObjectAtArrangedObjectIndex:originalRow];
	[favoritesController insertObject:draggedRow atArrangedObjectIndex:destinationRow];
	
	[favoritesTableView reloadData];
	[favoritesTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRow] byExtendingSelection:NO];
	
	// Update default favorite to take on new value
	if (lastFavoriteIndexCached == originalRow) {
		[prefs setInteger:destinationRow forKey:SPLastFavoriteIndex];
	}
	
	// Update default favorite to take on new value
	if ([prefs integerForKey:SPDefaultFavorite] == originalRow) {
		[prefs setInteger:destinationRow forKey:SPDefaultFavorite];
	}
	
	[[(SPPreferenceController *)[[[self view] window] delegate] generalPreferencePane] updateDefaultFavoritePopup];
	
	return YES;
}

#pragma mark -
#pragma mark TableView delegate methods

- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)index
{
	if ([cell isKindOfClass:[SPFavoriteTextFieldCell class]]) {
		[cell setFavoriteName:[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"name"]];
		
		if ([[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"type"] integerValue] == SPSocketConnection) {
			[cell setFavoriteHost:@"localhost"];
		} 
		else {
			[cell setFavoriteHost:[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"host"]];
		}
	}
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification
{	
	if ([[favoritesTableView selectedRowIndexes] count] > 0) {
		[favoritesController setSelectionIndexes:[favoritesTableView selectedRowIndexes]];
	}
	
	// If no selection is present, blank the password fields (which can't use bindings)
	if ([[favoritesTableView selectedRowIndexes] count] == 0) {
		[standardPasswordField setStringValue:@""];
		[socketPasswordField setStringValue:@""];
		[sshSQLPasswordField setStringValue:@""];
		[sshPasswordField setStringValue:@""];
		
		return;
	}
	
	// Keep a copy of the favorite as it currently stands
	if (currentFavorite) [currentFavorite release];
	
	currentFavorite = [[[favoritesController selectedObjects] objectAtIndex:0] copy];
	
	// Retrieve and set the password.
	NSString *keychainName    = [keychain nameForFavoriteName:[currentFavorite objectForKey:@"name"] id:[currentFavorite objectForKey:@"id"]];
	NSString *keychainAccount = [keychain accountForUser:[currentFavorite objectForKey:@"user"] host:(([[currentFavorite objectForKey:@"type"] integerValue] == SPSocketConnection)?@"localhost":[currentFavorite objectForKey:@"host"]) database:[currentFavorite objectForKey:@"database"]];
	NSString *passwordValue   = [keychain getPasswordForName:keychainName account:keychainAccount];
	
	[standardPasswordField setStringValue:passwordValue?passwordValue:@""];
	[socketPasswordField setStringValue:passwordValue?passwordValue:@""];
	[sshSQLPasswordField setStringValue:passwordValue?passwordValue:@""];
	
	// Retrieve the SSH keychain password if appropriate.
	NSString *keychainSSHName    = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:@"name"] id:[currentFavorite objectForKey:@"id"]];
	NSString *keychainSSHAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:@"sshUser"] sshHost:[currentFavorite objectForKey:@"sshHost"]];
	NSString *sshPasswordValue   = [keychain getPasswordForName:keychainSSHName account:keychainSSHAccount];
	[sshPasswordField setStringValue:sshPasswordValue?sshPasswordValue:@""];
	
	favoriteNameFieldWasTouched = YES;
}

#pragma mark -
#pragma mark TextField delegate methods and type change action

/**
 * Trap editing end notifications and use them to update the keychain password
 * appropriately when name, host, user, password or database changes.
 */
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor
{
	// Request a password refresh to keep keychain references in synch with favorites
	[self _updateFavoritePasswordsFromField:control];
	
	// Proceed with editing
	return YES;
}

/**
 * Trap and control the 'name' field of the selected favorite. If the user pressed
 * 'Add Favorite' the 'name' field is set to "New Favorite". If the user do not
 * change the 'name' field or delete that field it will be set to user@host automatically.
 */
- (void)controlTextDidChange:(NSNotification *)notification
{
	id field = [notification object];
	
	BOOL nameFieldIsEmpty = ([[favoritesController valueForKeyPath:@"selection.name"] isEqualToString:@""] || 
							 [[favoriteNameTextField stringValue] isEqualToString:@""]);
	
	switch (favoriteType) 
	{
		case 0:
			if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && (field == favoriteUserTextField || field == favoriteHostTextField))) {
				[favoriteNameTextField setStringValue:[NSString stringWithFormat:@"%@@%@", [favoriteUserTextField stringValue], [favoriteHostTextField stringValue]]];
				[favoritesController setValue:[favoriteNameTextField stringValue] forKeyPath:@"selection.name"];
				[prefs synchronize];
				
				// if name field is empty enable user@host update
				if (nameFieldIsEmpty) favoriteNameFieldWasTouched = NO;
			}
			break;
		case 1:
			if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && field == favoriteUserTextFieldSocket)) {
				[favoriteNameTextField setStringValue:[NSString stringWithFormat:@"%@@localhost", [favoriteUserTextFieldSocket stringValue]]];
				[favoritesController setValue:[favoriteNameTextField stringValue] forKeyPath:@"selection.name"];
				[prefs synchronize];
				
				// if name field is empty enable user@host update
				if (nameFieldIsEmpty) favoriteNameFieldWasTouched = NO;
			}
			break;
		case 2:
			if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && (field == favoriteUserTextFieldSSH || field == favoriteHostTextFieldSSH))) {
				[favoriteNameTextField setStringValue:[NSString stringWithFormat:@"%@@%@", [favoriteUserTextFieldSSH stringValue], [favoriteHostTextFieldSSH stringValue]]];
				[favoritesController setValue:[favoriteNameTextField stringValue] forKeyPath:@"selection.name"];
				[prefs synchronize];
				
				// if name field is empty enable user@host update
				if (nameFieldIsEmpty) favoriteNameFieldWasTouched = NO;
			}
			break;
		default:
			break;
	}
	
	if (field == favoriteNameTextField) favoriteNameFieldWasTouched = YES;
}

#pragma mark -
#pragma mark SplitView delegate methods

- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
{
	return (proposedMax - 220);
}

- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
{
	return (proposedMin + 94);
}

#pragma mark -
#pragma mark Other

/**
 * Menu item validation;
 */
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	SEL action = [menuItem action];
	
	if ((action == @selector(removeFavorite:)) || (action == @selector(duplicateFavorite:))) {
		return ([favoritesTableView numberOfSelectedRows] > 0);
	}
	
	if (action == @selector(makeSelectedFavoriteDefault:)) {
		return ([favoritesTableView numberOfSelectedRows] == 1);
	}
	
	if ((action == @selector(sortFavorites:)) || (action == @selector(reverseFavoritesSortOrder:))) {
		
		// Loop all the items in the sort by menu only checking the currently selected one
		for (NSMenuItem *item in [[menuItem menu] itemArray])
		{
			[item setState:([[menuItem menu] indexOfItem:item] == currentSortItem)];
		}
		
		// Check or uncheck the reverse sort item
		if (action == @selector(reverseFavoritesSortOrder:)) {
			[menuItem setState:reverseFavoritesSort];
		}
		
		return [[[[[self view] window] toolbar] selectedItemIdentifier] isEqualToString:SPPreferenceToolbarFavorites];
	}
	
	return YES;
}

/**
 * Called after closing the SSH/SSL key selection sheet.
 */
- (void)chooseKeyLocationSheetDidEnd:(NSOpenPanel *)openPanel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
	NSString *abbreviatedFileName = [[[openPanel URL] path] stringByAbbreviatingWithTildeInPath];
	
	// SSH key file selection
	if (contextInfo == sshSSHKeyButton) {
		if (returnCode == NSCancelButton) {
			[favoritesController setValue:[NSNumber numberWithInt:NSOffState] forKeyPath:@"selection.sshKeyLocationEnabled"];
			return;
		}
		
		[favoritesController setValue:abbreviatedFileName forKeyPath:@"selection.sshKeyLocation"];
		
		// SSL key file selection
	} 
	else if (contextInfo == standardSSLKeyFileButton || contextInfo == socketSSLKeyFileButton) {
		if (returnCode == NSCancelButton) {
			[favoritesController setValue:[NSNumber numberWithInt:NSOffState] forKeyPath:@"selection.sslKeyFileLocationEnabled"];
			[favoritesController setValue:nil forKeyPath:@"selection.sslKeyFileLocation"];
			return;
		}
		
		[favoritesController setValue:abbreviatedFileName forKeyPath:@"selection.sslKeyFileLocation"];
		
		// SSL certificate file selection
	} 
	else if (contextInfo == standardSSLCertificateButton || contextInfo == socketSSLCertificateButton) {
		if (returnCode == NSCancelButton) {
			[favoritesController setValue:[NSNumber numberWithInt:NSOffState] forKeyPath:@"selection.sslCertificateFileLocationEnabled"];
			[favoritesController setValue:nil forKeyPath:@"selection.sslCertificateFileLocation"];
			return;
		}
		
		[favoritesController setValue:abbreviatedFileName forKeyPath:@"selection.sslCertificateFileLocation"];
		
		// SSL CA certificate file selection
	} 
	else if (contextInfo == standardSSLCACertButton || contextInfo == socketSSLCACertButton) {
		if (returnCode == NSCancelButton) {
			[favoritesController setValue:[NSNumber numberWithInt:NSOffState] forKeyPath:@"selection.sslCACertFileLocationEnabled"];
			[favoritesController setValue:nil forKeyPath:@"selection.sslCACertFileLocation"];
			return;
		}
		
		[favoritesController setValue:abbreviatedFileName forKeyPath:@"selection.sslCACertFileLocation"];
	}
}

- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
{
	// Order out current sheet to suppress overlapping of sheets
	if ([sheet respondsToSelector:@selector(orderOut:)]) {
		[sheet orderOut:nil];
	}
	else if ([sheet respondsToSelector:@selector(window)]) {
		[[sheet window] orderOut:nil];
	}
	
	// Remove the current favorite
	if ([contextInfo isEqualToString:@"removeFavorite"]) {
		if (returnCode == NSAlertDefaultReturn) {
			
			// Get selected favorite's details
			NSString *name     = [favoritesController valueForKeyPath:@"selection.name"];
			NSString *user     = [favoritesController valueForKeyPath:@"selection.user"];
			NSString *host     = [favoritesController valueForKeyPath:@"selection.host"];
			NSString *database = [favoritesController valueForKeyPath:@"selection.database"];
			NSString *sshUser  = [favoritesController valueForKeyPath:@"selection.sshUser"];
			NSString *sshHost  = [favoritesController valueForKeyPath:@"selection.sshHost"];
			NSString *favoriteid = [favoritesController valueForKeyPath:@"selection.id"];
			NSInteger type     = [[favoritesController valueForKeyPath:@"selection.type"] integerValue];
			
			// Remove passwords from the Keychain
			[keychain deletePasswordForName:[keychain nameForFavoriteName:name id:favoriteid]
									account:[keychain accountForUser:user host:((type == SPSocketConnection)?@"localhost":host) database:database]];
			[keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:name id:favoriteid]
									account:[keychain accountForSSHUser:sshUser sshHost:sshHost]];
			
			// Reset last used favorite
			if ([favoritesTableView selectedRow] == [prefs integerForKey:SPLastFavoriteIndex]) {
				[prefs setInteger:0	forKey:SPLastFavoriteIndex];
			}
			
			// Reset default favorite
			if ([favoritesTableView selectedRow] == [prefs integerForKey:SPDefaultFavorite]) {
				[prefs setInteger:[prefs integerForKey:SPLastFavoriteIndex] forKey:SPDefaultFavorite];
			}
			
			[favoritesController removeObjectAtArrangedObjectIndex:[favoritesTableView selectedRow]];
			
			[favoritesTableView reloadData];
			
			[[(SPPreferenceController *)[[[self view] window] delegate] generalPreferencePane] updateDefaultFavoritePopup];
		}
	}	
}

#pragma mark -
#pragma mark Preference pane protocol methods

- (NSView *)preferencePaneView
{
	return [self view];
}

- (NSImage *)preferencePaneIcon
{
	return [NSImage imageNamed:@"toolbar-preferences-favorites"];
}

- (NSString *)preferencePaneName
{
	return NSLocalizedString(@"Favorites", @"favorites label");
}

- (NSString *)preferencePaneIdentifier
{
	return SPPreferenceToolbarFavorites;
}

- (NSString *)preferencePaneToolTip
{
	return NSLocalizedString(@"Favorite Preferences", @"favorites preference pane tooltip");
}

- (BOOL)preferencePaneAllowsResizing
{
	return YES;
}

#pragma mark -
#pragma mark Private API

/**
 * Sorts the connection favorites based on the selected criteria.
 */
- (void)_sortFavorites
{		
	NSString *sortKey = SPFavoriteNameKey;
	
	switch (currentSortItem)
	{
		case SPFavoritesSortNameItem:
			sortKey = SPFavoriteNameKey;
			break;
		case SPFavoritesSortHostItem:
			sortKey = SPFavoriteHostKey;
			break;
		case SPFavoritesSortTypeItem:
			sortKey = SPFavoriteTypeKey;
			break;
		default:
			return;
	}
	
	NSSortDescriptor *sortDescriptor = nil;
	
	if (currentSortItem == SPFavoritesSortTypeItem) {
		sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort)] autorelease];
	}
	else {
		sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort) selector:@selector(caseInsensitiveCompare:)] autorelease];
	}
	
	[favoritesController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
	
	[favoritesTableView reloadData];
	
	[[(SPPreferenceController *)[[[self view] window] delegate] generalPreferencePane] updateDefaultFavoritePopup];
}

/**
 * Check all fields used in the keychain names against the old values for that
 * favorite, and update the keychain names to match if necessary.
 * If an (optional) recognised password field is supplied, that field is assumed
 * to have changed and is used to supply the new value.
 */
- (void)_updateFavoritePasswordsFromField:(NSControl *)passwordControl
{
	if (!currentFavorite) return;
	
	NSString *passwordValue;
	NSString *oldKeychainName, *newKeychainName;
	NSString *oldKeychainAccount, *newKeychainAccount;
	NSString *oldHostnameForPassword = ([[currentFavorite objectForKey:@"type"] integerValue] == SPSocketConnection) ? @"localhost" : [currentFavorite objectForKey:@"host"];
	NSString *newHostnameForPassword = ([[favoritesController valueForKeyPath:@"selection.type"] integerValue] == SPSocketConnection) ? @"localhost" : [favoritesController valueForKeyPath:@"selection.host"];
	
	// SQL passwords are indexed by name, host, user and database.  If any of these
	// have changed, or a standard password field has, alter the keychain item to match.
	if (![[currentFavorite objectForKey:@"name"] isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]]
		|| ![oldHostnameForPassword isEqualToString:newHostnameForPassword]
		|| ![[currentFavorite objectForKey:@"user"] isEqualToString:[favoritesController valueForKeyPath:@"selection.user"]]
		|| ![[currentFavorite objectForKey:@"database"] isEqualToString:[favoritesController valueForKeyPath:@"selection.database"]]
		|| passwordControl == standardPasswordField || passwordControl == socketPasswordField || passwordControl == sshSQLPasswordField)
	{
		
		// Determine the correct password field to read the password from, defaulting to standard
		if (passwordControl == socketPasswordField) {
			passwordValue = [socketPasswordField stringValue];
		} 
		else if (passwordControl == sshSQLPasswordField) {
			passwordValue = [sshSQLPasswordField stringValue];
		} 
		else {
			passwordValue = [standardPasswordField stringValue];
		}
		
		// Get the old keychain name and account strings
		oldKeychainName = [keychain nameForFavoriteName:[currentFavorite objectForKey:@"name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
		oldKeychainAccount = [keychain accountForUser:[currentFavorite objectForKey:@"user"] host:oldHostnameForPassword database:[currentFavorite objectForKey:@"database"]];

		// If there's no new password, remove the old item from the keychain
		if (![passwordValue length]) {
			[keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];

		// Otherwise, set up the new keychain name and account strings and create or edit the item
		} else {
			newKeychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
			newKeychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:newHostnameForPassword database:[favoritesController valueForKeyPath:@"selection.database"]];
			if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) {
				[keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:passwordValue];
			} else {
				[keychain addPassword:passwordValue forName:newKeychainName account:newKeychainAccount];
			}

		}
		
		// Synch password changes
		[standardPasswordField setStringValue:passwordValue];
		[socketPasswordField setStringValue:passwordValue];
		[sshSQLPasswordField setStringValue:passwordValue];
		
		passwordValue = @"";
	}
	
	// If SSH account/password details have changed, update the keychain to match
	if (![[currentFavorite objectForKey:@"name"] isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]]
		|| ![[currentFavorite objectForKey:@"sshHost"] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshHost"]]
		|| ![[currentFavorite objectForKey:@"sshUser"] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshUser"]]
		|| passwordControl == sshPasswordField) {
		
		// Get the old keychain name and account strings
		oldKeychainName = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:@"name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
		oldKeychainAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:@"sshUser"] sshHost:[currentFavorite objectForKey:@"sshHost"]];
		
		// If there's no new password, delete the keychain item
		if (![[sshPasswordField stringValue] length]) {
			[keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];


		// Otherwise, set up the new keychain name and account strings and create or update the keychain item
		} else {
			newKeychainName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
			newKeychainAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]];
			if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) {
				[keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:[sshPasswordField stringValue]];
			} else {
				[keychain addPassword:[sshPasswordField stringValue] forName:newKeychainName account:newKeychainAccount];
			}
		}
	}
	
	// Update the current favorite
	if (currentFavorite) [currentFavorite release], currentFavorite = nil;
	
	if ([[favoritesTableView selectedRowIndexes] count] > 0)
		currentFavorite = [[[favoritesController selectedObjects] objectAtIndex:0] copy];
}

#pragma mark -

- (void)dealloc
{
	[keychain release], keychain = nil;
	
	if (currentFavorite) [currentFavorite release], currentFavorite = nil;
	
	[super dealloc];
}

@end