From cd4723774223f60784326a2af00e95a88c6dde1a Mon Sep 17 00:00:00 2001 From: michael starke Date: Mon, 29 Aug 2016 17:44:32 +0200 Subject: [PATCH] More improvements on modelchangeobserving --- Cartfile | 2 +- Cartfile.resolved | 2 +- MacPass.xcodeproj/project.pbxproj | 4 ++ MacPass/MPEntryViewController.m | 16 ++++++- MacPass/MPModelChangeObserving.h | 8 ++-- MacPass/MPModelChangeObserving.m | 45 +++++++++++++++++++ MacPassTests/MPTestAutotype.m | 24 ++++++++-- .../MPTestModelChangeObservingHelper.m | 43 ++++++++++++++++++ 8 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 MacPassTests/MPTestModelChangeObservingHelper.m diff --git a/Cartfile b/Cartfile index e8c6a73d..e968e925 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1,3 @@ github "sparkle-project/Sparkle" ~> 1.13.1 -github "mstarke/KeePassKit" "c53c045a93b1e8496e5d2122bc4bee32c08194a6" +github "mstarke/KeePassKit" "bf47781a618fc514288315995a966b5f630f6918" github "mstarke/HNHUi" ~> 1.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 2cbf4b2a..43d0fd65 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ github "mstarke/HNHUi" "1.0.1" -github "mstarke/KeePassKit" "c53c045a93b1e8496e5d2122bc4bee32c08194a6" +github "mstarke/KeePassKit" "722652647b9f1ff0967429ac02da046e3534178f" github "sparkle-project/Sparkle" "1.14.0" diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index ec60dba4..bc453a57 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -233,6 +233,7 @@ 4CE3E62617AB0D2D00D9E4B4 /* MPAttachmentTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE3E62517AB0D2D00D9E4B4 /* MPAttachmentTableDataSource.m */; }; 4CE501341BBC47F500FB819D /* MPTagsTokenFieldDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE501331BBC47F500FB819D /* MPTagsTokenFieldDelegate.m */; }; 4CE5B54B173AFBA700207B39 /* MPDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE5B549173AFBA700207B39 /* MPDocument.m */; }; + 4CE7FAFB1D74813500E2C856 /* MPTestModelChangeObservingHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE7FAFA1D74813500E2C856 /* MPTestModelChangeObservingHelper.m */; }; 4CE8246F16E2E93400573141 /* MPOverlayWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8246E16E2E93400573141 /* MPOverlayWindowController.m */; }; 4CE8247516E2F2B900573141 /* MPOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8247416E2F2B900573141 /* MPOverlayView.m */; }; 4CE88B9717BA651C0042E078 /* contextTriangleTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 4CE88B9617BA651C0042E078 /* contextTriangleTemplate.pdf */; }; @@ -656,6 +657,7 @@ 4CE501331BBC47F500FB819D /* MPTagsTokenFieldDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTagsTokenFieldDelegate.m; sourceTree = ""; }; 4CE5B548173AFBA700207B39 /* MPDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDocument.h; sourceTree = ""; }; 4CE5B549173AFBA700207B39 /* MPDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MPDocument.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4CE7FAFA1D74813500E2C856 /* MPTestModelChangeObservingHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTestModelChangeObservingHelper.m; sourceTree = ""; }; 4CE8246D16E2E93400573141 /* MPOverlayWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOverlayWindowController.h; sourceTree = ""; }; 4CE8246E16E2E93400573141 /* MPOverlayWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOverlayWindowController.m; sourceTree = ""; }; 4CE8247316E2F2B900573141 /* MPOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOverlayView.h; sourceTree = ""; }; @@ -991,6 +993,7 @@ 4C45FB2C178E0BCB0010007D /* MPDatabaseLoading.m */, 4C45FB2F178E0CE20010007D /* MPTestDocument.m */, 4C6BC65F1A36717E00BDDF3D /* MPDatabaseSearch.m */, + 4CE7FAFA1D74813500E2C856 /* MPTestModelChangeObservingHelper.m */, 4C45FB1F178E09ED0010007D /* Supporting Files */, ); path = MacPassTests; @@ -1679,6 +1682,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4CE7FAFB1D74813500E2C856 /* MPTestModelChangeObservingHelper.m in Sources */, 4C45FB2D178E0BCB0010007D /* MPDatabaseLoading.m in Sources */, 4C8DEAA21C314D2C00D24C32 /* MPTestAutotypeDelay.m in Sources */, 4C45FB30178E0CE20010007D /* MPTestDocument.m in Sources */, diff --git a/MacPass/MPEntryViewController.m b/MacPass/MPEntryViewController.m index f4397b88..471228fb 100644 --- a/MacPass/MPEntryViewController.m +++ b/MacPass/MPEntryViewController.m @@ -53,6 +53,7 @@ NSString *const MPEntryTableURLColumnIdentifier = @"MPEntryTableURLColumnIdentif NSString *const MPEntryTableNotesColumnIdentifier = @"MPEntryTableNotesColumnIdentifier"; NSString *const MPEntryTableAttachmentColumnIdentifier = @"MPEntryTableAttachmentColumnIdentifier"; NSString *const MPEntryTableModfiedColumnIdentifier = @"MPEntryTableModfiedColumnIdentifier"; +NSString *const MPEntryTableHistoryColumnIdentifier = @"MPEntryTableHistoryColumnIdentifier"; NSString *const _MPTableImageCellView = @"ImageCell"; NSString *const _MPTableStringCellView = @"StringCell"; @@ -128,12 +129,15 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; NSTableColumn *attachmentsColumn = [[NSTableColumn alloc] initWithIdentifier:MPEntryTableAttachmentColumnIdentifier]; NSTableColumn *notesColumn = [[NSTableColumn alloc] initWithIdentifier:MPEntryTableNotesColumnIdentifier]; NSTableColumn *modifiedColumn = [[NSTableColumn alloc] initWithIdentifier:MPEntryTableModfiedColumnIdentifier]; + NSTableColumn *historyColumn = [[NSTableColumn alloc] initWithIdentifier:MPEntryTableHistoryColumnIdentifier]; notesColumn.minWidth = 40.0; attachmentsColumn.minWidth = 40.0; modifiedColumn.minWidth = 40.0; + historyColumn.minWidth = 40.0; [self.entryTable addTableColumn:notesColumn]; [self.entryTable addTableColumn:attachmentsColumn]; [self.entryTable addTableColumn:modifiedColumn]; + [self.entryTable addTableColumn:historyColumn]; parentColumn.identifier = MPEntryTableParentColumnIdentifier; titleColumn.identifier = MPEntryTableTitleColumnIdentifier; @@ -161,6 +165,7 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; notesColumn.headerCell.stringValue = NSLocalizedString(@"NOTES", ""); attachmentsColumn.headerCell.stringValue = NSLocalizedString(@"ATTACHMENTS", ""); modifiedColumn.headerCell.stringValue = NSLocalizedString(@"MODIFIED", ""); + historyColumn.headerCell.stringValue = NSLocalizedString(@"HISTORY", ""); [self.entryTable bind:NSContentBinding toObject:self.entryArrayController withKeyPath:NSStringFromSelector(@selector(arrangedObjects)) options:nil]; [self.entryTable bind:NSSortDescriptorsBinding toObject:self.entryArrayController withKeyPath:NSStringFromSelector(@selector(sortDescriptors)) options:nil]; @@ -235,6 +240,7 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; BOOL isAttachmentColumn = [tableColumn.identifier isEqualToString:MPEntryTableAttachmentColumnIdentifier]; BOOL isNotesColumn = [tableColumn.identifier isEqualToString:MPEntryTableNotesColumnIdentifier]; BOOL isModifedColumn = [tableColumn.identifier isEqualToString:MPEntryTableModfiedColumnIdentifier]; + BOOL isHistoryColumn = [tableColumn.identifier isEqualToString:MPEntryTableHistoryColumnIdentifier]; NSTableCellView *view = nil; if(isTitleColumn || isGroupColumn) { @@ -320,10 +326,16 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; [view.textField bind:NSValueBinding toObject:view withKeyPath:notesKeyPath options:options]; } else if(isAttachmentColumn) { - NSString *binariesCoundKeyPath = [NSString stringWithFormat:@"%@.%@.@count", + NSString *binariesCountKeyPath = [NSString stringWithFormat:@"%@.%@.@count", NSStringFromSelector(@selector(objectValue)), NSStringFromSelector(@selector(binaries))]; - [view.textField bind:NSValueBinding toObject:view withKeyPath:binariesCoundKeyPath options:nil]; + [view.textField bind:NSValueBinding toObject:view withKeyPath:binariesCountKeyPath options:nil]; + } + else if(isHistoryColumn) { + NSString *historyCountKeyPath = [NSString stringWithFormat:@"%@.%@.@count", + NSStringFromSelector(@selector(objectValue)), + NSStringFromSelector(@selector(history))]; + [view.textField bind:NSValueBinding toObject:view withKeyPath:historyCountKeyPath options:nil]; } } return view; diff --git a/MacPass/MPModelChangeObserving.h b/MacPass/MPModelChangeObserving.h index 1bad6e46..16db5d2a 100644 --- a/MacPass/MPModelChangeObserving.h +++ b/MacPass/MPModelChangeObserving.h @@ -23,15 +23,17 @@ FOUNDATION_EXTERN NSString *const MPDidChangeModelNotification; FOUNDATION_EXTERN NSString *const MPModelChangeObservingKeyPathKey; @required -- (void)observerModelChangesForKeyPath:(NSString *)keyPath; +- (void)beginObservingModelChangesForKeyPath:(NSString *)keyPath; +- (void)endObservingModelChangesForKeyPath:(NSString *)keyPath; @end -/* Use this helper to fire the right notifications */ +/* Use this helper to fire the right notifications. You can hold an instance to help in you implementation of setValue:forKeyPath and observerModelChangesForKeyPath: */ @interface MPModelChangeObservingHelper : NSObject + (void)willChangeModelKeyPath:(NSString *)keyPath observer:(id)observer; + (void)didChangeModelKeyPath:(NSString *)keyPath observer:(id)observer; - +- (void)beginObservingModelChangesForKeyPath:(NSString *)keyPath; +- (void)endObservingModelChangesForKeyPath:(NSString *)keyPath; - (void)setValue:(id)value forKeyPath:(NSString *)keyPath forTarget:(id)target; @end diff --git a/MacPass/MPModelChangeObserving.m b/MacPass/MPModelChangeObserving.m index 0eae059b..60432a94 100644 --- a/MacPass/MPModelChangeObserving.m +++ b/MacPass/MPModelChangeObserving.m @@ -13,6 +13,11 @@ NSString *const MPDidChangeModelNotification = @"com.hicknhack.macpass.MPDidChan NSString *const MPModelChangeObservingKeyPathKey = @"MPModelChangeObservingKeyPathKey"; +@interface MPModelChangeObservingHelper () +@property (strong) NSMutableSet *observedPaths; +@property (strong) NSMutableSet *matchedPathsCache; +@end + @implementation MPModelChangeObservingHelper + (void)willChangeModelKeyPath:(NSString *)keyPath observer:(id)observer { @@ -24,7 +29,47 @@ NSString *const MPModelChangeObservingKeyPathKey = @"MPModelChangeObservingKeyPa } - (void)setValue:(id)value forKeyPath:(NSString *)keyPath forTarget:(id)target { + [MPModelChangeObservingHelper willChangeModelKeyPath:keyPath observer:target]; [target setValue:value forKeyPath:keyPath]; + [MPModelChangeObservingHelper didChangeModelKeyPath:keyPath observer:target]; +} + +- (void)beginObservingModelChangesForKeyPath:(NSString *)keyPath { + if(!self.observedPaths) { + self.observedPaths = [[NSMutableSet alloc] initWithCapacity:3]; + } + [self.observedPaths addObject:keyPath]; +} + +- (void)endObservingModelChangesForKeyPath:(NSString *)keyPath { + [self.observedPaths removeObject:keyPath]; + /* if we have nothing to observer, just clear the cache and exit */ + if(self.observedPaths.count == 0) { + self.matchedPathsCache = nil; + return; + } + + NSPredicate *predicat = [NSPredicate predicateWithBlock:^BOOL(id _Nonnull evaluatedObject, NSDictionary * _Nullable bindings) { + NSString *cachedPath = evaluatedObject; + return ![cachedPath hasPrefix:keyPath]; + }]; + [self.matchedPathsCache filterUsingPredicate:predicat]; +} + +- (BOOL)_isObservingKeyPath:(NSString *)keyPath { + if([self.matchedPathsCache containsObject:keyPath]) { + return YES; + } + for(NSString *observedKeyPath in self.observedPaths) { + if([keyPath hasPrefix:observedKeyPath]) { + if(!self.matchedPathsCache) { + self.matchedPathsCache = [[NSMutableSet alloc] initWithCapacity:3]; + } + [self.matchedPathsCache addObject:keyPath]; + return YES; + } + } + return NO; } @end diff --git a/MacPassTests/MPTestAutotype.m b/MacPassTests/MPTestAutotype.m index 39b680c4..1b7c3a20 100644 --- a/MacPassTests/MPTestAutotype.m +++ b/MacPassTests/MPTestAutotype.m @@ -16,6 +16,8 @@ #import "MPAutotypePaste.h" #import "MPAutotypeKeyPress.h" +#import "MPSettingsHelper.h" + #import "MPKeyMapper.h" @@ -196,7 +198,13 @@ keyPress = commands[0]; /* Lower case is ok, since we only need the key, not the sequence to reproduce the string */ XCTAssertTrue([@"t" isEqualToString:[MPKeyMapper stringForKey:keyPress.keyCode]]); - XCTAssertEqual(keyPress.modifierMask, kCGEventFlagMaskCommand); + BOOL useCommandInsteadOfControl = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeySendCommandForControlKey]; + if(useCommandInsteadOfControl) { + XCTAssertEqual(keyPress.modifierMask, kCGEventFlagMaskCommand); + } + else { + XCTAssertEqual(keyPress.modifierMask, kCGEventFlagMaskControl); + } /* {USERNAME} */ paste = commands[1]; @@ -205,7 +213,12 @@ /* %+^{TAB} */ keyPress = commands[2]; XCTAssertEqual(keyPress.keyCode, kVK_Tab); // Tab is a fixed key, no mapping needed - XCTAssertEqual(keyPress.modifierMask, (kCGEventFlagMaskCommand | kCGEventFlagMaskShift | kCGEventFlagMaskAlternate)); + if(useCommandInsteadOfControl) { + XCTAssertEqual(keyPress.modifierMask, (kCGEventFlagMaskCommand | kCGEventFlagMaskShift | kCGEventFlagMaskAlternate)); + } + else { + XCTAssertEqual(keyPress.modifierMask, (kCGEventFlagMaskControl | kCGEventFlagMaskShift | kCGEventFlagMaskAlternate)); + } /* Whoo{PASSWORD} */ paste = commands[3]; @@ -228,7 +241,12 @@ keyPress = commands[0]; XCTAssertEqualObjects(@"t", [MPKeyMapper stringForKey:keyPress.keyCode]); /* TODO - Respect user settings? */ - XCTAssertEqual(keyPress.modifierMask, kCGEventFlagMaskCommand); + if(useCommandInsteadOfControl) { + XCTAssertEqual(keyPress.modifierMask, kCGEventFlagMaskCommand); + } + else { + XCTAssertEqual(keyPress.modifierMask, kCGEventFlagMaskControl); + } /*XCTAssertEqual(keyPress.modifierMask, kCGEventFlagMaskControl);*/ } diff --git a/MacPassTests/MPTestModelChangeObservingHelper.m b/MacPassTests/MPTestModelChangeObservingHelper.m new file mode 100644 index 00000000..b5161a83 --- /dev/null +++ b/MacPassTests/MPTestModelChangeObservingHelper.m @@ -0,0 +1,43 @@ +// +// MPTestModelChangeObservingHelper.m +// MacPass +// +// Created by Michael Starke on 29/08/16. +// Copyright © 2016 HicknHack Software GmbH. All rights reserved. +// + +#import +#import "MPModelChangeObserving.h" + +@interface MPTestModelChangeObservingHelper : XCTestCase +@property (strong) MPModelChangeObservingHelper *helper; +@end + +@implementation MPTestModelChangeObservingHelper + +- (void)setUp { + [super setUp]; + self.helper = [[MPModelChangeObservingHelper alloc] init]; +} + +- (void)tearDown { + self.helper = nil; + [super tearDown]; +} + +- (void)testAddObserver { + [self.helper beginObservingModelChangesForKeyPath:@"testKey"]; + NSMutableSet *set = [self.helper valueForKey:@"observedPaths"]; + XCTAssertEqual(set.count, 1, @"Observed paths contains one element"); + XCTAssertTrue([set containsObject:@"testKey"], @"Observed set contains testKey"); +} + +- (void)testRemoveObserver { + NSString *aKeyPath = @"testKeyPath"; + [self.helper beginObservingModelChangesForKeyPath:aKeyPath]; + [self.helper endObservingModelChangesForKeyPath:aKeyPath]; + NSMutableSet *set = [self.helper valueForKey:@"observedPaths"]; + XCTAssertFalse([set containsObject:aKeyPath], @"Observed path is removed after end of observation"); +} + +@end