//
// $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
#import "SPFavoritesPreferencePane.h"
#import "SPFavoriteTextFieldCell.h"
#import "SPPreferenceController.h"
#import "SPKeychain.h"
#import
#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 )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 )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 filename] 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