From 3fa98438555ef9e5723bce64049e3174187c4b1a Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Fri, 21 Feb 2014 00:19:24 -0500 Subject: [PATCH] Improved site operations UI. [IMPROVED] New UI for site operations. [ADDED] Ability to delete a site. --- .../ObjC/Mac/MPElementCollectionView.h | 10 +- .../ObjC/Mac/MPElementCollectionView.m | 129 ++++++--------- MasterPassword/ObjC/Mac/MPElementModel.h | 25 +-- MasterPassword/ObjC/Mac/MPElementModel.m | 72 ++++++-- .../ObjC/Mac/MPPasswordWindowController.m | 28 +--- .../ObjC/Mac/MPPasswordWindowController.xib | 156 +++++++++++++----- 6 files changed, 250 insertions(+), 170 deletions(-) diff --git a/MasterPassword/ObjC/Mac/MPElementCollectionView.h b/MasterPassword/ObjC/Mac/MPElementCollectionView.h index 1d2ad402..bd9968d0 100644 --- a/MasterPassword/ObjC/Mac/MPElementCollectionView.h +++ b/MasterPassword/ObjC/Mac/MPElementCollectionView.h @@ -22,12 +22,12 @@ @interface MPElementCollectionView : NSCollectionViewItem @property (nonatomic) MPElementModel *representedObject; -@property (nonatomic) NSString *typeTitle; -@property (nonatomic) NSString *loginNameTitle; -@property (nonatomic) NSString *counterTitle; +@property (nonatomic) BOOL counterHidden; +@property (nonatomic) BOOL updateContentHidden; - (IBAction)toggleType:(id)sender; -- (IBAction)setLoginName:(id)sender; -- (IBAction)incrementCounter:(id)sender; +- (IBAction)updateLoginName:(id)sender; +- (IBAction)updateContent:(id)sender; +- (IBAction)delete:(id)sender; @end diff --git a/MasterPassword/ObjC/Mac/MPElementCollectionView.m b/MasterPassword/ObjC/Mac/MPElementCollectionView.m index f5f042c3..24b2b2cd 100644 --- a/MasterPassword/ObjC/Mac/MPElementCollectionView.m +++ b/MasterPassword/ObjC/Mac/MPElementCollectionView.m @@ -23,11 +23,10 @@ #define MPAlertChangeType @"MPAlertChangeType" #define MPAlertChangeLogin @"MPAlertChangeLogin" -#define MPAlertChangeCounter @"MPAlertChangeCounter" #define MPAlertChangeContent @"MPAlertChangeContent" +#define MPAlertDeleteSite @"MPAlertDeleteSite" @implementation MPElementCollectionView { - id _representedObjectObserver; } @dynamic representedObject; @@ -38,27 +37,16 @@ return nil; __weak MPElementCollectionView *wSelf = self; - _representedObjectObserver = [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { + [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { dispatch_async( dispatch_get_main_queue(), ^{ - wSelf.typeTitle = PearlString( @"Type:\n%@", wSelf.representedObject.typeName ); - wSelf.loginNameTitle = PearlString( @"Login Name:\n%@", wSelf.representedObject.loginName ); - - if (wSelf.representedObject.type & MPElementTypeClassGenerated) - wSelf.counterTitle = PearlString( @"Number:\n%@", wSelf.representedObject.counter ); - else if (wSelf.representedObject.type & MPElementTypeClassStored) - wSelf.counterTitle = PearlString( @"Update Password" ); + wSelf.counterHidden = !(MPElementTypeClassGenerated & wSelf.representedObject.type); + wSelf.updateContentHidden = !(MPElementTypeClassStored & wSelf.representedObject.type); } ); - } forKeyPath:@"representedObject" options:0 context:nil]; + } forKeyPath:@"representedObject" options:0 context:nil]; return self; } -- (void)dealloc { - - if (_representedObjectObserver) - [self removeObserver:_representedObjectObserver forKeyPath:@"representedObject"]; -} - - (IBAction)toggleType:(id)sender { id algorithm = self.representedObject.algorithm; @@ -74,7 +62,7 @@ didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeType]; } -- (IBAction)setLoginName:(id)sender { +- (IBAction)updateLoginName:(id)sender { NSAlert *alert = [NSAlert alertWithMessageText:@"Update Login Name" defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil @@ -87,29 +75,26 @@ didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeLogin]; } -- (IBAction)incrementCounter:(id)sender { +- (IBAction)updateContent:(id)sender { - if (self.representedObject.type & MPElementTypeClassGenerated) { - [[NSAlert alertWithMessageText:@"Change Password Number" - defaultButton:@"New Password" alternateButton:@"Cancel" otherButton:@"Initial Password" - informativeTextWithFormat:@"Increasing the password number gives you a new password for the site.\n" - @"You will need to update your account with the new password.\n\n" - @"Changing back to the initial password will reset the password number to 1."] - beginSheetModalForWindow:self.view.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeCounter]; - } + NSAlert *alert = [NSAlert alertWithMessageText:@"Update Password" + defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil + informativeTextWithFormat:@"Enter the new password for %@:", self.representedObject.site]; + NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; + [alert setAccessoryView:passwordField]; + [alert layout]; + [passwordField becomeFirstResponder]; + [alert beginSheetModalForWindow:self.view.window modalDelegate:self + didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeContent]; +} - else if (self.representedObject.type & MPElementTypeClassStored) { - NSAlert *alert = [NSAlert alertWithMessageText:@"Update Password" - defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil - informativeTextWithFormat:@"Enter the new password for %@:", self.representedObject.site]; - NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; - [alert setAccessoryView:passwordField]; - [alert layout]; - [passwordField becomeFirstResponder]; - [alert beginSheetModalForWindow:self.view.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeContent]; - } +- (IBAction)delete:(id)sender { + + NSAlert *alert = [NSAlert alertWithMessageText:@"Delete Site" + defaultButton:@"Delete" alternateButton:@"Cancel" otherButton:nil + informativeTextWithFormat:@"Are you sure you want to delete the site: %@?", self.representedObject.site]; + [alert beginSheetModalForWindow:self.view.window modalDelegate:self + didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertDeleteSite]; } - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { @@ -178,49 +163,6 @@ return; } - if (contextInfo == MPAlertChangeCounter) { - switch (returnCode) { - case NSAlertDefaultReturn: { - // "New Password" button. - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPElementEntity *element = [self.representedObject entityInContext:context]; - if ([element isKindOfClass:[MPElementGeneratedEntity class]]) { - MPElementGeneratedEntity *generatedElement = (MPElementGeneratedEntity *)element; - ++generatedElement.counter; - [context saveToStore]; - - self.representedObject = [[MPElementModel alloc] initWithEntity:element]; - } - }]; - break; - } - - case NSAlertAlternateReturn: { - // "Cancel" button. - break; - } - - case NSAlertOtherReturn: { - // "Initial Password" button. - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPElementEntity *element = [self.representedObject entityInContext:context]; - if ([element isKindOfClass:[MPElementGeneratedEntity class]]) { - MPElementGeneratedEntity *generatedElement = (MPElementGeneratedEntity *)element; - generatedElement.counter = 1; - [context saveToStore]; - - self.representedObject = [[MPElementModel alloc] initWithEntity:element]; - } - }]; - break; - } - - default: - break; - } - - return; - } if (contextInfo == MPAlertChangeContent) { switch (returnCode) { case NSAlertDefaultReturn: { @@ -245,6 +187,29 @@ break; } + return; + } + if (contextInfo == MPAlertDeleteSite) { + switch (returnCode) { + case NSAlertDefaultReturn: { + // "Delete" button. + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementEntity *element = [self.representedObject entityInContext:context]; + [context deleteObject:element]; + [context saveToStore]; + }]; + break; + } + + case NSAlertAlternateReturn: { + // "Cancel" button. + break; + } + + default: + break; + } + return; } } diff --git a/MasterPassword/ObjC/Mac/MPElementModel.h b/MasterPassword/ObjC/Mac/MPElementModel.h index da5ce71f..6c3e4c9e 100644 --- a/MasterPassword/ObjC/Mac/MPElementModel.h +++ b/MasterPassword/ObjC/Mac/MPElementModel.h @@ -20,18 +20,19 @@ @class MPElementEntity; @interface MPElementModel : NSObject -@property (nonatomic) NSString *site; -@property (nonatomic) MPElementType type; -@property (nonatomic) NSString *typeName; -@property (nonatomic) NSString *content; -@property (nonatomic) NSString *loginName; -@property (nonatomic) NSNumber *uses; -@property (nonatomic) NSNumber *counter; -@property (nonatomic) NSDate *lastUsed; -@property (nonatomic, strong) id algorithm; - -- (MPElementEntity *)entityForMainThread; -- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc; +@property (nonatomic, readonly) NSString *site; +@property (nonatomic, readonly) MPElementType type; +@property (nonatomic, readonly) NSString *typeName; +@property (nonatomic, readonly) NSString *content; +@property (nonatomic, readonly) NSString *loginName; +@property (nonatomic, readonly) NSNumber *uses; +@property (nonatomic) NSUInteger counter; +@property (nonatomic, readonly) NSDate *lastUsed; +@property (nonatomic, readonly) id algorithm; +@property (nonatomic, readonly) NSArray *types; +@property (nonatomic) NSUInteger typeIndex; - (id)initWithEntity:(MPElementEntity *)entity; +- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc; + @end diff --git a/MasterPassword/ObjC/Mac/MPElementModel.m b/MasterPassword/ObjC/Mac/MPElementModel.m index 7b16cd6e..0efb40dc 100644 --- a/MasterPassword/ObjC/Mac/MPElementModel.m +++ b/MasterPassword/ObjC/Mac/MPElementModel.m @@ -26,9 +26,13 @@ @interface MPElementModel() @property(nonatomic, strong) NSManagedObjectID *entityOID; +@property(nonatomic, readwrite) NSString *content; +@property(nonatomic, readwrite) MPElementType type; +@property(nonatomic, readwrite) NSString *typeName; @end @implementation MPElementModel { + NSMutableDictionary *_typesByName; } - (id)initWithEntity:(MPElementEntity *)entity { @@ -36,25 +40,29 @@ if (!(self = [super init])) return nil; - self.site = entity.name; - self.lastUsed = entity.lastUsed; - self.loginName = entity.loginName; - self.type = entity.type; - self.typeName = entity.typeName; - self.uses = entity.uses_; - self.counter = @([entity isKindOfClass:[MPElementGeneratedEntity class]]? [(MPElementGeneratedEntity *)entity counter]: 0); - self.content = [entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key]; - self.algorithm = entity.algorithm; - self.entityOID = entity.objectID; + _site = entity.name; + _lastUsed = entity.lastUsed; + _loginName = entity.loginName; + _type = entity.type; + _typeName = entity.typeName; + _uses = entity.uses_; + _counter = [entity isKindOfClass:[MPElementGeneratedEntity class]]? [(MPElementGeneratedEntity *)entity counter]: 0; + _content = [entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key]; + _algorithm = entity.algorithm; + _entityOID = entity.objectID; + + // Find all password types and the index of the current type amongst them. + _typesByName = [NSMutableDictionary dictionary]; + MPElementType type = _type; + do { + [_typesByName setObject:@(type) forKey:[_algorithm shortNameOfType:type]]; + } while (_type != (type = [_algorithm nextType:type])); + _types = [_typesByName keysSortedByValueUsingSelector:@selector(compare:)]; + _typeIndex = [[[_typesByName allValues] sortedArrayUsingSelector:@selector(compare:)] indexOfObject:@(_type)]; return self; } -- (MPElementEntity *)entityForMainThread { - - return [self entityInContext:[MPMacAppDelegate managedObjectContextForMainThreadIfReady]]; -} - - (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc { if (!_entityOID) @@ -68,4 +76,38 @@ return entity; } +- (void)setCounter:(NSUInteger)counter { + + if (counter == _counter) + return; + _counter = counter; + + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementEntity *entity = [self entityInContext:context]; + if ([entity isKindOfClass:[MPElementGeneratedEntity class]]) { + ((MPElementGeneratedEntity *)entity).counter = counter; + [context saveToStore]; + + self.content = [entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key]; + } + }]; +} + +- (void)setTypeIndex:(NSUInteger)typeIndex { + + if (typeIndex == _typeIndex) + return; + _typeIndex = typeIndex; + + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementEntity *entity = [self entityInContext:context]; + entity.type_ = _typesByName[_types[typeIndex]]; + [context saveToStore]; + + self.type = entity.type; + self.typeName = entity.typeName; + self.content = [entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key]; + }]; +} + @end diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m index f675f168..bcfe8cad 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m @@ -197,34 +197,24 @@ - (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector { - if (commandSelector == @selector(cancel:)) { + if (commandSelector == @selector(cancel:)) [self close]; - return YES; - } - if (commandSelector == @selector(moveUp:)) { + if (commandSelector == @selector(moveUp:)) self.elementSelectionIndexes = [NSIndexSet indexSetWithIndex:MAX(self.elementSelectionIndexes.firstIndex, (NSUInteger)1) - 1]; - return YES; - } - if (commandSelector == @selector(moveDown:)) { + if (commandSelector == @selector(moveDown:)) self.elementSelectionIndexes = [NSIndexSet indexSetWithIndex:MIN(self.elementSelectionIndexes.firstIndex + 1, self.elements.count - 1)]; - return YES; - } - if (commandSelector == @selector(moveLeft:)) { + if (commandSelector == @selector(moveLeft:)) [[self selectedView].animator setBoundsOrigin:NSZeroPoint]; - return YES; - } - if (commandSelector == @selector(moveRight:)) { + if (commandSelector == @selector(moveRight:)) [[self selectedView].animator setBoundsOrigin:NSMakePoint( self.siteCollectionView.frame.size.width / 2, 0 )]; - return YES; - } - if (commandSelector == @selector(insertNewline:)) { + if (commandSelector == @selector(insertNewline:)) [self useSite]; - return YES; - } + else + return NO; - return NO; + return YES; } - (void)controlTextDidChange:(NSNotification *)note { diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib b/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib index 4b76b05f..b0f4524b 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib @@ -19,7 +19,7 @@ - + @@ -39,7 +39,7 @@ - + @@ -88,11 +88,21 @@ + + + @@ -200,77 +210,149 @@ - - - + + + + + + + + + - - - - + + + + - + + - - - + - + + + - - + + + + + + + +