From cbb98ff50f0a35cdd4f94d388107046541e45217 Mon Sep 17 00:00:00 2001 From: michael starke Date: Fri, 26 Feb 2016 14:26:09 +0100 Subject: [PATCH] Multiple selections are now handled correctly --- MacPass/MPDocument.h | 4 -- MacPass/MPDocument.m | 62 +++++++----------- MacPass/MPDocumentWindowController.m | 36 +++++++---- MacPass/MPEntryContextMenuDelegate.m | 19 +++--- MacPass/MPEntryTableDataSource.m | 16 ++--- MacPass/MPEntryViewController.m | 96 ++++++++++++++-------------- MacPass/MPOutlineViewController.m | 26 ++++---- MacPass/MPTargetNodeResolving.h | 6 +- 8 files changed, 125 insertions(+), 140 deletions(-) diff --git a/MacPass/MPDocument.h b/MacPass/MPDocument.h index 93d8db2a..54be4f3d 100644 --- a/MacPass/MPDocument.h +++ b/MacPass/MPDocument.h @@ -75,10 +75,6 @@ APPKIT_EXTERN NSString *const MPDocumentGroupKey; /* State (active group/entry) */ -//@property (nonatomic, weak) KPKEntry *selectedEntry; -//@property (nonatomic, weak) KPKGroup *selectedGroup; -//@property (nonatomic, weak) KPKNode *selectedItem; - @property (nonatomic, copy, readonly) NSArray *selectedNodes; @property (nonatomic, copy) NSArray *selectedGroups; diff --git a/MacPass/MPDocument.m b/MacPass/MPDocument.m index c51c58ec..e5091a92 100644 --- a/MacPass/MPDocument.m +++ b/MacPass/MPDocument.m @@ -496,13 +496,6 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey [self _presentTrashAlertForItem:node]; return; } - -// if(node.asGroup == self.selectedGroup) { -// self.selectedGroup = node.asGroup; -// } -// if(node.asEntry == self.selectedEntry) { -// self.selectedEntry = node.asEntry; -// } if(!self.tree.metaData.useTrash) { /* Display warning about permanently removing items! */ @@ -610,35 +603,21 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey } - (BOOL)validateUserInterfaceItem:(id)anItem { - id entryResolver = [NSApp targetForAction:@selector(currentTargetEntry)]; - id groupResolver = [NSApp targetForAction:@selector(currentTargetGroup)]; - id nodeResolver = [NSApp targetForAction:@selector(currentTargetNode)]; + id entryResolver = [NSApp targetForAction:@selector(currentTargetEntries)]; + id groupResolver = [NSApp targetForAction:@selector(currentTargetGroups)]; + id nodeResolver = [NSApp targetForAction:@selector(currentTargetNodes)]; - /* - NSLog(@"entryResolver:%@", [entryResolver class]); - NSLog(@"groupResolver:%@", [groupResolver class]); - NSLog(@"nodeResolver:%@", [nodeResolver class]); - */ - KPKNode *targetNode = [nodeResolver currentTargetNode]; - KPKEntry *targetEntry = [entryResolver currentTargetEntry]; - KPKGroup *targetGroup = [groupResolver currentTargetGroup]; + NSArray *targetNodes = [nodeResolver currentTargetNodes]; + NSArray *targetGroups = [groupResolver currentTargetGroups]; + NSArray *targetEntries = [entryResolver currentTargetEntries]; - /* - if(targetNode.asGroup) { - NSLog(@"targetNode:%@", ((KPKGroup *)targetNode).name); - } - else if(targetNode.asEntry) { - NSLog(@"targetNode:%@", ((KPKEntry *)targetNode).title); - } - - NSLog(@"targetGroup:%@", targetGroup.name); - NSLog(@"tagetEntry:%@", targetEntry.title ); - */ + KPKEntry *targetEntry = targetEntries.count == 1 ? targetEntries.firstObject : nil; + KPKGroup *targetGroup = targetGroups.count == 1 ? targetGroups.firstObject : nil; if(self.encrypted || self.isReadOnly) { return NO; } - BOOL valid = targetNode ? targetNode.isEditable : YES; + BOOL valid = /*targetNode ? targetNode.isEditable : */YES; switch([MPActionHelper typeForAction:[anItem action]]) { case MPActionAddGroup: valid &= (nil != targetGroup); @@ -651,13 +630,17 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey valid &= !targetGroup.isTrashed; break; case MPActionDelete: - valid &= (nil != targetNode); - valid &= (self.trash != targetNode); - valid &= (targetNode != self.tree.root); - //valid &= ![self isItemTrashed:targetNode]; + valid &= targetNodes.count > 0; + for(KPKNode *node in targetNodes) { + valid &= (self.trash != node); + valid &= (node != self.tree.root); + if(!valid) { + break; + } + } break; case MPActionDuplicateEntry: - valid &= (nil != targetEntry); + valid &= targetEntries.count > 0; break; case MPActionEmptyTrash: valid &= ([self.trash.groups count] + [self.trash.entries count]) > 0; @@ -726,13 +709,12 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey #pragma mark - #pragma mark MPTargetNodeResolving -- (KPKEntry *)currentTargetEntry { - return self.selectedEntries.firstObject; - //return self.selectedEntry; +- (NSArray *)currentTargetEntries { + return self.selectedEntries; } -- (KPKGroup *)currentTargetGroup { - return self.selectedGroups.firstObject; +- (NSArray *)currentTargetGroups { + return self.selectedGroups; } @end diff --git a/MacPass/MPDocumentWindowController.m b/MacPass/MPDocumentWindowController.m index dd1856a7..ff874f97 100644 --- a/MacPass/MPDocumentWindowController.m +++ b/MacPass/MPDocumentWindowController.m @@ -334,25 +334,31 @@ typedef void (^MPPasswordChangedBlock)(BOOL didChangePassword); } - (void)createGroup:(id)sender { - id target = [NSApp targetForAction:@selector(currentTargetGroup)]; - KPKGroup *group = [target currentTargetGroup]; + id target = [NSApp targetForAction:@selector(currentTargetGroups)]; + NSArray *groups = [target currentTargetGroups]; MPDocument *document = self.document; - if(!group) { - group = document.root; + if(groups.count == 1) { + [document createGroup:groups.firstObject]; + } + else { + [document createGroup:document.root]; } - [document createGroup:group]; } - (void)createEntry:(id)sender { - id target = [NSApp targetForAction:@selector(currentTargetGroup)]; - KPKGroup *group = [target currentTargetGroup]; - [(MPDocument *)self.document createEntry:group]; + id target = [NSApp targetForAction:@selector(currentTargetGroups)]; + NSArray *groups = [target currentTargetGroups]; + if(groups.count == 1) { + [(MPDocument *)self.document createEntry:groups.firstObject]; + } } - (void)delete:(id)sender { - id target = [NSApp targetForAction:@selector(currentTargetNode)]; - KPKNode *node = [target currentTargetNode]; - [self.document deleteNode:node]; + id target = [NSApp targetForAction:@selector(currentTargetNodes)]; + NSArray *nodes = [target currentTargetNodes]; + for(KPKNode *node in nodes) { + [self.document deleteNode:node]; + } } - (void)pickExpiryDate:(id)sender { @@ -377,9 +383,11 @@ typedef void (^MPPasswordChangedBlock)(BOOL didChangePassword); } - (void)performAutotypeForEntry:(id)sender { - id entryResolver = [NSApp targetForAction:@selector(currentTargetEntry)]; - KPKEntry *targetEntry = [entryResolver currentTargetEntry]; - [[MPAutotypeDaemon defaultDaemon] performAutotypeForEntry:targetEntry]; + id entryResolver = [NSApp targetForAction:@selector(currentTargetEntries)]; + NSArray *entries = [entryResolver currentTargetEntries]; + if(entries.count == 1) { + [[MPAutotypeDaemon defaultDaemon] performAutotypeForEntry:entries.firstObject]; + } } - (void)showInspector:(id)sender { diff --git a/MacPass/MPEntryContextMenuDelegate.m b/MacPass/MPEntryContextMenuDelegate.m index 1a7c2187..eba2c1a1 100644 --- a/MacPass/MPEntryContextMenuDelegate.m +++ b/MacPass/MPEntryContextMenuDelegate.m @@ -45,22 +45,25 @@ static NSUInteger const kMPAttachmentsMenuItem = 2000; [menu removeItem:lastItem]; } /* since we can get opened on the non-selected entry, we have to resolve the target node */ - id entryResolver = [NSApp targetForAction:@selector(currentTargetEntry)]; - KPKEntry *entry = [entryResolver currentTargetEntry]; - - if([entry.customAttributes count] > 0) { + id entryResolver = [NSApp targetForAction:@selector(currentTargetEntries)]; + NSArray *entries = [entryResolver currentTargetEntries]; + if(entries.count != 1) { + return; + } + KPKEntry *entry = entries.lastObject; + if(entry.customAttributes.count > 0) { [menu addItem:[NSMenuItem separatorItem]]; NSMenuItem *attributeItem = [[NSMenuItem alloc] init]; NSMenu *submenu = [[NSMenu alloc] initWithTitle:@"Fields"]; - [attributeItem setTitle:NSLocalizedString(@"COPY_CUSTOM_FIELDS", "Submenu to Copy custom fields")]; - [attributeItem setTag:kMPCustomFieldMenuItem]; + attributeItem.title = NSLocalizedString(@"COPY_CUSTOM_FIELDS", "Submenu to Copy custom fields"); + attributeItem.tag = kMPCustomFieldMenuItem; for (KPKAttribute *attribute in entry.customAttributes) { NSString *title = [NSString stringWithFormat:NSLocalizedString(@"COPY_FIELD_%@", "Mask for title to copy field value"), attribute.key]; NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(copyCustomAttribute:) keyEquivalent:@""]; - [item setTag:[entry.customAttributes indexOfObject:attribute]]; + item.tag = [entry.customAttributes indexOfObject:attribute]; [submenu addItem:item]; } - [attributeItem setSubmenu:submenu]; + attributeItem.submenu = submenu; [menu addItem:attributeItem]; } } diff --git a/MacPass/MPEntryTableDataSource.m b/MacPass/MPEntryTableDataSource.m index dfc58e5d..37be0993 100644 --- a/MacPass/MPEntryTableDataSource.m +++ b/MacPass/MPEntryTableDataSource.m @@ -32,17 +32,13 @@ @implementation MPEntryTableDataSource - (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard { - - if([rowIndexes count] != 1) { - return NO; // No valid drag + NSArray *entries = self.viewController.entryArrayController.selectedObjects; + for(KPKEntry *entry in entries) { + if(![entry isKindOfClass:[KPKEntry class]]) { + return NO; + } } - - id item = [self.viewController.entryArrayController arrangedObjects][[rowIndexes firstIndex]]; - if(![item isKindOfClass:[KPKEntry class]]) { - return NO; - } - KPKEntry *draggedEntry = (KPKEntry *)item; - [pboard writeObjects:@[draggedEntry]]; + [pboard writeObjects:entries]; return YES; } diff --git a/MacPass/MPEntryViewController.m b/MacPass/MPEntryViewController.m index 0e70085c..bb91240c 100644 --- a/MacPass/MPEntryViewController.m +++ b/MacPass/MPEntryViewController.m @@ -256,9 +256,9 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; NSAssert(entry.parent != nil, @"Entry needs to have a parent"); NSString *parentTitleKeyPath = [NSString stringWithFormat:@"%@.%@.%@", - NSStringFromSelector(@selector(objectValue)), - NSStringFromSelector(@selector(parent)), - NSStringFromSelector(@selector(title))]; + NSStringFromSelector(@selector(objectValue)), + NSStringFromSelector(@selector(parent)), + NSStringFromSelector(@selector(title))]; NSString *parentIconImageKeyPath = [NSString stringWithFormat:@"%@.%@.%@", NSStringFromSelector(@selector(objectValue)), NSStringFromSelector(@selector(parent)), @@ -317,14 +317,14 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; else if(isNotesColumn) { NSDictionary *options = @{ NSValueTransformerNameBindingOption : MPStripLineBreaksTransformerName }; NSString *notesKeyPath = [NSString stringWithFormat:@"%@.%@", - NSStringFromSelector(@selector(objectValue)), - NSStringFromSelector(@selector(notes))]; + NSStringFromSelector(@selector(objectValue)), + NSStringFromSelector(@selector(notes))]; [view.textField bind:NSValueBinding toObject:view withKeyPath:notesKeyPath options:options]; } else if(isAttachmentColumn) { NSString *binariesCoundKeyPath = [NSString stringWithFormat:@"%@.%@.@count", - NSStringFromSelector(@selector(objectValue)), - NSStringFromSelector(@selector(binaries))]; + NSStringFromSelector(@selector(objectValue)), + NSStringFromSelector(@selector(binaries))]; [view.textField bind:NSValueBinding toObject:view withKeyPath:binariesCoundKeyPath options:nil]; } } @@ -348,25 +348,22 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; } #pragma mark MPTargetItemResolving -- (KPKEntry *)currentTargetEntry { - NSInteger activeRow = self.entryTable.clickedRow; - /* Fall back to selection e.g. for toolbar actions */ - if(activeRow < 0 ) { - activeRow = self.entryTable.selectedRow; +- (NSArray *)currentTargetEntries { + /*NSInteger activeRow = self.entryTable.clickedRow; + if(activeRow > -1) { + return @[ [self.entryArrayController arrangedObjects][activeRow] ]; } - if(activeRow >= 0 && activeRow <= [self.entryArrayController.arrangedObjects count]) { - return [self.entryArrayController arrangedObjects][activeRow]; - } - return nil; + */ + return self.entryArrayController.selectedObjects; } -- (KPKNode *)currentTargetNode { - KPKEntry *entry = [self currentTargetEntry]; - if(entry) { - return entry; +- (NSArray *)currentTargetNodes { + NSArray *entries = [self currentTargetEntries]; + if(entries.count > 0) { + return entries; } MPDocument *document = self.windowController.document; - return document.selectedNodes.firstObject; + return document.selectedNodes; } #pragma mark MPDocument Notifications @@ -403,13 +400,13 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; document.selectedEntries = self.entryArrayController.selectedObjects; /* - if(document.selectedEntry.parent == document.selectedGroup || document.hasSearch) { - document.selectedItem = document.selectedEntry; - } - else { - document.selectedEntry = nil; - } - */ + if(document.selectedEntry.parent == document.selectedGroup || document.hasSearch) { + document.selectedItem = document.selectedEntry; + } + else { + document.selectedEntry = nil; + } + */ } - (void)_didAddItem:(NSNotification *)notification { @@ -433,13 +430,13 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; - (void)_didExitSearch:(NSNotification *)notification { [[self.entryTable tableColumnWithIdentifier:MPEntryTableParentColumnIdentifier] setHidden:YES]; -// MPDocument *document = [[self windowController] document]; -// document.selectedItem = document.selectedGroup; -// // TODO: really necessary? -// if( nil == document.selectedItem && nil == document.selectedGroup ) { -// [self.entryArrayController unbind:NSContentArrayBinding]; -// [self.entryArrayController setContent:nil]; -// } + // MPDocument *document = [[self windowController] document]; + // document.selectedItem = document.selectedGroup; + // // TODO: really necessary? + // if( nil == document.selectedItem && nil == document.selectedGroup ) { + // [self.entryArrayController unbind:NSContentArrayBinding]; + // [self.entryArrayController setContent:nil]; + // } [self _updateContextBar]; } @@ -452,8 +449,8 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; /* If the document was locked and unlocked we do not need to recheck */ if(document.unlockCount != 1) { /* TODO add another method to display this! - [self.footerInfoText setHidden:![document hasMalformedAutotypeItems]]; - [self.footerInfoText setStringValue:NSLocalizedString(@"DOCUMENT_AUTOTYPE_CORRUPTION_WARNING", "")]; + [self.footerInfoText setHidden:![document hasMalformedAutotypeItems]]; + [self.footerInfoText setStringValue:NSLocalizedString(@"DOCUMENT_AUTOTYPE_CORRUPTION_WARNING", "")]; */ } } @@ -623,21 +620,24 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; #pragma mark Actions - (void)copyPassword:(id)sender { - KPKEntry *selectedEntry = [self currentTargetNode].asEntry; + NSArray *nodes = [self currentTargetNodes]; + KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; if(selectedEntry) { [self _copyToPasteboard:[selectedEntry.password finalValueForEntry:selectedEntry] overlayInfo:MPOverlayInfoPassword name:nil]; } } - (void)copyUsername:(id)sender { - KPKEntry *selectedEntry = [self currentTargetNode].asEntry; + NSArray *nodes = [self currentTargetNodes]; + KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; if(selectedEntry) { [self _copyToPasteboard:[selectedEntry.username finalValueForEntry:selectedEntry] overlayInfo:MPOverlayInfoUsername name:nil]; } } - (void)copyCustomAttribute:(id)sender { - KPKEntry *selectedEntry = [self currentTargetNode].asEntry; + NSArray *nodes = [self currentTargetNodes]; + KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; if(selectedEntry && [selectedEntry isKindOfClass:[KPKEntry class]]) { NSUInteger index = [sender tag]; NSAssert((index >= 0) && (index < [selectedEntry.customAttributes count]), @"Index for custom field needs to be valid"); @@ -647,14 +647,16 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; } - (void)copyURL:(id)sender { - KPKEntry *selectedEntry = [self currentTargetNode].asEntry; + NSArray *nodes = [self currentTargetNodes]; + KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; if(selectedEntry) { [self _copyToPasteboard:[selectedEntry.url finalValueForEntry:selectedEntry] overlayInfo:MPOverlayInfoURL name:nil]; } } - (void)openURL:(id)sender { - KPKEntry *selectedEntry = [self currentTargetNode].asEntry; + NSArray *nodes = [self currentTargetNodes]; + KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; NSString *expandedURL = [selectedEntry.url finalValueForEntry:selectedEntry]; if(expandedURL.length > 0) { NSURL *webURL = [NSURL URLWithString:[expandedURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; @@ -680,13 +682,11 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; } - (void)delete:(id)sender { - KPKEntry *entry = [self currentTargetNode].asEntry; - if(!entry) { - return; - } - + NSArray *entries = [self currentTargetEntries]; MPDocument *document = self.windowController.document; - [document deleteNode:entry]; + for(KPKEntry *entry in entries) { + [document deleteNode:entry]; + } } diff --git a/MacPass/MPOutlineViewController.m b/MacPass/MPOutlineViewController.m index a9cd446a..69ae59f9 100644 --- a/MacPass/MPOutlineViewController.m +++ b/MacPass/MPOutlineViewController.m @@ -134,21 +134,21 @@ NSString *const _MPOutlinveViewHeaderViewIdentifier = @"HeaderCell"; } #pragma mark MPTargetNodeResolving -- (KPKGroup *)currentTargetGroup { - NSInteger row = self.outlineView.clickedRow; - if( row < 0 ) { - row = self.outlineView.selectedRow; - } - return [[self.outlineView itemAtRow:row] representedObject]; +- (NSArray *)currentTargetGroups { + // NSInteger row = self.outlineView.clickedRow; +// if( row > -1 ) { +// return @[ [[self.outlineView itemAtRow:row] representedObject] ]; +// } + return self.treeController.selectedObjects; } -- (KPKNode *)currentTargetNode { - KPKGroup *group = [self currentTargetGroup]; - if(group) { - return group; +- (NSArray *)currentTargetNodes { + NSArray *groups = [self currentTargetGroups]; + if(groups.count > 0) { + return groups; } MPDocument *document = self.windowController.document; - return document.selectedNodes.firstObject; + return document.selectedNodes; } #pragma mark Notifications @@ -184,7 +184,7 @@ NSString *const _MPOutlinveViewHeaderViewIdentifier = @"HeaderCell"; } - (id)itemUnderMouse { - NSPoint mouseLocation = [[self.outlineView window] mouseLocationOutsideOfEventStream]; + NSPoint mouseLocation = [self.outlineView.window mouseLocationOutsideOfEventStream]; NSPoint localPoint = [self.outlineView convertPoint:mouseLocation fromView:[[self.outlineView window] contentView]]; NSInteger row = [self.outlineView rowAtPoint:localPoint]; if(row == -1) { @@ -278,7 +278,7 @@ NSString *const _MPOutlinveViewHeaderViewIdentifier = @"HeaderCell"; if(![document validateUserInterfaceItem:menuItem]) { return NO; } - KPKGroup *group = [self currentTargetNode].asGroup; + KPKGroup *group = [self currentTargetNodes].firstObject.asGroup; return group.isTrash && group.isTrashed; } diff --git a/MacPass/MPTargetNodeResolving.h b/MacPass/MPTargetNodeResolving.h index 28cf37cf..e6ee95c7 100644 --- a/MacPass/MPTargetNodeResolving.h +++ b/MacPass/MPTargetNodeResolving.h @@ -14,8 +14,8 @@ @protocol MPTargetNodeResolving @optional -- (KPKNode *)currentTargetNode; -- (KPKGroup *)currentTargetGroup; -- (KPKEntry *)currentTargetEntry; +- (NSArray *)currentTargetNodes; +- (NSArray *)currentTargetGroups; +- (NSArray *)currentTargetEntries; @end