Multiple selections are now handled correctly

This commit is contained in:
michael starke
2016-02-26 14:26:09 +01:00
parent c4eb499cf6
commit cbb98ff50f
8 changed files with 125 additions and 140 deletions

View File

@@ -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<KPKNode *> *selectedNodes;
@property (nonatomic, copy) NSArray<KPKGroup *> *selectedGroups;

View File

@@ -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<NSValidatedUserInterfaceItem>)anItem {
id<MPTargetNodeResolving> entryResolver = [NSApp targetForAction:@selector(currentTargetEntry)];
id<MPTargetNodeResolving> groupResolver = [NSApp targetForAction:@selector(currentTargetGroup)];
id<MPTargetNodeResolving> nodeResolver = [NSApp targetForAction:@selector(currentTargetNode)];
id<MPTargetNodeResolving> entryResolver = [NSApp targetForAction:@selector(currentTargetEntries)];
id<MPTargetNodeResolving> groupResolver = [NSApp targetForAction:@selector(currentTargetGroups)];
id<MPTargetNodeResolving> 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<KPKEntry *> *)currentTargetEntries {
return self.selectedEntries;
}
- (KPKGroup *)currentTargetGroup {
return self.selectedGroups.firstObject;
- (NSArray<KPKGroup *> *)currentTargetGroups {
return self.selectedGroups;
}
@end

View File

@@ -334,25 +334,31 @@ typedef void (^MPPasswordChangedBlock)(BOOL didChangePassword);
}
- (void)createGroup:(id)sender {
id<MPTargetNodeResolving> target = [NSApp targetForAction:@selector(currentTargetGroup)];
KPKGroup *group = [target currentTargetGroup];
id<MPTargetNodeResolving> 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<MPTargetNodeResolving> target = [NSApp targetForAction:@selector(currentTargetGroup)];
KPKGroup *group = [target currentTargetGroup];
[(MPDocument *)self.document createEntry:group];
id<MPTargetNodeResolving> target = [NSApp targetForAction:@selector(currentTargetGroups)];
NSArray *groups = [target currentTargetGroups];
if(groups.count == 1) {
[(MPDocument *)self.document createEntry:groups.firstObject];
}
}
- (void)delete:(id)sender {
id<MPTargetNodeResolving> target = [NSApp targetForAction:@selector(currentTargetNode)];
KPKNode *node = [target currentTargetNode];
[self.document deleteNode:node];
id<MPTargetNodeResolving> 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<MPTargetNodeResolving> entryResolver = [NSApp targetForAction:@selector(currentTargetEntry)];
KPKEntry *targetEntry = [entryResolver currentTargetEntry];
[[MPAutotypeDaemon defaultDaemon] performAutotypeForEntry:targetEntry];
id<MPTargetNodeResolving> entryResolver = [NSApp targetForAction:@selector(currentTargetEntries)];
NSArray *entries = [entryResolver currentTargetEntries];
if(entries.count == 1) {
[[MPAutotypeDaemon defaultDaemon] performAutotypeForEntry:entries.firstObject];
}
}
- (void)showInspector:(id)sender {

View File

@@ -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<MPTargetNodeResolving> entryResolver = [NSApp targetForAction:@selector(currentTargetEntry)];
KPKEntry *entry = [entryResolver currentTargetEntry];
if([entry.customAttributes count] > 0) {
id<MPTargetNodeResolving> 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];
}
}

View File

@@ -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;
}

View File

@@ -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<KPKEntry *> *)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<KPKNode *> *)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];
}
}

View File

@@ -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<KPKGroup *> *)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<KPKNode *> *)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;
}

View File

@@ -14,8 +14,8 @@
@protocol MPTargetNodeResolving <NSObject>
@optional
- (KPKNode *)currentTargetNode;
- (KPKGroup *)currentTargetGroup;
- (KPKEntry *)currentTargetEntry;
- (NSArray<KPKNode *> *)currentTargetNodes;
- (NSArray<KPKGroup *> *)currentTargetGroups;
- (NSArray<KPKEntry *> *)currentTargetEntries;
@end