aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2012-10-06 11:48:15 +0000
committerrowanbeentje <rowan@beent.je>2012-10-06 11:48:15 +0000
commitecb5c70566d1303288e4faf170bda40672a799e1 (patch)
tree2882fa5fd6f25eed9c754e810785f5834225b95f /Source
parentada181f6fe5b010a5ab56030d16b35e92e58af10 (diff)
downloadsequelpro-ecb5c70566d1303288e4faf170bda40672a799e1.tar.gz
sequelpro-ecb5c70566d1303288e4faf170bda40672a799e1.tar.bz2
sequelpro-ecb5c70566d1303288e4faf170bda40672a799e1.zip
Change the connection screen interface, particularly relating to favourite editing on the connection screen (Issue #1339, Issue #1440):
- No longer save changes made to connection favourites as soon as the interface is updated - Alter the interfaace if favourites are editing, offering to save the changes either to the old connection favourite or to a new favourite - Add a "Test connection" button to verify changes before saving - Add a "Quick Connect" entry to the top of the connection view's favourites list so a blank form is always available - Use a custom highlight when editing favourites to show the favourite has changed but is still linked - Reduce the margin on the left-hand side of the connection favourites list to increase the available space - Alter favourite name generation, making it less aggressive when generating names from partial details (eg creating names of just "@") and removing the user - Alter key icon usage to correctly update the button appearance if an SSL or SSH key is selected
Diffstat (limited to 'Source')
-rw-r--r--Source/SPConnectionController.h25
-rw-r--r--Source/SPConnectionController.m581
-rw-r--r--Source/SPConnectionControllerDataSource.m36
-rw-r--r--Source/SPConnectionControllerDelegate.m220
-rw-r--r--Source/SPConnectionControllerInitializer.m37
-rw-r--r--Source/SPConnectionHandler.m61
-rw-r--r--Source/SPFavoriteTextFieldCell.h16
-rw-r--r--Source/SPFavoriteTextFieldCell.m208
-rw-r--r--Source/SPFavoritesOutlineView.m63
9 files changed, 714 insertions, 533 deletions
diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h
index de517fdf..dda04343 100644
--- a/Source/SPConnectionController.h
+++ b/Source/SPConnectionController.h
@@ -42,7 +42,8 @@
SPSplitView
#ifndef SP_REFACTOR /* class decl */
,SPKeychain,
- SPFavoriteNode
+ SPFavoriteNode,
+ SPFavoriteTextFieldCell
#endif
;
@@ -64,6 +65,8 @@
BOOL cancellingConnection;
BOOL isConnecting;
+ BOOL isEditingConnection;
+ BOOL isTestingConnection;
// Standard details
NSInteger previousType;
@@ -141,25 +144,31 @@
IBOutlet NSButton *socketSSLCertificateButton;
IBOutlet NSButton *socketSSLCACertButton;
- IBOutlet NSButton *addToFavoritesButton;
IBOutlet NSButton *connectButton;
+ IBOutlet NSButton *testConnectButton;
IBOutlet NSButton *helpButton;
+ IBOutlet NSButton *saveFavoriteButton;
IBOutlet NSProgressIndicator *progressIndicator;
IBOutlet NSTextField *progressIndicatorText;
IBOutlet NSMenuItem *favoritesSortByMenuItem;
IBOutlet NSView *exportPanelAccessoryView;
-
- BOOL isEditing;
+ IBOutlet NSView *editButtonsView;
+
+ BOOL isEditingItemName;
BOOL reverseFavoritesSort;
BOOL initComplete;
BOOL mySQLConnectionCancelled;
- BOOL favoriteNameFieldWasTouched;
+ BOOL favoriteNameFieldWasAutogenerated;
#ifndef SP_REFACTOR /* ivars */
NSArray *draggedNodes;
NSImage *folderImage;
SPTreeNode *favoritesRoot;
+ SPTreeNode *quickConnectItem;
+
+ SPFavoriteTextFieldCell *quickConnectCell;
+
NSDictionary *currentFavorite;
SPFavoritesController *favoritesController;
SPFavoritesSortItem currentSortItem;
@@ -198,6 +207,7 @@
#endif
@property (readonly, assign) BOOL isConnecting;
+@property (readonly, assign) BOOL isEditingConnection;
// Connection processes
- (IBAction)initiateConnection:(id)sender;
@@ -211,18 +221,19 @@
- (IBAction)updateSSLInterface:(id)sender;
- (IBAction)updateKeyLocationFileVisibility:(id)sender;
- (void)updateSplitViewSize;
+
- (void)resizeTabViewToConnectionType:(NSUInteger)theType animating:(BOOL)animate;
+
- (IBAction)sortFavorites:(id)sender;
- (IBAction)reverseSortFavorites:(NSMenuItem *)sender;
-- (void)resizeTabViewToConnectionType:(NSUInteger)theType animating:(BOOL)animate;
-
// Favorites interaction
- (void)updateFavoriteSelection:(id)sender;
- (NSMutableDictionary *)selectedFavorite;
- (SPTreeNode *)selectedFavoriteNode;
- (NSArray *)selectedFavoriteNodes;
+- (IBAction)saveFavorite:(id)sender;
- (IBAction)addFavorite:(id)sender;
- (IBAction)addFavoriteUsingCurrentDetails:(id)sender;
- (IBAction)addGroup:(id)sender;
diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m
index d6078f1d..a6ca5f75 100644
--- a/Source/SPConnectionController.m
+++ b/Source/SPConnectionController.m
@@ -31,6 +31,7 @@
// More info at <http://code.google.com/p/sequel-pro/>
#import "SPConnectionController.h"
+#import "SPConnectionHandler.h"
#import "SPDatabaseDocument.h"
#ifndef SP_REFACTOR /* headers */
@@ -67,6 +68,10 @@ static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist";
@interface SPConnectionController ()
+// Privately redeclare as read/write to get the synthesized setter
+@property (readwrite, assign) BOOL isEditingConnection;
+
+- (void)_saveCurrentDetailsCreatingNewFavorite:(BOOL)createNewFavorite;
- (BOOL)_checkHost;
#ifndef SP_REFACTOR
- (void)_sortFavorites;
@@ -83,13 +88,22 @@ static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist";
- (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID;
- (NSString *)_stripInvalidCharactersFromString:(NSString *)subject;
-- (void)_updateFavoritePasswordsFromField:(NSControl *)control;
+- (NSString *)_generateNameForConnection;
+
+- (void)_startEditingConnection;
static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, void *key);
#endif
@end
+@interface SPConnectionController (SPConnectionControllerDelegate)
+
+- (void)_stopEditingConnection;
+
+@end
+
+
@implementation SPConnectionController
@synthesize delegate;
@@ -125,6 +139,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
@synthesize connectionSSHKeychainItemAccount;
@synthesize isConnecting;
+@synthesize isEditingConnection;
#pragma mark -
#pragma mark Connection processes
@@ -143,6 +158,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
if (sender == favoritesOutlineView && [favoritesOutlineView clickedRow] <= 0) return;
#endif
+ // If triggered via the "Test Connection" button, set the state - otherwise clear it
+ isTestingConnection = (sender == testConnectButton);
+
// Ensure that host is not empty if this is a TCP/IP or SSH connection
if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) {
SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message"));
@@ -212,9 +230,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
// Disable the favorites outline view to prevent further connections attempts
[favoritesOutlineView setEnabled:NO];
- [addToFavoritesButton setHidden:YES];
[helpButton setHidden:YES];
[connectButton setEnabled:NO];
+ [testConnectButton setEnabled:NO];
[progressIndicator startAnimation:self];
[progressIndicatorText setHidden:NO];
#endif
@@ -226,7 +244,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
// have been changed or not; if not, leave the mark in place and remove the password from the field
// for increased security.
#ifndef SP_REFACTOR
- if (connectionKeychainItemName) {
+ if (connectionKeychainItemName && !isTestingConnection) {
if ([[keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount] isEqualToString:[self password]]) {
[self setPassword:[[NSString string] stringByPaddingToLength:[[self password] length] withString:@"sp" startingAtIndex:0]];
@@ -240,7 +258,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
}
}
- if (connectionSSHKeychainItemName) {
+ if (connectionSSHKeychainItemName && !isTestingConnection) {
if ([[keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount] isEqualToString:[self sshPassword]]) {
[self setSshPassword:[[NSString string] stringByPaddingToLength:[[self sshPassword] length] withString:@"sp" startingAtIndex:0]];
[[sshSSHPasswordField undoManager] removeAllActionsWithTarget:sshSSHPasswordField];
@@ -302,10 +320,15 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
SPTreeNode *node = [self selectedFavoriteNode];
if (node) {
+ if (node == quickConnectItem) {
+ return;
+ }
+
// Only proceed to initiate a connection if a leaf node (i.e. a favorite and not a group) was double clicked.
if (![node isGroup]) {
[self initiateConnection:self];
}
+
// Otherwise start editing the group node's name
else {
[favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES];
@@ -325,6 +348,11 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
keySelectionPanel = [NSOpenPanel openPanel];
[keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]];
+ // If the button was toggled off, ensure editing is ended
+ if ([sender state] == NSOffState) {
+ [self _startEditingConnection];
+ }
+
// Switch details by sender.
// First, SSH keys:
if (sender == sshSSHKeyButton) {
@@ -402,6 +430,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
- (IBAction)updateSSLInterface:(id)sender
{
[self resizeTabViewToConnectionType:[self type] animating:YES];
+ [self _startEditingConnection];
}
/**
@@ -436,7 +465,10 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
- (void)resizeTabViewToConnectionType:(NSUInteger)theType animating:(BOOL)animate
{
NSRect frameRect, targetResizeRect;
- NSInteger additionalFormHeight = 55;
+
+ // Use a magic number which needs to be added to the form when calculating resizes -
+ // including the height of the button areas below.
+ NSInteger additionalFormHeight = 92;
frameRect = [connectionResizeContainer frame];
@@ -529,6 +561,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
currentFavorite = [fav copy];
[connectionResizeContainer setHidden:NO];
+ [self _stopEditingConnection];
// Set up the type, also storing it in the previous type store to prevent type "changes" triggering actions
NSUInteger connectionType = ([fav objectForKey:SPFavoriteTypeKey] ? [[fav objectForKey:SPFavoriteTypeKey] integerValue] : SPTCPIPConnection);
@@ -639,6 +672,14 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
}
/**
+ * Saves the current connection favorite.
+ */
+- (IBAction)saveFavorite:(id)sender
+{
+ [self _saveCurrentDetailsCreatingNewFavorite:NO];
+}
+
+/**
* Adds a new connection favorite.
*/
- (IBAction)addFavorite:(id)sender
@@ -687,7 +728,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
[[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup];
- favoriteNameFieldWasTouched = NO;
+ favoriteNameFieldWasAutogenerated = YES;
[favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES];
}
@@ -698,98 +739,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
*/
- (IBAction)addFavoriteUsingCurrentDetails:(id)sender
{
- NSString *thePassword, *theSSHPassword;
- NSNumber *favoriteid = [self _createNewFavoriteID];
- NSString *favoriteName = [[self name] length] ? [self name] : [NSString stringWithFormat:@"%@@%@", ([self user] && [[self user] length])?[self user] : @"anonymous", (([self type] == SPSocketConnection) ? @"localhost" : [self host])];
-
- if (![[self name] length] && [self database] && ![[self database] isEqualToString:@""]) {
- favoriteName = [NSString stringWithFormat:@"%@ %@", [self database], favoriteName];
- }
-
- // Ensure that host is not empty if this is a TCP/IP or SSH connection
- if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) {
- SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"),
- NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil,
- NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"insufficient details informative message"));
- return;
- }
-
- // If SSH is enabled, ensure that the SSH host is not nil
- if ([self type] == SPSSHTunnelConnection && ![[self sshHost] length]) {
- SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"),
- NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil,
- NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete"));
- return;
- }
-
- // Ensure that a socket connection is not inadvertently used
- if (![self _checkHost]) return;
-
- // Construct the favorite details - cannot use only dictionaryWithObjectsAndKeys for possible nil values.
- NSMutableDictionary *newFavorite = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- [NSNumber numberWithInteger:[self type]], SPFavoriteTypeKey,
- favoriteName, SPFavoriteNameKey,
- favoriteid, SPFavoriteIDKey,
- nil];
-
- // Standard details
- if ([self host]) [newFavorite setObject:[self host] forKey:SPFavoriteHostKey];
- if ([self socket]) [newFavorite setObject:[self socket] forKey:SPFavoriteSocketKey];
- if ([self user]) [newFavorite setObject:[self user] forKey:SPFavoriteUserKey];
- if ([self port]) [newFavorite setObject:[self port] forKey:SPFavoritePortKey];
- if ([self database]) [newFavorite setObject:[self database] forKey:SPFavoriteDatabaseKey];
-
- // SSL details
- if ([self useSSL]) [newFavorite setObject:[NSNumber numberWithInteger:[self useSSL]] forKey:SPFavoriteUseSSLKey];
- [newFavorite setObject:[NSNumber numberWithInteger:[self sslKeyFileLocationEnabled]] forKey:SPFavoriteSSLKeyFileLocationEnabledKey];
- if ([self sslKeyFileLocation]) [newFavorite setObject:[self sslKeyFileLocation] forKey:SPFavoriteSSLKeyFileLocationKey];
- [newFavorite setObject:[NSNumber numberWithInteger:[self sslCertificateFileLocationEnabled]] forKey:SPFavoriteSSLCertificateFileLocationEnabledKey];
- if ([self sslCertificateFileLocation]) [newFavorite setObject:[self sslCertificateFileLocation] forKey:SPFavoriteSSLCertificateFileLocationKey];
- [newFavorite setObject:[NSNumber numberWithInteger:[self sslCACertFileLocationEnabled]] forKey:SPFavoriteSSLCACertFileLocationEnabledKey];
- if ([self sslCACertFileLocation]) [newFavorite setObject:[self sslCACertFileLocation] forKey:SPFavoriteSSLCACertFileLocationKey];
-
- // SSH details
- if ([self sshHost]) [newFavorite setObject:[self sshHost] forKey:SPFavoriteSSHHostKey];
- if ([self sshUser]) [newFavorite setObject:[self sshUser] forKey:SPFavoriteSSHUserKey];
- if ([self sshPort]) [newFavorite setObject:[self sshPort] forKey:SPFavoriteSSHPortKey];
- [newFavorite setObject:[NSNumber numberWithInteger:[self sshKeyLocationEnabled]] forKey:SPFavoriteSSHKeyLocationEnabledKey];
- if ([self sshKeyLocation]) [newFavorite setObject:[self sshKeyLocation] forKey:SPFavoriteSSHKeyLocationKey];
-
- // Add the password to keychain as appropriate
- thePassword = [self password];
-
- if (mySQLConnection && connectionKeychainItemName) {
- thePassword = [keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount];
- }
-
- if (thePassword && (![thePassword isEqualToString:@""])) {
- [keychain addPassword:thePassword
- forName:[keychain nameForFavoriteName:favoriteName id:[NSString stringWithFormat:@"%lld", [favoriteid longLongValue]]]
- account:[keychain accountForUser:[self user] host:(([self type] == SPSocketConnection) ? @"localhost" : [self host]) database:[self database]]];
- }
-
- // Add the SSH password to keychain as appropriate
- theSSHPassword = [self sshPassword];
-
- if (mySQLConnection && connectionSSHKeychainItemName) {
- theSSHPassword = [keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount];
- }
-
- if (theSSHPassword && (![theSSHPassword isEqualToString:@""])) {
- [keychain addPassword:theSSHPassword
- forName:[keychain nameForSSHForFavoriteName:favoriteName id:[NSString stringWithFormat:@"%lld", [favoriteid longLongValue]]]
- account:[keychain accountForSSHUser:[self sshUser] sshHost:[self sshHost]]];
- }
-
- SPTreeNode *selectedNode = [self selectedFavoriteNode];
-
- SPTreeNode *node = [favoritesController addFavoriteNodeWithData:newFavorite asChildOfNode:[selectedNode isGroup] ? selectedNode : nil];
-
- [self _reloadFavoritesViewData];
- [self _selectNode:node];
-
- // Update the favorites popup button in the preferences
- [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup];
+ [self _saveCurrentDetailsCreatingNewFavorite:YES];
}
/**
@@ -805,9 +755,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
[self _reloadFavoritesViewData];
[self _selectNode:node];
-
- isEditing = YES;
-
+
[favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES];
}
@@ -999,26 +947,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
- NSMutableDictionary *selectedFavorite = [self selectedFavorite];
- if (!selectedFavorite) return;
-
- id oldObject = [change objectForKey:NSKeyValueChangeOldKey];
- id newObject = [change objectForKey:NSKeyValueChangeNewKey];
-
- if (oldObject != newObject) {
- [selectedFavorite setObject:![newObject isNSNull] ? newObject : @"" forKey:keyPath];
-
- // Save the new data to disk
- [favoritesController saveFavorites];
-
- [self _reloadFavoritesViewData];
-
- if ([keyPath isEqualToString:SPFavoriteNameKey]) {
- [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self];
- }
- }
}
+
#pragma mark -
#pragma mark Sheet methods
@@ -1082,6 +1013,8 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
[self setSslCACertFileLocation:abbreviatedFileName];
}
+
+ [self _startEditingConnection];
}
/**
@@ -1119,15 +1052,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
if (returnCode == NSAlertAlternateReturn) {
[self setType:SPSocketConnection];
[self setHost:@""];
-#ifndef SP_REFACTOR
- [self _updateFavoritePasswordsFromField:standardSQLHostField];
-#endif
}
else {
[self setHost:@"127.0.0.1"];
-#ifndef SP_REFACTOR
- [self _updateFavoritePasswordsFromField:standardSQLHostField];
-#endif
}
}
@@ -1135,6 +1062,245 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
#pragma mark Private API
/**
+ * Take the current details and either save them to the currently selected
+ * favourite, or create a new connection favourite using them.
+ * If creating a new favourite, also select it and ensure the selected
+ * favourite is visible.
+ */
+- (void)_saveCurrentDetailsCreatingNewFavorite:(BOOL)createNewFavorite
+{
+
+ // Complete any active editing
+ if ([[connectionView window] firstResponder]) {
+ [[connectionView window] endEditingFor:[[connectionView window] firstResponder]];
+ }
+
+
+ // Ensure that host is not empty if this is a TCP/IP or SSH connection
+ if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) {
+ SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"),
+ NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil,
+ NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"insufficient details informative message"));
+ return;
+ }
+
+ // If SSH is enabled, ensure that the SSH host is not nil
+ if ([self type] == SPSSHTunnelConnection && ![[self sshHost] length]) {
+ SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"),
+ NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil,
+ NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete"));
+ return;
+ }
+
+ // Ensure that a socket connection is not inadvertently used
+ if (![self _checkHost]) return;
+
+
+ // Set up the favourite, or get the mutable dictionary for the current favourite.
+ NSMutableDictionary *theFavorite;
+ if (createNewFavorite) {
+ theFavorite = [NSMutableDictionary dictionary];
+ [theFavorite setObject:[self _createNewFavoriteID] forKey:SPFavoriteIDKey];
+ } else {
+ if (!currentFavorite) {
+ [NSException raise:NSInternalInconsistencyException format:@"Tried to save a current favourite with no currentFavorite"];
+ }
+ theFavorite = [self selectedFavorite];
+ }
+
+ // Set the name - either taking the provided name, or generating one.
+ if ([[self name] length]) {
+ [theFavorite setObject:[self name] forKey:SPFavoriteNameKey];
+ } else {
+ NSString *favoriteName = [self _generateNameForConnection];
+ if (!favoriteName) {
+ favoriteName = NSLocalizedString(@"Untitled", @"Name for an untitled connection");
+ }
+ [theFavorite setObject:favoriteName forKey:SPFavoriteNameKey];
+ }
+
+ // Set standard details for the connection
+ [theFavorite setObject:[NSNumber numberWithInteger:[self type]] forKey:SPFavoriteTypeKey];
+ if ([self host]) {
+ [theFavorite setObject:[self host] forKey:SPFavoriteHostKey];
+ }
+ if ([self socket]) {
+ [theFavorite setObject:[self socket] forKey:SPFavoriteSocketKey];
+ }
+ if ([self user]) {
+ [theFavorite setObject:[self user] forKey:SPFavoriteUserKey];
+ }
+ if ([self port]) {
+ [theFavorite setObject:[self port] forKey:SPFavoritePortKey];
+ }
+ if ([self database]) {
+ [theFavorite setObject:[self database] forKey:SPFavoriteDatabaseKey];
+ }
+
+ // SSL details
+ if ([self useSSL]) {
+ [theFavorite setObject:[NSNumber numberWithInteger:[self useSSL]] forKey:SPFavoriteUseSSLKey];
+ }
+ [theFavorite setObject:[NSNumber numberWithInteger:[self sslKeyFileLocationEnabled]] forKey:SPFavoriteSSLKeyFileLocationEnabledKey];
+ if ([self sslKeyFileLocation]) {
+ [theFavorite setObject:[self sslKeyFileLocation] forKey:SPFavoriteSSLKeyFileLocationKey];
+ }
+ [theFavorite setObject:[NSNumber numberWithInteger:[self sslCertificateFileLocationEnabled]] forKey:SPFavoriteSSLCertificateFileLocationEnabledKey];
+ if ([self sslCertificateFileLocation]) {
+ [theFavorite setObject:[self sslCertificateFileLocation] forKey:SPFavoriteSSLCertificateFileLocationKey];
+ }
+ [theFavorite setObject:[NSNumber numberWithInteger:[self sslCACertFileLocationEnabled]] forKey:SPFavoriteSSLCACertFileLocationEnabledKey];
+ if ([self sslCACertFileLocation]) {
+ [theFavorite setObject:[self sslCACertFileLocation] forKey:SPFavoriteSSLCACertFileLocationKey];
+ }
+
+ // SSH details
+ if ([self sshHost]) {
+ [theFavorite setObject:[self sshHost] forKey:SPFavoriteSSHHostKey];
+ }
+ if ([self sshUser]) {
+ [theFavorite setObject:[self sshUser] forKey:SPFavoriteSSHUserKey];
+ }
+ if ([self sshPort]) {
+ [theFavorite setObject:[self sshPort] forKey:SPFavoriteSSHPortKey];
+ }
+ [theFavorite setObject:[NSNumber numberWithInteger:[self sshKeyLocationEnabled]] forKey:SPFavoriteSSHKeyLocationEnabledKey];
+ if ([self sshKeyLocation]) {
+ [theFavorite setObject:[self sshKeyLocation] forKey:SPFavoriteSSHKeyLocationKey];
+ }
+
+
+ /**
+ * Password handling for the SQL connection
+ */
+ NSString *oldKeychainName, *oldKeychainAccount, *newKeychainName, *newKeychainAccount;;
+ NSString *oldHostnameForPassword = ([[currentFavorite objectForKey:SPFavoriteTypeKey] integerValue] == SPSocketConnection) ? @"localhost" : [currentFavorite objectForKey:SPFavoriteHostKey];
+ NSString *newHostnameForPassword = ([self type] == SPSocketConnection) ? @"localhost" : [self host];
+
+ // Grab the password for this connection
+ // Add the password to keychain as appropriate
+ NSString *sqlPassword = [self password];
+ if (mySQLConnection && connectionKeychainItemName) {
+ sqlPassword = [keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount];
+ }
+
+ // If creating a new favourite, always add the password to the keychain if it's set
+ if (createNewFavorite && [sqlPassword length]) {
+ [keychain addPassword:sqlPassword
+ forName:[keychain nameForFavoriteName:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]]
+ account:[keychain accountForUser:[self user] host:newHostnameForPassword database:[self database]]];
+ }
+
+ // If not creating a new favourite...
+ if (!createNewFavorite) {
+
+ // Get the old keychain name and account strings
+ oldKeychainName = [keychain nameForFavoriteName:[currentFavorite objectForKey:SPFavoriteNameKey] id:[currentFavorite objectForKey:SPFavoriteIDKey]];
+ oldKeychainAccount = [keychain accountForUser:[currentFavorite objectForKey:SPFavoriteUserKey] host:oldHostnameForPassword database:[currentFavorite objectForKey:SPFavoriteDatabaseKey]];
+
+ // If there's no new password, remove the old item from the keychain
+ if (![sqlPassword 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:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]];
+ newKeychainAccount = [keychain accountForUser:[self user] host:newHostnameForPassword database:[self database]];
+ if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) {
+ [keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:sqlPassword];
+ } else {
+ [keychain addPassword:sqlPassword forName:newKeychainName account:newKeychainAccount];
+ }
+ }
+ }
+ sqlPassword = nil;
+
+
+ /**
+ * Password handling for the SSH connection
+ */
+ NSString *theSSHPassword = [self sshPassword];
+ if (mySQLConnection && connectionSSHKeychainItemName) {
+ theSSHPassword = [keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount];
+ }
+
+ // If creating a new favourite, always add the password if it's set
+ if (createNewFavorite && [theSSHPassword length]) {
+ [keychain addPassword:theSSHPassword
+ forName:[keychain nameForSSHForFavoriteName:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]]
+ account:[keychain accountForSSHUser:[self sshUser] sshHost:[self sshHost]]];
+ }
+
+ // If not creating a new favourite...
+ if (!createNewFavorite) {
+
+ // Get the old keychain name and account strings
+ oldKeychainName = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:SPFavoriteNameKey] id:[currentFavorite objectForKey:SPFavoriteIDKey]];
+ oldKeychainAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:SPFavoriteSSHUserKey] sshHost:[currentFavorite objectForKey:SPFavoriteSSHHostKey]];
+
+ // If there's no new password, remove the old item from the keychain
+ if (![theSSHPassword 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 nameForSSHForFavoriteName:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]];
+ newKeychainAccount = [keychain accountForSSHUser:[self sshUser] sshHost:[self sshHost]];
+ if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) {
+ [keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:theSSHPassword];
+ } else {
+ [keychain addPassword:theSSHPassword forName:newKeychainName account:newKeychainAccount];
+ }
+ }
+ }
+ theSSHPassword = nil;
+
+
+ /**
+ * Saving the connection
+ */
+
+ // If creating the connection, add to the favourites tree.
+ if (createNewFavorite) {
+ SPTreeNode *selectedNode = [self selectedFavoriteNode];
+ SPTreeNode *parentNode = nil;
+
+ // If the current node is a group node, create the favorite as a child of it
+ if ([selectedNode isGroup]) {
+ parentNode = selectedNode;
+
+ // Otherwise, create the new node as a sibling of the selected node if possible
+ } else if ([selectedNode parentNode] && [selectedNode parentNode] != favoritesRoot) {
+ parentNode = (SPTreeNode *)[selectedNode parentNode];
+ }
+
+ // Add the new node and select it
+ SPTreeNode *newNode = [favoritesController addFavoriteNodeWithData:theFavorite asChildOfNode:parentNode];
+ [self _reloadFavoritesViewData];
+ [self _selectNode:newNode];
+
+ // Update the favorites popup button in the preferences
+ [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup];
+
+ // Otherwise, if editing the favourite, update it
+ } else {
+ [[[self selectedFavoriteNode] representedObject] setNodeFavorite:theFavorite];
+
+ // Save the new data to disk
+ [favoritesController saveFavorites];
+
+ [self _stopEditingConnection];
+
+ if (currentFavorite) [currentFavorite release], currentFavorite = nil;
+ currentFavorite = [theFavorite copy];
+
+ [self _reloadFavoritesViewData];
+ }
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self];
+}
+
+/**
* Check the host field and ensure it isn't set to 'localhost' for non-socket connections.
*/
- (BOOL)_checkHost
@@ -1281,16 +1447,12 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
}
// Update the name for newly added favorites if not already touched by the user, by triggering a KVO update
- if (![[self name] length]) {
- [self setName:[NSString stringWithFormat:@"%@@%@",
- ([favorite objectForKey:SPFavoriteUserKey]) ? [favorite objectForKey:SPFavoriteUserKey] : @"",
- ((previousType == SPSocketConnection) ? @"localhost" :
- (([favorite objectForKey:SPFavoriteHostKey]) ? [favorite valueForKeyPath:SPFavoriteHostKey] : @""))
- ]];
+ if (![[self name] length] || favoriteNameFieldWasAutogenerated) {
+ NSString *favoriteName = [self _generateNameForConnection];
+ if (favoriteName) {
+ [self setName:favoriteName];
+ }
}
-
- // Trigger a password change in response to host changes
- [self _updateFavoritePasswordsFromField:nil];
}
/**
@@ -1350,13 +1512,12 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
[dbDocument setIsProcessing:NO];
// Reset the UI
- [addToFavoritesButton setHidden:NO];
- [addToFavoritesButton display];
[helpButton setHidden:NO];
[helpButton display];
[connectButton setTitle:NSLocalizedString(@"Connect", @"connect button")];
[connectButton setEnabled:YES];
[connectButton display];
+ [testConnectButton setEnabled:YES];
[progressIndicator stopAnimation:self];
[progressIndicator display];
[progressIndicatorText setHidden:YES];
@@ -1463,6 +1624,8 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
SPTreeNode *favoriteNode = nil;
if (!favoritesRoot) return favoriteNode;
+
+ if (!favoriteID) return quickConnectItem;
for (SPTreeNode *node in [favoritesRoot allChildLeafs])
{
@@ -1486,106 +1649,70 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
return [result stringByReplacingOccurrencesOfString:@"\n" withString:@""];
}
-#ifndef SP_REFACTOR
/**
- * 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.
+ * Generate a name for the current connection based on any other populated details.
+ * Currently uses the host and database fields.
+ * If a name cannot be generated because there are insufficient other details, returns nil.
*/
-- (void)_updateFavoritePasswordsFromField:(NSControl *)control
+- (NSString *)_generateNameForConnection
{
- if (!currentFavorite) return;
-
- NSDictionary *oldFavorite = currentFavorite;
- NSDictionary *newFavorite = [[[self selectedFavoriteNode] representedObject] nodeFavorite];
-
- NSString *passwordValue;
- NSString *oldKeychainName, *newKeychainName;
- NSString *oldKeychainAccount, *newKeychainAccount;
- NSString *oldHostnameForPassword = ([[oldFavorite objectForKey:SPFavoriteTypeKey] integerValue] == SPSocketConnection) ? @"localhost" : [oldFavorite objectForKey:SPFavoriteHostKey];
- NSString *newHostnameForPassword = ([[newFavorite objectForKey:SPFavoriteTypeKey] integerValue] == SPSocketConnection) ? @"localhost" : [newFavorite objectForKey:SPFavoriteHostKey];
-
- // 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 (![[oldFavorite objectForKey:SPFavoriteNameKey] isEqualToString:[newFavorite objectForKey:SPFavoriteNameKey]] ||
- ![oldHostnameForPassword isEqualToString:newHostnameForPassword] ||
- ![[oldFavorite objectForKey:SPFavoriteUserKey] isEqualToString:[newFavorite objectForKey:SPFavoriteUserKey]] ||
- ![[oldFavorite objectForKey:SPFavoriteDatabaseKey] isEqualToString:[newFavorite objectForKey:SPFavoriteDatabaseKey]] ||
- control == standardPasswordField || control == socketPasswordField || control == sshPasswordField)
- {
- // Determine the correct password field to read the password from, defaulting to standard
- if (control == socketPasswordField) {
- passwordValue = [socketPasswordField stringValue];
- }
- else if (control == sshPasswordField) {
- passwordValue = [sshPasswordField stringValue];
- }
- else {
- passwordValue = [standardPasswordField stringValue];
- }
-
- // Get the old keychain name and account strings
- oldKeychainName = [keychain nameForFavoriteName:[oldFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]];
- oldKeychainAccount = [keychain accountForUser:[oldFavorite objectForKey:SPFavoriteUserKey] host:oldHostnameForPassword database:[oldFavorite objectForKey:SPFavoriteDatabaseKey]];
-
- // If there's no new password, remove the old item from the keychain
- if (![passwordValue length]) {
- [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];
+ NSString *aName;
- // Otherwise, set up the new keychain name and account strings and create or edit the item
- } else {
- newKeychainName = [keychain nameForFavoriteName:[newFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]];
- newKeychainAccount = [keychain accountForUser:[newFavorite objectForKey:SPFavoriteUserKey] host:newHostnameForPassword database:[newFavorite objectForKey:SPFavoriteDatabaseKey]];
- 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?passwordValue:@""];
- [socketPasswordField setStringValue:passwordValue?passwordValue:@""];
- [sshPasswordField setStringValue:passwordValue?passwordValue:@""];
-
- passwordValue = @"";
+ if ([self type] != SPSocketConnection && ![[self host] length]) {
+ return nil;
}
-
- // If SSH account/password details have changed, update the keychain to match
- if (![[oldFavorite objectForKey:SPFavoriteNameKey] isEqualToString:[newFavorite objectForKey:SPFavoriteNameKey]] ||
- ![[oldFavorite objectForKey:SPFavoriteSSHHostKey] isEqualToString:[newFavorite objectForKey:SPFavoriteSSHHostKey]] ||
- ![[oldFavorite objectForKey:SPFavoriteSSHUserKey] isEqualToString:[newFavorite objectForKey:SPFavoriteSSHUserKey]] ||
- control == sshSSHPasswordField)
- {
- // Get the old keychain name and account strings
- oldKeychainName = [keychain nameForSSHForFavoriteName:[oldFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]];
- oldKeychainAccount = [keychain accountForSSHUser:[oldFavorite objectForKey:SPFavoriteSSHUserKey] sshHost:[oldFavorite objectForKey:SPFavoriteSSHHostKey]];
- // If there's no new password, delete the keychain item
- if (![[sshSSHPasswordField stringValue] length]) {
- [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];
+ aName = ([self type] == SPSocketConnection) ? @"localhost" : [self host];
- // Otherwise, set up the new keychain name and account strings and create or update the keychain item
- } else {
- newKeychainName = [keychain nameForSSHForFavoriteName:[newFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]];
- newKeychainAccount = [keychain accountForSSHUser:[newFavorite objectForKey:SPFavoriteSSHUserKey] sshHost:[newFavorite objectForKey:SPFavoriteSSHHostKey]];
- if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) {
- [keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:[sshSSHPasswordField stringValue]];
- } else {
- [keychain addPassword:[sshSSHPasswordField stringValue] forName:newKeychainName account:newKeychainAccount];
- }
- }
+ if ([[self database] length]) {
+ aName = [NSString stringWithFormat:@"%@/%@", aName, [self database]];
}
-
- // Update the current favorite
- if (currentFavorite) [currentFavorite release], currentFavorite = nil;
-
- if ([[favoritesOutlineView selectedRowIndexes] count]) {
- currentFavorite = [[[[self selectedFavoriteNode] representedObject] nodeFavorite] copy];
+
+ return aName;
+}
+
+
+/**
+ * If editing is not already active, mark editing as starting, triggering UI updates
+ * to match.
+ */
+- (void)_startEditingConnection
+{
+
+ // If not connecting, hide the connection status text to reflect changes
+ if (!isConnecting) {
+ [progressIndicatorText setHidden:YES];
}
+
+ if (isEditingConnection) return;
+
+ // Fade and move the edit button area in
+ [editButtonsView setAlphaValue:0.0];
+ [editButtonsView setHidden:NO];
+ [editButtonsView setFrameOrigin:NSMakePoint([editButtonsView frame].origin.x, [editButtonsView frame].origin.y - 40)];
+ [[editButtonsView animator] setFrameOrigin:NSMakePoint([editButtonsView frame].origin.x, [editButtonsView frame].origin.y + 40)];
+ [[editButtonsView animator] setAlphaValue:1.0];
+
+ // Update the "Save" button state as appropriate
+ [saveFavoriteButton setEnabled:([self selectedFavorite] != nil)];
+
+ // Show the area to allow saving the changes
+ [self setIsEditingConnection:YES];
+ [favoritesOutlineView setNeedsDisplayInRect:[favoritesOutlineView rectOfRow:[favoritesOutlineView selectedRow]]];
+}
+
+/**
+ * If editing is active, mark editing as complete, triggering UI updates to match.
+ */
+- (void)_stopEditingConnection
+{
+ if (!isEditingConnection) return;
+
+ [editButtonsView setHidden:YES];
+ [progressIndicatorText setHidden:YES];
+
+ [self setIsEditingConnection:NO];
}
-#endif
#pragma mark -
@@ -1624,6 +1751,8 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2,
#ifndef SP_REFACTOR
[folderImage release], folderImage = nil;
+ [quickConnectItem release], quickConnectItem = nil;
+ [quickConnectCell release], quickConnectCell = nil;
#endif
for (id retainedObject in nibObjectsToRelease) [retainedObject release];
diff --git a/Source/SPConnectionControllerDataSource.m b/Source/SPConnectionControllerDataSource.m
index 86f6db49..d7f3d235 100644
--- a/Source/SPConnectionControllerDataSource.m
+++ b/Source/SPConnectionControllerDataSource.m
@@ -39,7 +39,6 @@
@interface SPConnectionController ()
- (void)_reloadFavoritesViewData;
-- (void)_updateFavoritePasswordsFromField:(NSControl *)control;
@end
@@ -47,16 +46,42 @@
#ifndef SP_REFACTOR
+/**
+ * Return the number of children for the specified item in the favourites tree.
+ * Note that to support the "Quick Connect" entry, the returned count is amended
+ * for the top level.
+ */
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item);
-
+
+ // If at the root, return the count plus one for the "Quick Connect" entry
+ if (!item) {
+ return [[node childNodes] count] + 1;
+ }
+
return [[node childNodes] count];
}
+/**
+ * Return the branch at the specified index of a supplied tree level.
+ * Note that to support the "Quick Connect" entry, children of the top level
+ * have their offsets amended.
+ */
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)childIndex ofItem:(id)item
{
+
+ // For the top level of the tree, return the "Quick Connect" child for position zero;
+ // amend all other positions to compensate for the faked position.
+ if (!item) {
+ if (childIndex == 0) {
+ return quickConnectItem;
+ }
+ childIndex--;
+ }
+
SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item);
+
return NSArrayObjectAtIndex([node childNodes], childIndex);
}
@@ -83,11 +108,10 @@
SPTreeNode *node = [self selectedFavoriteNode];
if (![node isGroup]) {
+
// Updating the name triggers a KVO update
- [self setName:newName];
-
- // Update associated Keychain items
- [self _updateFavoritePasswordsFromField:nil];
+ [self setName:newName];
+ [self saveFavorite:self];
}
else {
[[node representedObject] setNodeName:newName];
diff --git a/Source/SPConnectionControllerDelegate.m b/Source/SPConnectionControllerDelegate.m
index 30b602cd..e18c43ab 100644
--- a/Source/SPConnectionControllerDelegate.m
+++ b/Source/SPConnectionControllerDelegate.m
@@ -43,19 +43,27 @@
#endif
static NSString *SPDatabaseImage = @"database-small";
+static NSString *SPQuickConnectImage = @"quick-connect-icon.pdf";
+static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf";
@interface SPConnectionController ()
+// Privately redeclare as read/write to get the synthesized setter
+@property (readwrite, assign) BOOL isEditingConnection;
+
- (void)_checkHost;
- (void)_sortFavorites;
- (void)_favoriteTypeDidChange;
- (void)_reloadFavoritesViewData;
-- (void)_updateFavoritePasswordsFromField:(NSControl *)control;
- (NSString *)_stripInvalidCharactersFromString:(NSString *)subject;
+- (void)_startEditingConnection;
+- (void)_stopEditingConnection;
- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification;
+- (NSString *)_generateNameForConnection;
+
@end
@implementation SPConnectionController (SPConnectionControllerDelegate)
@@ -88,25 +96,27 @@ static NSString *SPDatabaseImage = @"database-small";
return ([[(SPTreeNode *)item parentNode] parentNode] == nil);
}
+- (void)outlineViewSelectionIsChanging:(NSNotification *)notification
+{
+ if (isEditingConnection) {
+ [self _stopEditingConnection];
+ [[notification object] setNeedsDisplay:YES];
+ }
+}
+
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
-{
+{
NSInteger selected = [favoritesOutlineView numberOfSelectedRows];
-
- if (selected == 1) {
- SPTreeNode *node = [self selectedFavoriteNode];
-
- [self updateFavoriteSelection:self];
+ if (isEditingConnection) {
+ [self _stopEditingConnection];
+ [[notification object] setNeedsDisplay:YES];
+ }
- if (![node isGroup]) {
- [addToFavoritesButton setEnabled:NO];
+ if (selected == 1) {
+ [self updateFavoriteSelection:self];
- favoriteNameFieldWasTouched = YES;
- }
- else {
- [addToFavoritesButton setEnabled:YES];
- }
-
+ favoriteNameFieldWasAutogenerated = NO;
[connectionResizeContainer setHidden:NO];
[connectionInstructionsTextField setStringValue:NSLocalizedString(@"Enter connection details below, or choose a favorite", @"enter connection details label")];
}
@@ -116,17 +126,58 @@ static NSString *SPDatabaseImage = @"database-small";
}
}
+- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
+{
+ if (item == quickConnectItem) {
+ return (NSCell *)quickConnectCell;
+ }
+
+ return [tableColumn dataCellForRow:[outlineView rowForItem:item]];
+}
+
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
SPTreeNode *node = (SPTreeNode *)item;
-
+
+ // Draw entries with the small system font by default
[(SPTableTextFieldCell *)cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
- [(SPTableTextFieldCell *)cell setImage:(![[node parentNode] parentNode]) ? nil : (![node isGroup]) ? [NSImage imageNamed:SPDatabaseImage] : folderImage];
+
+ // Set an image as appropriate; the quick connect image for that entry, no image for other
+ // top-level items, the folder image for group nodes, or the database image for other nodes.
+ if (![[node parentNode] parentNode]) {
+ if (node == quickConnectItem) {
+ if ([outlineView rowForItem:item] == [outlineView selectedRow]) {
+ [(SPTableTextFieldCell *)cell setImage:[NSImage imageNamed:SPQuickConnectImageWhite]];
+ } else {
+ [(SPTableTextFieldCell *)cell setImage:[NSImage imageNamed:SPQuickConnectImage]];
+ }
+ } else {
+ [(SPTableTextFieldCell *)cell setImage:nil];
+ }
+ } else {
+ if ([node isGroup]) {
+ [(SPTableTextFieldCell *)cell setImage:folderImage];
+ } else {
+ [(SPTableTextFieldCell *)cell setImage:[NSImage imageNamed:SPDatabaseImage]];
+ }
+ }
+
+ // If a favourite item is being edited, draw the text in bold to show state
+ if (isEditingConnection && ![node isGroup] && [outlineView rowForItem:item] == [outlineView selectedRow]) {
+ NSMutableAttributedString *editedCellString = [[cell attributedStringValue] mutableCopy];
+ [editedCellString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.25f alpha:1.f] range:NSMakeRange(0, [editedCellString length])];
+ [cell setAttributedStringValue:editedCellString];
+ [editedCellString release];
+ }
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
- return ([[item parentNode] parentNode]) ? 17 : 22;
+ if (item == quickConnectItem) {
+ return 24.f;
+ }
+
+ return ([[item parentNode] parentNode]) ? 17.f : 22.f;
}
- (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn item:(id)item mouseLocation:(NSPoint)mouseLocation
@@ -170,8 +221,18 @@ static NSString *SPDatabaseImage = @"database-small";
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
-{
- return ([[item parentNode] parentNode] != nil);
+{
+
+ // If this is a top level item, only allow the "Quick Connect" item to be selectable
+ if (![[item parentNode] parentNode]) {
+ if (item == quickConnectItem) {
+ return YES;
+ }
+ return NO;
+ }
+
+ // Otherwise allow all items to be selectable
+ return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item
@@ -184,6 +245,11 @@ static NSString *SPDatabaseImage = @"database-small";
return ([[item parentNode] parentNode] != nil);
}
+- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
+{
+ return (item != quickConnectItem);
+}
+
- (void)outlineViewItemDidCollapse:(NSNotification *)notification
{
[self _setNodeIsExpanded:NO fromNotification:notification];
@@ -210,12 +276,12 @@ static NSString *SPDatabaseImage = @"database-small";
}
// If the user is in the process of changing a node's name, trigger a save and prevent dragging.
- if (isEditing) {
+ if (isEditingItemName) {
[favoritesController saveFavorites];
[self _reloadFavoritesViewData];
- isEditing = NO;
+ isEditingItemName = NO;
return NO;
}
@@ -350,77 +416,60 @@ static NSString *SPDatabaseImage = @"database-small";
#ifndef SP_REFACTOR
/**
- * 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 did not
- * change the 'name' field or delete that field it will be set to user@host automatically.
+ * React to control text changes in the connection interface
*/
- (void)controlTextDidChange:(NSNotification *)notification
{
id field = [notification object];
-
+
+ // If a 'name' field was edited, and is now of zero length, trigger a replacement
+ // with a standard suggestion
if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) {
-
- favoriteNameFieldWasTouched = YES;
-
- NSString *favoriteName = [self _stripInvalidCharactersFromString:[field stringValue]];
-
- BOOL nameFieldIsEmpty = [favoriteName length] == 0;
-
- switch (previousType)
- {
- case SPTCPIPConnection:
- if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && (field == standardUserField || field == standardSQLHostField))) {
- [standardNameField setStringValue:[NSString stringWithFormat:@"%@@%@", [standardUserField stringValue], [standardSQLHostField stringValue]]];
- }
-
- break;
- case SPSocketConnection:
- if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && field == socketUserField)) {
- [socketNameField setStringValue:[NSString stringWithFormat:@"%@@localhost", [socketUserField stringValue]]];
- }
-
- break;
- case SPSSHTunnelConnection:
- if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && (field == sshUserField || field == sshSQLHostField))) {
- [sshNameField setStringValue:[NSString stringWithFormat:@"%@@%@", [sshUserField stringValue], [sshSQLHostField stringValue]]];
- }
-
- break;
+ if (![[self _stripInvalidCharactersFromString:[field stringValue]] length]) {
+ [self controlTextDidEndEditing:notification];
}
-
- // Trigger KVO update
- [self setName:favoriteName];
-
- // If name field is empty enable user@host update
- if (nameFieldIsEmpty) favoriteNameFieldWasTouched = NO;
+ }
+
+ [self _startEditingConnection];
+
+ if (favoriteNameFieldWasAutogenerated) {
+ [self setName:[self _generateNameForConnection]];
}
}
/**
- * When a host field finishes editing, ensure that it hasn't been set to "localhost"
- * to ensure that socket connections don't inadvertently occur.
+ * React to the end of control text changes in the connection interface.
*/
- (void)controlTextDidEndEditing:(NSNotification *)notification
{
- if ([notification object] == standardSQLHostField || [notification object] == sshSQLHostField) {
- [self _checkHost];
+ id field = [notification object];
+
+ // Handle updates to the 'name' field of the selected favourite. The favourite name should
+ // have leading or trailing spaces removed at the end of editing, and if it's left empty,
+ // should have a default name set.
+ if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) {
+
+ NSString *favoriteName = [self _stripInvalidCharactersFromString:[field stringValue]];
+
+ if (![favoriteName length]) {
+ favoriteName = [self _generateNameForConnection];
+ if (favoriteName) {
+ [self setName:favoriteName];
+ }
+
+ // Enable user@host update in reaction to other UI changes
+ favoriteNameFieldWasAutogenerated = YES;
+ } else if (![[field stringValue] isEqualToString:[self name]]) {
+ favoriteNameFieldWasAutogenerated = NO;
+ [self setName:favoriteName];
+ }
}
-}
-/**
- * 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 sync with favorites, but only if a favorite
- // is selected, meaning we're editing an existing one, not a new one.
- if (((id)control != (id)favoritesOutlineView) && ([self selectedFavoriteNode])) {
- [self _updateFavoritePasswordsFromField:control];
+ // When a host field finishes editing, ensure that it hasn't been set to "localhost" to
+ // ensure that socket connections don't inadvertently occur.
+ if (field == standardSQLHostField || field == sshSQLHostField) {
+ [self _checkHost];
}
-
- // Proceed with editing
- return YES;
}
#endif
@@ -442,19 +491,18 @@ static NSString *SPDatabaseImage = @"database-small";
NSInteger selectedTabView = [tabView indexOfTabViewItem:tabViewItem];
if (selectedTabView == previousType) return;
-
+
[self resizeTabViewToConnectionType:selectedTabView animating:YES];
// Update the host as appropriate
if ((selectedTabView != SPSocketConnection) && [[self host] isEqualToString:@"localhost"]) {
[self setHost:@""];
}
-
+
previousType = selectedTabView;
-
- // Enable the add to favorites button
- [addToFavoritesButton setEnabled:YES];
-
+
+ [self _startEditingConnection];
+
[self _favoriteTypeDidChange];
}
@@ -510,7 +558,11 @@ static NSString *SPDatabaseImage = @"database-small";
SPTreeNode *node = [self selectedFavoriteNode];
NSInteger selectedRows = [favoritesOutlineView numberOfSelectedRows];
-
+
+ if (node == quickConnectItem) {
+ return NO;
+ }
+
if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) {
if ([[favoritesRoot allChildLeafs] count] < 2) return NO;
diff --git a/Source/SPConnectionControllerInitializer.m b/Source/SPConnectionControllerInitializer.m
index cee0770a..73ebcad8 100644
--- a/Source/SPConnectionControllerInitializer.m
+++ b/Source/SPConnectionControllerInitializer.m
@@ -33,6 +33,7 @@
#import "SPConnectionControllerInitializer.h"
#import "SPKeychain.h"
#import "SPFavoritesController.h"
+#import "SPFavoriteTextFieldCell.h"
#import "SPTreeNode.h"
#import "SPFavoriteNode.h"
#import "SPGroupNode.h"
@@ -78,13 +79,14 @@ static NSString *SPConnectionViewNibName = @"ConnectionView";
connectionSSHKeychainItemAccount = nil;
initComplete = NO;
- isEditing = NO;
+ isEditingItemName = NO;
isConnecting = NO;
+ isTestingConnection = NO;
sshTunnel = nil;
mySQLConnection = nil;
cancellingConnection = NO;
mySQLConnectionCancelled = NO;
- favoriteNameFieldWasTouched = YES;
+ favoriteNameFieldWasAutogenerated = NO;
[self loadNib];
[self registerForNotifications];
@@ -101,9 +103,8 @@ static NSString *SPConnectionViewNibName = @"ConnectionView";
// Generic folder image for use in the outline view's groups
folderImage = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] retain];
-
[folderImage setSize:NSMakeSize(16, 16)];
-
+
// Set up a keychain instance and preferences reference, and create the initial favorites list
keychain = [[SPKeychain alloc] init];
prefs = [[NSUserDefaults standardUserDefaults] retain];
@@ -111,11 +112,20 @@ static NSString *SPConnectionViewNibName = @"ConnectionView";
// Create a reference to the favorites controller, forcing the data to be loaded from disk
// and the tree to be constructed.
favoritesController = [SPFavoritesController sharedFavoritesController];
-
+
// Tree references
favoritesRoot = [favoritesController favoritesTree];
currentFavorite = nil;
-
+
+ // Create the "Quick Connect" placeholder group
+ quickConnectItem = [[SPTreeNode treeNodeWithRepresentedObject:[SPGroupNode groupNodeWithName:[NSLocalizedString(@"Quick Connect", @"Quick connect item label") uppercaseString]]] retain];
+ [quickConnectItem setIsGroup:YES];
+
+ // Create a NSOutlineView cell for the Quick Connect group
+ quickConnectCell = [[SPFavoriteTextFieldCell alloc] init];
+ [quickConnectCell setDrawsDividerUnderCell:YES];
+ [quickConnectCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+
// Update the UI
[self _reloadFavoritesViewData];
[self setUpFavoritesOutlineView];
@@ -290,13 +300,16 @@ static NSString *SPConnectionViewNibName = @"ConnectionView";
SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:[prefs boolForKey:SPSelectLastFavoriteUsed] ? SPLastFavoriteID : SPDefaultFavorite]];
if (favorite) {
+
+ if (favorite == quickConnectItem) {
+ [self _selectNode:favorite];
+ } else {
+ NSNumber *typeNumber = [[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey];
+ previousType = typeNumber ? [typeNumber integerValue] : SPTCPIPConnection;
- NSNumber *typeNumber = [[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey];
-
- previousType = typeNumber ? [typeNumber integerValue] : SPTCPIPConnection;
-
- [self _selectNode:favorite];
- [self resizeTabViewToConnectionType:[[[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey] integerValue] animating:NO];
+ [self _selectNode:favorite];
+ [self resizeTabViewToConnectionType:[[[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey] integerValue] animating:NO];
+ }
[self _scrollToSelectedNode];
}
diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m
index 5ae8d586..1a85bc50 100644
--- a/Source/SPConnectionHandler.m
+++ b/Source/SPConnectionHandler.m
@@ -45,8 +45,7 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
@interface SPConnectionController ()
- (void)_restoreConnectionInterface;
-
-- (void)_updateFavoritePasswordsFromField:(NSControl *)control;
+- (void)_showConnectionTestResult:(NSString *)resultString;
@end
@@ -58,7 +57,17 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
- (void)initiateMySQLConnection
{
#ifndef SP_REFACTOR
- [progressIndicatorText setStringValue:(sshTunnel) ? NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message") : NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")];
+ if (isTestingConnection) {
+ if (sshTunnel) {
+ [progressIndicatorText setStringValue:NSLocalizedString(@"Testing MySQL...", @"MySQL connection test very short status message")];
+ } else {
+ [progressIndicatorText setStringValue:NSLocalizedString(@"Testing connection...", @"Connection test very short status message")];
+ }
+ } else if (sshTunnel) {
+ [progressIndicatorText setStringValue:NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message")];
+ } else {
+ [progressIndicatorText setStringValue:NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")];
+ }
[progressIndicatorText display];
[connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")];
@@ -200,7 +209,9 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
if ([self database] && ![[self database] isEqualToString:@""]) {
if (![mySQLConnection selectDatabase:[self database]]) {
- [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Could not select database", @"message when database selection failed") errorMessage:[NSString stringWithFormat:NSLocalizedString(@"Connected to host, but unable to connect to database %@.\n\nBe sure that the database exists and that you have the necessary privileges.\n\nMySQL said: %@", @"message of panel when connection to db failed"), [self database], [mySQLConnection lastErrorMessage]] detail:nil rawErrorText:[mySQLConnection lastErrorMessage]];
+ if (!isTestingConnection) {
+ [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Could not select database", @"message when database selection failed") errorMessage:[NSString stringWithFormat:NSLocalizedString(@"Connected to host, but unable to connect to database %@.\n\nBe sure that the database exists and that you have the necessary privileges.\n\nMySQL said: %@", @"message of panel when connection to db failed"), [self database], [mySQLConnection lastErrorMessage]] detail:nil rawErrorText:[mySQLConnection lastErrorMessage]];
+ }
// Tidy up
isConnecting = NO;
@@ -209,6 +220,10 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
[mySQLConnection release], mySQLConnection = nil;
[self _restoreConnectionInterface];
+ if (isTestingConnection) {
+ [self _showConnectionTestResult:NSLocalizedString(@"Invalid database", @"Invalid database very short status message")];
+ }
+
[pool release];
return;
@@ -228,7 +243,11 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
*/
- (void)initiateSSHTunnelConnection
{
- [progressIndicatorText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")];
+ if (isTestingConnection) {
+ [progressIndicatorText setStringValue:NSLocalizedString(@"Testing SSH...", @"SSH testing very short status message")];
+ } else {
+ [progressIndicatorText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")];
+ }
[progressIndicatorText display];
// Trim whitespace and newlines from the SSH host field before attempting to connect
@@ -266,9 +285,9 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
{
isConnecting = NO;
- // If the user hit cancel during the connection attempt, kill the connection once
- // established and reset the UI.
- if (mySQLConnectionCancelled) {
+ // If the user hit cancel during the connection attempt, or a test connection is
+ // occurring, kill the connection once established and reset the UI.
+ if (mySQLConnectionCancelled || isTestingConnection) {
if ([mySQLConnection isConnected]) {
[mySQLConnection disconnect];
[mySQLConnection release], mySQLConnection = nil;
@@ -278,7 +297,11 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
[self cancelConnection];
[self _restoreConnectionInterface];
-
+
+ if (isTestingConnection) {
+ [self _showConnectionTestResult:NSLocalizedString(@"Connection succeeded", @"Connection success very short status message")];
+ }
+
return;
}
@@ -296,7 +319,6 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
[connectButton display];
[progressIndicator stopAnimation:self];
[progressIndicatorText setHidden:YES];
- [addToFavoritesButton setHidden:NO];
#endif
// If SSL was enabled, check it was established correctly
@@ -418,9 +440,8 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
[progressIndicator display];
[progressIndicatorText setHidden:YES];
[progressIndicatorText display];
- [addToFavoritesButton setHidden:NO];
- [addToFavoritesButton display];
[connectButton setEnabled:YES];
+ [testConnectButton setEnabled:YES];
[dbDocument clearStatusIcon];
#endif
@@ -483,7 +504,6 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
// Change connection details
[self setPort:tunnelPort];
[self setHost:SPLocalhostAddress];
- [self _updateFavoritePasswordsFromField:standardSQLHostField];
#ifndef SP_REFACTOR
// Change to standard TCP/IP connection view
@@ -495,4 +515,19 @@ static NSString *SPLocalhostAddress = @"127.0.0.1";
}
}
+/**
+ * Display a connection test error or success message
+ */
+- (void)_showConnectionTestResult:(NSString *)resultString
+{
+ if (![NSThread isMainThread]) {
+ [[self onMainThread] _showConnectionTestResult:resultString];
+ }
+
+ [helpButton setHidden:NO];
+ [progressIndicator stopAnimation:self];
+ [progressIndicatorText setStringValue:resultString];
+ [progressIndicatorText setHidden:NO];
+}
+
@end
diff --git a/Source/SPFavoriteTextFieldCell.h b/Source/SPFavoriteTextFieldCell.h
index c006c9e7..84bee6ba 100644
--- a/Source/SPFavoriteTextFieldCell.h
+++ b/Source/SPFavoriteTextFieldCell.h
@@ -34,20 +34,10 @@
@interface SPFavoriteTextFieldCell : ImageAndTextCell
{
- NSString *favoriteName;
- NSString *favoriteHost;
-
- NSColor *mainStringColor;
- NSColor *subStringColor;
+ BOOL drawsDividerUnderCell;
}
-- (NSString *)favoriteName;
-- (void)setFavoriteName:(NSString *)name;
-
-- (NSString *)favoriteHost;
-- (void)setFavoriteHost:(NSString *)host;
-
-- (void)invertFontColors;
-- (void)restoreFontColors;
+- (BOOL)drawsDividerUnderCell;
+- (void)setDrawsDividerUnderCell:(BOOL)drawsDivider;
@end
diff --git a/Source/SPFavoriteTextFieldCell.m b/Source/SPFavoriteTextFieldCell.m
index e084d173..f9ebd280 100644
--- a/Source/SPFavoriteTextFieldCell.m
+++ b/Source/SPFavoriteTextFieldCell.m
@@ -32,17 +32,6 @@
#import "SPFavoriteTextFieldCell.h"
-#define FAVORITE_NAME_FONT_SIZE 12.0f
-
-@interface SPFavoriteTextFieldCell (PrivateAPI)
-
-- (NSAttributedString *)constructSubStringAttributedString;
-- (NSAttributedString *)attributedStringForFavoriteName;
-- (NSDictionary *)mainStringAttributedStringAttributes;
-- (NSDictionary *)subStringAttributedStringAttributes;
-
-@end
-
@implementation SPFavoriteTextFieldCell
/**
@@ -51,10 +40,7 @@
- (id)init
{
if ((self = [super init])) {
- mainStringColor = [NSColor blackColor];
- subStringColor = [NSColor grayColor];
- favoriteName = nil;
- favoriteHost = nil;
+ drawsDividerUnderCell = NO;
}
return self;
@@ -63,186 +49,64 @@
- (id)copyWithZone:(NSZone *)zone
{
SPFavoriteTextFieldCell *cell = (SPFavoriteTextFieldCell *)[super copyWithZone:zone];
-
- cell->favoriteName = nil;
- if (favoriteName) cell->favoriteName = [favoriteName copyWithZone:zone];
- cell->favoriteHost = nil;
- if (favoriteHost) cell->favoriteHost = [favoriteHost copyWithZone:zone];
+ cell->drawsDividerUnderCell = drawsDividerUnderCell;
return cell;
}
/**
- * Get the cell's favorite name.
- */
-- (NSString *)favoriteName
-{
- return favoriteName;
-}
-
-/**
- * Set the cell's favorite name to the supplied name.
+ * Returns whether this cell is set to draw a divider in the space directly below
+ * the cell (whatever currently populates that space).
*/
-- (void)setFavoriteName:(NSString *)name
+- (BOOL)drawsDividerUnderCell
{
- if (favoriteName != name) {
- [favoriteName release];
- favoriteName = [name retain];
- }
+ return drawsDividerUnderCell;
}
/**
- * Get the cell's favorite host.
+ * Set whether this cell should draw a divider in the space directly below
+ * the cell (whatever currently populates that space).
*/
-- (NSString *)favoriteHost
+- (void)setDrawsDividerUnderCell:(BOOL)drawsDivider
{
- return favoriteHost;
+ drawsDividerUnderCell = drawsDivider;
}
-/**
- * Set the cell's favorite host to the supplied name.
- */
-- (void)setFavoriteHost:(NSString *)host
-{
- if (favoriteHost != host) {
- [favoriteHost release];
- favoriteHost = [host retain];
- }
-}
+#pragma mark -
/**
- * Draws the actual cell.
+ * Draws the actual cell, with a divider if appropriate.
*/
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
- (([self isHighlighted]) && (![[self highlightColorWithFrame:cellFrame inView:controlView] isEqualTo:[NSColor secondarySelectedControlColor]])) ? [self invertFontColors] : [self restoreFontColors];
-
- // Construct and get the sub text attributed string
- NSAttributedString *mainString = [self attributedStringForFavoriteName];
- NSAttributedString *subString = [self constructSubStringAttributedString];
-
- NSRect subFrame = NSMakeRect(0.0f, 0.0f, [subString size].width, [subString size].height);
-
- // Total height of both strings with a 2 pixel separation space
- CGFloat totalHeight = [mainString size].height + [subString size].height + 1.0f;
-
- cellFrame.origin.y += (cellFrame.size.height - totalHeight) / 2.0f;
- cellFrame.origin.x += 10.0f; // Indent main string from image
-
- // Position the sub text's frame rect
- subFrame.origin.y = [mainString size].height + cellFrame.origin.y + 1.0f;
- subFrame.origin.x = cellFrame.origin.x;
-
- cellFrame.size.height = totalHeight;
-
- NSUInteger i;
- CGFloat maxWidth = cellFrame.size.width;
- CGFloat mainStringWidth = [mainString size].width;
- CGFloat subStringWidth = [subString size].width;
-
- // Set a right-padding
- maxWidth -= 10;
-
- if (maxWidth < mainStringWidth) {
- for (i = 0; i <= [mainString length]; i++) {
- if ([[mainString attributedSubstringFromRange:NSMakeRange(0, i)] size].width >= maxWidth && i >= 3) {
- mainString = [[[NSMutableAttributedString alloc] initWithString:[[[mainString attributedSubstringFromRange:NSMakeRange(0, i - 3)] string] stringByAppendingString:@"..."] attributes:[self mainStringAttributedStringAttributes]] autorelease];
- }
- }
- }
-
- if (maxWidth < subStringWidth) {
- for (i = 0; i <= [subString length]; i++) {
- if ([[subString attributedSubstringFromRange:NSMakeRange(0, i)] size].width >= maxWidth && i >= 3) {
- subString = [[[NSMutableAttributedString alloc] initWithString:[[[subString attributedSubstringFromRange:NSMakeRange(0, i - 3)] string] stringByAppendingString:@"..."] attributes:[self subStringAttributedStringAttributes]] autorelease];
- }
- }
- }
-
- [mainString drawInRect:cellFrame];
- [subString drawInRect:subFrame];
-}
+ [super drawInteriorWithFrame:cellFrame inView:controlView];
-- (NSSize)cellSize
-{
- NSSize cellSize = [super cellSize];
- NSAttributedString *mainString = [self attributedStringForFavoriteName];
- NSAttributedString *subString = [self constructSubStringAttributedString];
-
- // 15 := indention 10 from image to string plus 5 px padding
- CGFloat theWidth = MAX([mainString size].width, [subString size].width) + (([self image] != nil) ? [[self image] size].width : 0) + 15;
+ if (drawsDividerUnderCell) {
+ NSRect viewFrame = [controlView frame];
- CGFloat totalHeight = [mainString size].height + [subString size].height + 1.0f;
-
- cellSize.width = theWidth;
- cellSize.height = totalHeight + 13.0f;
- return cellSize;
-}
+ NSPoint startPoint = NSMakePoint(viewFrame.origin.x + 7.f, viewFrame.origin.y);
+ NSPoint endPoint = NSMakePoint(viewFrame.origin.x + viewFrame.size.width - 7.f, viewFrame.origin.y);
-/**
- * Inverts the displayed font colors when the cell is selected.
- */
-- (void)invertFontColors
-{
- mainStringColor = [NSColor whiteColor];
- subStringColor = [NSColor whiteColor];
-}
-
-/**
- * Restores the displayed font colors once the cell is no longer selected.
- */
-- (void)restoreFontColors
-{
- mainStringColor = [NSColor blackColor];
- subStringColor = [NSColor grayColor];
-}
-
-/**
- * Dealloc.
- */
-- (void)dealloc
-{
- [favoriteName release], favoriteName = nil;
- [favoriteHost release], favoriteHost = nil;
-
- [super dealloc];
-}
-
-@end
-
-@implementation SPFavoriteTextFieldCell (PrivateAPI)
-
-/**
- * Constructs the attributed string to be used as the cell's substring.
- */
-- (NSAttributedString *)constructSubStringAttributedString
-{
- return [[[NSAttributedString alloc] initWithString:favoriteHost attributes:[self subStringAttributedStringAttributes]] autorelease];
-}
-
-/**
- * Constructs the attributed string for the cell's favorite name.
- */
-- (NSAttributedString *)attributedStringForFavoriteName
-{
- return [[[NSAttributedString alloc] initWithString:favoriteName attributes:[self mainStringAttributedStringAttributes]] autorelease];
-}
-
-/**
- * Returns the attributes of the cell's main string.
- */
-- (NSDictionary *)mainStringAttributedStringAttributes
-{
- return [NSDictionary dictionaryWithObjectsAndKeys:mainStringColor, NSForegroundColorAttributeName, [NSFont systemFontOfSize:FAVORITE_NAME_FONT_SIZE], NSFontAttributeName, nil];
-}
+ if ([controlView isFlipped]) {
+ startPoint.y += cellFrame.size.height + 8.5f;
+ endPoint.y += cellFrame.size.height + 8.5f;
+ } else {
+ startPoint.y -= cellFrame.size.height + 8.5f;
+ endPoint.y -= cellFrame.size.height + 8.5f;
+ }
-/**
- * Returns the attributes of the cell's sub string.
- */
-- (NSDictionary *)subStringAttributedStringAttributes
-{
- return [NSDictionary dictionaryWithObjectsAndKeys:subStringColor, NSForegroundColorAttributeName, [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, nil];
+ [NSGraphicsContext saveGraphicsState];
+ [[NSColor gridColor] set];
+ NSShadow *lineGlow = [[NSShadow alloc] init];
+ [lineGlow setShadowBlurRadius:1];
+ [lineGlow setShadowColor:[[NSColor controlLightHighlightColor] colorWithAlphaComponent:0.75f]];
+ [lineGlow setShadowOffset:NSMakeSize(0, -1)];
+ [lineGlow set];
+ [NSBezierPath strokeLineFromPoint:startPoint toPoint:endPoint];
+ [lineGlow release];
+ [NSGraphicsContext restoreGraphicsState];
+ }
}
-@end
+@end \ No newline at end of file
diff --git a/Source/SPFavoritesOutlineView.m b/Source/SPFavoritesOutlineView.m
index 883896ef..08880ec8 100644
--- a/Source/SPFavoritesOutlineView.m
+++ b/Source/SPFavoritesOutlineView.m
@@ -31,6 +31,9 @@
// More info at <http://code.google.com/p/sequel-pro/>
#import "SPFavoritesOutlineView.h"
+#import "SPConnectionControllerDelegate.h"
+
+static NSUInteger SPFavoritesOutlineViewUnindent = 14;
@implementation SPFavoritesOutlineView
@@ -88,4 +91,64 @@
}
}
+/**
+ * Don't reserve a gap for the disclosure triangles for top-level items. This involves reducing the
+ * padding - and increasing the width - of all rows to compensate.
+ */
+- (NSRect)frameOfCellAtColumn:(NSInteger)columnIndex row:(NSInteger)rowIndex
+{
+ NSRect superFrame = [super frameOfCellAtColumn:columnIndex row:rowIndex];
+
+ return NSMakeRect(superFrame.origin.x - SPFavoritesOutlineViewUnindent, superFrame.origin.y, superFrame.size.width + SPFavoritesOutlineViewUnindent, superFrame.size.height);
+}
+
+/**
+ * As no gap is reserved for the disclosure triangles at the top level, the frames for other
+ * disclosure items need to be similarly moved.
+ */
+- (NSRect)frameOfOutlineCellAtRow:(NSInteger)rowIndex
+{
+ NSRect superFrame = [super frameOfOutlineCellAtRow:rowIndex];
+
+ if (superFrame.origin.x > SPFavoritesOutlineViewUnindent) {
+ return NSMakeRect(superFrame.origin.x - SPFavoritesOutlineViewUnindent, superFrame.origin.y, superFrame.size.width, superFrame.size.height);
+ }
+
+ return superFrame;
+}
+
+
+/**
+ * If the delegate is a SPConnectionControllerDelegate, and editing is currently in
+ * progress, draw a custom highlight.
+ */
+- (void)highlightSelectionInClipRect:(NSRect)clipRect
+{
+
+ // Only proceed if a the delegate is a SPConnectionControllerDelegate and a favoruite being edited
+ if ([[self delegate] isKindOfClass:[SPConnectionController class]]
+ && [(SPConnectionController *)[self delegate] isEditingConnection]
+ && [(SPConnectionController *)[self delegate] selectedFavorite])
+ {
+
+ // Draw an editing dot instead of highlighting the whole row
+ NSRect rowRect = [self rectOfRow:[self selectedRow]];
+ float dotSize = rowRect.size.height / 1.9;
+ NSRect dotRect = NSMakeRect(9.f, rowRect.origin.y + ((rowRect.size.height - dotSize) / 2), dotSize, dotSize);
+ [NSGraphicsContext saveGraphicsState];
+
+ NSBezierPath *clipPath = [NSBezierPath bezierPath];
+ [clipPath appendBezierPathWithOvalInRect:dotRect];
+ [clipPath addClip];
+
+ NSGradient *dotGradient = [[[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceRed:0.44f green:0.72f blue:0.92f alpha:1.f] endingColor:[NSColor colorWithDeviceRed:0.21f green:0.53f blue:0.82f alpha:1.f]] autorelease];
+ [dotGradient drawInRect:dotRect angle:90.f];
+
+ [NSGraphicsContext restoreGraphicsState];
+ return;
+ }
+
+ [super highlightSelectionInClipRect:clipRect];
+}
+
@end