aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPConnectionControllerDelegate.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/SPConnectionControllerDelegate.m')
-rw-r--r--Source/SPConnectionControllerDelegate.m528
1 files changed, 418 insertions, 110 deletions
diff --git a/Source/SPConnectionControllerDelegate.m b/Source/SPConnectionControllerDelegate.m
index 665119da..3318958e 100644
--- a/Source/SPConnectionControllerDelegate.m
+++ b/Source/SPConnectionControllerDelegate.m
@@ -25,87 +25,12 @@
#import "SPConnectionControllerDelegate.h"
#import "SPTableTextFieldCell.h"
+#import "SPFavoriteNode.h"
+#import "SPGroupNode.h"
-@implementation SPConnectionController (SPConnectionControllerDelegate)
+#define CELL(cell) (SPTableTextFieldCell *)cell
-/*#pragma mark -
- #pragma mark TableView drag & drop delegate methods
-
- - (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
- {
- NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
- [pboard declareTypes:[NSArray arrayWithObject:favoritesPBoardType] owner:self];
- [pboard setData:archivedData forType:favoritesPBoardType];
- return YES;
- }
-
- - (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation
- {
- if (row == 0) return NSDragOperationNone;
- if ([info draggingSource] == aTableView)
- {
- [aTableView setDropRow:row dropOperation:NSTableViewDropAbove];
- return NSDragOperationMove;
- }
- return NSDragOperationNone;
- }
-
- - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
- {
- BOOL acceptedDrop = NO;
-
- if ((row == 0) || ([info draggingSource] != aTableView)) return acceptedDrop;
-
- // Disable all automatic sorting
- currentSortItem = -1;
- reverseFavoritesSort = NO;
-
- [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy];
- [prefs setBool:NO forKey:SPFavoritesSortedInReverse];
-
- // Remove sort descriptors
- [favorites sortUsingDescriptors:[NSArray array]];
-
- // Uncheck sort by menu items
- for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray])
- {
- [menuItem setState:NSOffState];
- }
-
- NSPasteboard* pboard = [info draggingPasteboard];
- NSData* rowData = [pboard dataForType:favoritesPBoardType];
- NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
- NSInteger dragRow = [rowIndexes firstIndex];
- NSInteger defaultConnectionRow = [prefs integerForKey:SPLastFavoriteIndex];
- if (defaultConnectionRow == dragRow)
- {
- [prefs setInteger:row forKey:SPLastFavoriteIndex];
- }
- NSMutableDictionary *draggedFavorite = [favorites objectAtIndex:dragRow];
- [favorites removeObjectAtIndex:dragRow];
- if (row > dragRow)
- {
- row--;
- }
- [favorites insertObject:draggedFavorite atIndex:row];
- [aTableView reloadData];
-
- // reset the prefs with the new order
- NSMutableArray *reorderedFavorites = [[NSMutableArray alloc] initWithArray:favorites];
- [reorderedFavorites removeObjectAtIndex:0];
- [prefs setObject:reorderedFavorites forKey:SPFavorites];
-
- [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup];
-
- [reorderedFavorites release];
-
- [self updateFavorites];
- [aTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
-
- acceptedDrop = YES;
-
- return acceptedDrop;
- }*/
+@implementation SPConnectionController (SPConnectionControllerDelegate)
#pragma mark -
#pragma mark SplitView delegate methods
@@ -117,9 +42,9 @@
/**
* When the split view is resized, trigger a resize in the hidden table
- * width as well, to keep the connection view and connected view in synch.
+ * width as well, to keep the connection view and connected view in sync.
*/
-- (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
+- (void)splitViewDidResizeSubviews:(NSNotification *)notification
{
[databaseConnectionView setPosition:[[[connectionSplitView subviews] objectAtIndex:0] frame].size.width ofDividerAtIndex:0];
}
@@ -129,81 +54,464 @@
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
- SPFavoriteNode *node = (item == nil ? favoritesRoot : (SPFavoriteNode *)item);
+ SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item);
- return [[node nodeChildren] count];
+ return [[node childNodes] count];
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
- SPFavoriteNode *node = (item == nil ? favoritesRoot : (SPFavoriteNode *)item);
+ SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item);
- return NSArrayObjectAtIndex([node nodeChildren], index);
+ return NSArrayObjectAtIndex([node childNodes], index);
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
- return [(SPFavoriteNode *)item nodeIsGroup];
+ return [(SPTreeNode *)item isGroup];
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
- SPFavoriteNode *node = (SPFavoriteNode *)item;
+ SPTreeNode *node = (SPTreeNode *)item;
+
+ return (![node isGroup]) ? [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey] : [[node representedObject] nodeName];
+}
+
+- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
+{
+ // Trim whitespace
+ NSString *newName = [object stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- return ([node nodeIsGroup]) ? [node nodeName] : [[node nodeFavorite] objectForKey:SPFavoriteNameKey];
+ if ([newName length]) {
+
+ // Get the node that was renamed
+ SPTreeNode *node = [self selectedFavoriteNode];
+
+ if (![node isGroup]) {
+ // Updating the name triggers a KVO update
+ [self setName:newName];
+
+ // Update associated Keychain items
+ [self _updateFavoritePasswordsFromField:nil];
+ }
+ else {
+ [[node representedObject] setNodeName:newName];
+
+ [favoritesController saveFavorites];
+
+ [self _reloadFavoritesViewData];
+ }
+ }
}
#pragma mark -
#pragma mark Outline view delegate methods
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
-{
- return [(SPFavoriteNode *)item nodeIsGroup];
+{
+ return ([[(SPTreeNode *)item parentNode] parentNode] == nil);
}
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
-{
- if ([favoritesTable numberOfSelectedRows] == 1) {
- [self updateFavoriteSelection:self];
+{
+ NSInteger selected = [favoritesOutlineView numberOfSelectedRows];
+
+ if (selected == 1) {
+
+ SPTreeNode *node = [self selectedFavoriteNode];
- [addToFavoritesButton setEnabled:NO];
- }
- else {
- [addToFavoritesButton setEnabled:YES];
+ if (![node isGroup]) {
+ [self updateFavoriteSelection:self];
+
+ [addToFavoritesButton setEnabled:NO];
+
+ favoriteNameFieldWasTouched = YES;
+
+ [connectionResizeContainer setHidden:NO];
+ [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Enter connection details below, or choose a favorite", @"enter connection details label")];
+ }
+ else {
+ [connectionResizeContainer setHidden:YES];
+ [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Please choose a favorite", @"please choose a favorite connection view label")];
+ }
+ }
+ else if (selected > 1) {
+ [connectionResizeContainer setHidden:YES];
+ [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Please choose a favorite", @"please choose a favorite connection view label")];
}
}
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
- [(SPTableTextFieldCell *)cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ SPTreeNode *node = (SPTreeNode *)item;
+
+ [CELL(cell) setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+
+ [CELL(cell) setTextColor:([favoritesOutlineView isEnabled]) ? [NSColor blackColor] : [NSColor grayColor]];
- if ([favoritesTable isEnabled]) {
- [(SPTableTextFieldCell *)cell setTextColor:[NSColor blackColor]];
+ if (![[node parentNode] parentNode]) {
+ [CELL(cell) setImage:nil];
}
else {
- [(SPTableTextFieldCell *)cell setTextColor:[NSColor grayColor]];
- }
-
- [(SPTableTextFieldCell *)cell setImage:([(SPFavoriteNode *)item nodeIsGroup]) ? nil : [NSImage imageNamed:@"database-small"]];
+ [CELL(cell) setImage:(![node isGroup]) ? [NSImage imageNamed:@"database-small"] : folderImage];
+ }
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
- return ([item nodeIsGroup]) ? 22 : 17;
+ return (![[item parentNode] parentNode]) ? 22 : 17;
+}
+
+- (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn item:(id)item mouseLocation:(NSPoint)mouseLocation
+{
+ SPTreeNode *node = (SPTreeNode *)item;
+
+ if (![node isGroup]) {
+ return [NSString stringWithFormat:@"%@ (%@)", [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey], [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteHostKey]];
+ }
+ else {
+ NSUInteger favCount = [[node childNodes] count];
+
+ return [NSString stringWithFormat:@"%@ - %d %@", [[node representedObject] nodeName], favCount, (favCount == 1) ? NSLocalizedString(@"favorite", @"favorite singular label") : NSLocalizedString(@"favorites", @"favorites plural label")];
+ }
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
+{
+ return ([[item parentNode] parentNode] != nil);
+}
+
+#pragma mark -
+#pragma mark Outline view drag & drop
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
+{
+ [pboard declareTypes:[NSArray arrayWithObject:SPFavoritesPasteboardDragType] owner:self];
+ [pboard setData:[NSData data] forType:SPFavoritesPasteboardDragType];
+
+ return YES;
+}
+
+- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
- return (![item nodeIsGroup]);
+ NSDragOperation result = NSDragOperationNone;
+
+ // Prevent dropping favorites on other favorites (non-groups)
+ if ((index == NSOutlineViewDropOnItemIndex) && (![item isGroup])) return result;
+
+ if ([info draggingSource] == outlineView) {
+ [outlineView setDropItem:item dropChildIndex:index];
+
+ result = NSDragOperationMove;
+ }
+
+ return result;
}
+- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
+{
+ BOOL acceptedDrop = NO;
+
+ if ((!item) || ([info draggingSource] != outlineView)) return acceptedDrop;
+
+ SPTreeNode *node = (item) ? item : [[[[favoritesRoot childNodes] objectAtIndex:0] childNodes] objectAtIndex:0];
+
+ // Disable all automatic sorting
+ currentSortItem = -1;
+ reverseFavoritesSort = NO;
+
+ [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy];
+ [prefs setBool:NO forKey:SPFavoritesSortedInReverse];
+
+ // Uncheck sort by menu items
+ for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray])
+ {
+ [menuItem setState:NSOffState];
+ }
+
+ NSArray *nodes = [self selectedFavoriteNodes];
+
+ if ([node isGroup]) {
+ if (index == NSOutlineViewDropOnItemIndex) {
+ index = 0;
+ }
+ }
+ else {
+ if (index == NSOutlineViewDropOnItemIndex) {
+ index = 0;
+ }
+ }
+
+ if (![[node representedObject] nodeName]) {
+ node = [[favoritesRoot childNodes] objectAtIndex:0];
+ }
+
+ NSMutableArray *childNodeArray = [node mutableChildNodes];
+
+ for (SPTreeNode *treeNode in nodes)
+ {
+ // Remove the node from its old location
+ NSInteger oldIndex = [childNodeArray indexOfObject:treeNode];
+ NSInteger newIndex = index;
+
+ if (oldIndex != NSNotFound) {
+
+ [childNodeArray removeObjectAtIndex:oldIndex];
+
+ if (index > oldIndex) {
+ newIndex--;
+ }
+ }
+ else {
+ [[[treeNode parentNode] mutableChildNodes] removeObject:treeNode];
+ }
+
+ [childNodeArray insertObject:treeNode atIndex:newIndex];
+
+ newIndex++;
+ }
+
+ [favoritesController saveFavorites];
+
+ [self _reloadFavoritesViewData];
+
+ [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup];
+
+ acceptedDrop = YES;
+
+ return acceptedDrop;
+}
+
+#pragma mark -
+#pragma mark Textfield delegate methods
/**
- * Double-Click opens the connection.
+ * 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.
*/
-- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
+- (void)controlTextDidChange:(NSNotification *)notification
{
- if (!isConnecting) [self initiateConnection:self];
- return NO;
+ id field = [notification object];
+
+ if ([self selectedFavoriteNode]) {
+
+ BOOL nameFieldIsEmpty = [[field stringValue] isEqualToString:@""];
+
+ switch (previousType)
+ {
+ case SPTCPIPConnection:
+
+ nameFieldIsEmpty = (nameFieldIsEmpty || [[standardNameField stringValue] isEqualToString:@""]);
+
+ if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && (field == standardUserField || field == standardSQLHostField))) {
+ [standardNameField setStringValue:[NSString stringWithFormat:@"%@@%@", [standardUserField stringValue], [standardSQLHostField stringValue]]];
+
+ // Trigger KVO update
+ [self setName:[standardNameField stringValue]];
+
+ // If name field is empty enable user@host update
+ if (nameFieldIsEmpty) favoriteNameFieldWasTouched = NO;
+ }
+
+ break;
+ case SPSocketConnection:
+
+ nameFieldIsEmpty = (nameFieldIsEmpty || [[socketNameField stringValue] isEqualToString:@""]);
+
+ if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && field == socketUserField)) {
+ [socketNameField setStringValue:[NSString stringWithFormat:@"%@@localhost", [socketUserField stringValue]]];
+
+ // Trigger KVO update
+ [self setName:[socketNameField stringValue]];
+
+ // If name field is empty enable user@host update
+ if (nameFieldIsEmpty) favoriteNameFieldWasTouched = NO;
+ }
+
+ break;
+ case SPSSHTunnelConnection:
+
+ nameFieldIsEmpty = (nameFieldIsEmpty || [[sshNameField stringValue] isEqualToString:@""]);
+
+ if (nameFieldIsEmpty || (!favoriteNameFieldWasTouched && (field == sshUserField || field == sshSQLHostField))) {
+ [sshNameField setStringValue:[NSString stringWithFormat:@"%@@%@", [sshUserField stringValue], [sshSQLHostField stringValue]]];
+
+ // Trigger KVO update
+ [self setName:[sshNameField stringValue]];
+
+ // If name field is empty enable user@host update
+ if (nameFieldIsEmpty) favoriteNameFieldWasTouched = NO;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ if ((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) favoriteNameFieldWasTouched = YES;
+ }
+}
+
+/**
+ * When a host field finishes editing, ensure that it hasn't been set to "localhost"
+ * to ensure that socket connections don't inadvertently occur.
+ */
+- (void)controlTextDidEndEditing:(NSNotification *)notification
+{
+ if ([notification object] == standardSQLHostField || [notification object] == sshSQLHostField) {
+ [self _checkHost];
+ }
+}
+
+/**
+ * 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, but only if a favorite
+ // is selected, meaning we're editing an existing one, not a new one.
+ if ((control != favoritesOutlineView) && ([self selectedFavoriteNode])) {
+ [self _updateFavoritePasswordsFromField:control];
+ }
+
+ // Proceed with editing
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Tab bar delegate methods
+
+/**
+ * Trigger a resize action whenever the tab view changes. The connection
+ * detail forms are held within container views, which are of a fixed width;
+ * the tabview and buttons are contained within a resizable view which
+ * is set to dimensions based on the container views, allowing the view
+ * to be sized according to the detail type.
+ */
+- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ NSInteger selectedTabView = [tabView indexOfTabViewItem:tabViewItem];
+
+ // Deselect any selected favorite for manual changes
+ if (!automaticFavoriteSelection) [favoritesOutlineView deselectAll:self];
+ automaticFavoriteSelection = NO;
+
+ 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 _favoriteTypeDidChange];
+}
+
+#pragma mark -
+#pragma mark Scroll view notifications
+
+/**
+ * As the scrollview resizes, keep the details centered within it if
+ * the detail frame is larger than the scrollview size; otherwise, pin
+ * the detail frame to the top of the scrollview.
+ */
+- (void)scrollViewFrameChanged:(NSNotification *)aNotification
+{
+ NSRect scrollViewFrame = [connectionDetailsScrollView frame];
+ NSRect scrollDocumentFrame = [[connectionDetailsScrollView documentView] frame];
+ NSRect connectionDetailsFrame = [connectionResizeContainer frame];
+
+ // Scroll view is smaller than contents - keep positioned at top.
+ if (scrollViewFrame.size.height < connectionDetailsFrame.size.height + 10) {
+ if (connectionDetailsFrame.origin.y != 0) {
+ connectionDetailsFrame.origin.y = 0;
+ [connectionResizeContainer setFrame:connectionDetailsFrame];
+ scrollDocumentFrame.size.height = connectionDetailsFrame.size.height + 10;
+ [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame];
+ }
+ }
+ // Otherwise, center
+ else {
+ connectionDetailsFrame.origin.y = (scrollViewFrame.size.height - connectionDetailsFrame.size.height)/3;
+ [connectionResizeContainer setFrame:connectionDetailsFrame];
+ scrollDocumentFrame.size.height = scrollViewFrame.size.height;
+ [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame];
+ }
}
+
+#pragma mark -
+#pragma mark Menu Validation
+
+/**
+ * Menu item validation.
+ */
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
+{
+ SEL action = [menuItem action];
+
+ SPTreeNode *node = [self selectedFavoriteNode];
+
+ if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) {
+
+ // 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) ? NSOnState : NSOffState];
+ }
+
+ // Check or uncheck the reverse sort item
+ if (action == @selector(reverseSortFavorites:)) {
+ [menuItem setState:reverseFavoritesSort];
+ }
+ }
+
+ // Remove the selected favorite
+ if (action == @selector(removeNode:)) {
+ return ([favoritesOutlineView numberOfSelectedRows] == 1);
+ }
+
+ // Duplicate and make the selected favorite the default
+ if (action == @selector(duplicateFavorite:)) {
+ return (([favoritesOutlineView numberOfSelectedRows] == 1) && (![node isGroup]));
+ }
+
+ // Make selected favorite the default
+ if (action == @selector(makeSelectedFavoriteDefault:)) {
+ NSInteger favoriteID = [[[self selectedFavorite] objectForKey:SPFavoriteIDKey] integerValue];
+
+ return (([favoritesOutlineView numberOfSelectedRows] == 1) && (![node isGroup]) && (favoriteID != [prefs integerForKey:SPDefaultFavorite]));
+ }
+
+ // Rename selected favorite/group
+ if (action == @selector(renameFavorite:)) {
+ return ([favoritesOutlineView numberOfSelectedRows] == 1);
+ }
+
+ // Favorites export
+ if (action == @selector(exportFavorites:)) {
+
+ NSInteger rows = [favoritesOutlineView numberOfSelectedRows];
+
+ if (rows > 1) {
+ [menuItem setTitle:NSLocalizedString(@"Export Selected...", @"export selected favorites menu item")];
+ }
+ else if (rows == 1) {
+ return (![[self selectedFavoriteNode] isGroup]);
+ }
+
+ return YES;
+ }
+
+ return YES;
+}
+
@end