diff options
Diffstat (limited to 'Source/SPConnectionControllerDelegate.m')
-rw-r--r-- | Source/SPConnectionControllerDelegate.m | 527 |
1 files changed, 420 insertions, 107 deletions
diff --git a/Source/SPConnectionControllerDelegate.m b/Source/SPConnectionControllerDelegate.m index a2653c5a..6ad83963 100644 --- a/Source/SPConnectionControllerDelegate.m +++ b/Source/SPConnectionControllerDelegate.m @@ -25,88 +25,11 @@ #import "SPConnectionControllerDelegate.h" #import "SPTableTextFieldCell.h" +#import "SPFavoriteNode.h" +#import "SPGroupNode.h" @implementation SPConnectionController (SPConnectionControllerDelegate) -/*#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; - }*/ - #pragma mark - #pragma mark SplitView delegate methods @@ -144,81 +67,471 @@ - (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 { + SPTreeNode *node = (SPTreeNode *)item; + [(SPTableTextFieldCell *)cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - if ([favoritesTable isEnabled]) { - [(SPTableTextFieldCell *)cell setTextColor:[NSColor blackColor]]; + [(SPTableTextFieldCell *)cell setTextColor:([favoritesOutlineView isEnabled]) ? [NSColor blackColor] : [NSColor grayColor]]; + + if (![[node parentNode] parentNode]) { + [(SPTableTextFieldCell *)cell setImage:nil]; } else { - [(SPTableTextFieldCell *)cell setTextColor:[NSColor grayColor]]; - } - - [(SPTableTextFieldCell *)cell setImage:([(SPFavoriteNode *)item nodeIsGroup]) ? nil : [NSImage imageNamed:@"database-small"]]; + [(SPTableTextFieldCell *)cell setImage:(![node isGroup]) ? [NSImage imageNamed:@"database-small"] : folderImage]; + } } - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item { - return ([item nodeIsGroup]) ? 22 : 17; + return ((SPTreeNode *)[[item parentNode] parentNode] == nil) ? 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 [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey]; + } + 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 +{ + NSDragOperation result = NSDragOperationNone; + + 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]; + + // TODO: Fix me + + // 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]; + } + + NSArray *nodes = [self selectedFavoriteNodes]; + + if ([node isGroup]) { + if (index == NSOutlineViewDropOnItemIndex) { + index = 0; + } + else { + SPTreeNode *oldNode = node; + + node = [node parentNode]; + index = ([[node childNodes] indexOfObject:oldNode] + 1); + } + } + else { + if (index == NSOutlineViewDropOnItemIndex) { + index = 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 + +/** + * 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. + */ +- (void)controlTextDidChange:(NSNotification *)notification +{ + 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 { - return (![item nodeIsGroup]); + 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 /** - * Double-Click opens the connection. + * Menu item validation. */ -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - [self initiateConnection:self]; - return NO; + SEL action = [menuItem action]; + + SPTreeNode *node = [self selectedFavoriteNode]; + + if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) { + + // TODO: Fix me + return NO; + + // 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 |