aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPFavoritesPreferencePane.m
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2010-11-03 14:49:58 +0000
committerstuconnolly <stuart02@gmail.com>2010-11-03 14:49:58 +0000
commit14cab5efffa90887562c18517bff68b2ec97d4bd (patch)
treef5ab52406b4c508d776d157c4df6376c03d5c530 /Source/SPFavoritesPreferencePane.m
parent6d5927b835706d28ee8ec24254d7b5c19bf6e5fe (diff)
downloadsequelpro-14cab5efffa90887562c18517bff68b2ec97d4bd.tar.gz
sequelpro-14cab5efffa90887562c18517bff68b2ec97d4bd.tar.bz2
sequelpro-14cab5efffa90887562c18517bff68b2ec97d4bd.zip
Rearchitect preferences, including:
- Split each preference pane into their own controller (subclass of SPPreferencePane, which is a subclass of NSViewController). - Each preference pane controller conforms to the protocol SPPreferencePaneProtocol to allow the main preference controller to build the toolbar. - Move the preferences upgrade function to it's own file. - Add SPFontPreviewTextField which is based on Colloquy's JVFontPreviewField to allow previewing of the selected font in the tables and editor preference panes. - Update localisable strings files.
Diffstat (limited to 'Source/SPFavoritesPreferencePane.m')
-rw-r--r--Source/SPFavoritesPreferencePane.m902
1 files changed, 902 insertions, 0 deletions
diff --git a/Source/SPFavoritesPreferencePane.m b/Source/SPFavoritesPreferencePane.m
new file mode 100644
index 00000000..07405dd3
--- /dev/null
+++ b/Source/SPFavoritesPreferencePane.m
@@ -0,0 +1,902 @@
+//
+// $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"
+
+@interface SPFavoritesPreferencePane (PrivateAPI)
+
+- (void)_sortFavorites;
+- (void)_updateFavoritePasswordsFromField:(NSControl *)passwordControl;
+
+@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 > -1) [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 (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;
+ NSOpenPanel *openPanel = [NSOpenPanel openPanel];
+
+ // 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];
+
+ [openPanel 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];
+ [openPanel 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", @"", nil];
+ [openPanel 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", @"", nil];
+ [openPanel setAccessoryView:sslCACertLocationHelp];
+ }
+
+ [openPanel beginSheetForDirectory:directoryPath
+ file:filePath
+ types:permittedFileTypes
+ modalForWindow:[[self view] window]
+ modalDelegate:self
+ didEndSelector:@selector(chooseKeyLocationSheetDidEnd:returnCode:contextInfo:)
+ contextInfo:sender];
+}
+
+#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];
+ [socketPasswordField setStringValue:passwordValue];
+ [sshSQLPasswordField setStringValue: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"]];
+
+ [sshPasswordField setStringValue:[keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]];
+
+ 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"];
+ [self setSshKeyLocation:abbreviatedFileName];
+
+ // 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 database
+ 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 preference pane name");
+}
+
+- (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 = @"";
+
+ switch (currentSortItem)
+ {
+ case SPFavoritesSortNameItem:
+ sortKey = @"name";
+ break;
+ case SPFavoritesSortHostItem:
+ sortKey = @"host";
+ break;
+ case SPFavoritesSortTypeItem:
+ sortKey = @"type";
+ break;
+ }
+
+ 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"]];
+
+ // Delete the old keychain item
+ [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];
+
+ // Set up the new keychain name and account strings
+ 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"]];
+
+ // Add the new keychain item if the password field has a value
+ if ([passwordValue length])
+ [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"]];
+
+ // Delete the old keychain item
+ [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];
+
+ // Set up the new keychain name and account strings
+ newKeychainName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
+ newKeychainAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]];
+
+ // Add the new keychain item if the password field has a value
+ if ([[sshPasswordField stringValue] length])
+ [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