From f1a72d816002b3de7f139cb048468b34868edcc7 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sun, 29 Jun 2014 23:18:25 -0400 Subject: [PATCH] Initial window improvements, reveal and reset master password. [FIXED] Initial intro window didn't always show up reliably. [ADDED] Ability to temporarily reveal master password while typing it. [IMPROVED] When you go down the sites list, fade out the fade-out gradient to prevent the selection from becoming invisible. [ADDED] Ability to reset your master password. [UPDATED] Initial screenshot of Master Password for Mac and iPhone. --- .travis.yml | 2 +- MasterPassword/ObjC/Mac/MPInitialWindow.xib | 14 +- .../ObjC/Mac/MPInitialWindowController.h | 29 ++ .../ObjC/Mac/MPInitialWindowController.m | 60 ++++ MasterPassword/ObjC/Mac/MPMacAppDelegate.h | 8 +- MasterPassword/ObjC/Mac/MPMacAppDelegate.m | 51 +-- .../ObjC/Mac/MPPasswordWindowController.h | 9 +- .../ObjC/Mac/MPPasswordWindowController.m | 197 ++++++++---- .../ObjC/Mac/MPPasswordWindowController.xib | 300 ++++++++++++++++-- .../project.pbxproj | 6 + .../Media/shot-laptop-leaning-iphone.png | Bin 592921 -> 390473 bytes 11 files changed, 525 insertions(+), 151 deletions(-) create mode 100644 MasterPassword/ObjC/Mac/MPInitialWindowController.h create mode 100644 MasterPassword/ObjC/Mac/MPInitialWindowController.m diff --git a/.travis.yml b/.travis.yml index 3f8a35cb..69f79f06 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c xcode_workspace: MasterPassword.xcworkspace -xcode_scheme: MasterPassword iOS (Development) +xcode_scheme: 'MasterPassword iOS (Development)' xcode_sdk: iphonesimulator git: submodules: false diff --git a/MasterPassword/ObjC/Mac/MPInitialWindow.xib b/MasterPassword/ObjC/Mac/MPInitialWindow.xib index a13ff4c5..3aa11128 100644 --- a/MasterPassword/ObjC/Mac/MPInitialWindow.xib +++ b/MasterPassword/ObjC/Mac/MPInitialWindow.xib @@ -1,20 +1,22 @@ - + - + - + + - + + @@ -125,7 +127,7 @@ from anywhere. - + @@ -153,4 +155,4 @@ from anywhere. - \ No newline at end of file + diff --git a/MasterPassword/ObjC/Mac/MPInitialWindowController.h b/MasterPassword/ObjC/Mac/MPInitialWindowController.h new file mode 100644 index 00000000..57f4eaaa --- /dev/null +++ b/MasterPassword/ObjC/Mac/MPInitialWindowController.h @@ -0,0 +1,29 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPInitialWindowController.h +// MPInitialWindowController +// +// Created by lhunath on 2014-06-29. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import + +@interface MPInitialWindowController : NSWindowController + +@property(nonatomic, weak) IBOutlet NSButton *openAtLoginButton; +@property(nonatomic, weak) IBOutlet NSButton *enableCloudButton; + +- (IBAction)iphoneAppStore:(id)sender; +- (IBAction)togglePreference:(id)sender; + +@end diff --git a/MasterPassword/ObjC/Mac/MPInitialWindowController.m b/MasterPassword/ObjC/Mac/MPInitialWindowController.m new file mode 100644 index 00000000..1f97239e --- /dev/null +++ b/MasterPassword/ObjC/Mac/MPInitialWindowController.m @@ -0,0 +1,60 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPInitialWindowController.h +// MPInitialWindowController +// +// Created by lhunath on 2014-06-29. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPInitialWindowController.h" +#import "MPMacAppDelegate.h" +#import "MPAppDelegate_Store.h" + +@implementation MPInitialWindowController + +#pragma mark - Life + +- (void)windowDidLoad { + + [super windowDidLoad]; + + [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window + queue:nil usingBlock:^(NSNotification *note) { + [MPMacAppDelegate get].initialWindowController = nil; + }]; +} + +#pragma mark - Actions + +- (IBAction)iphoneAppStore:(id)sender { + + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://itunes.apple.com/app/id510296984"]]; + [self close]; +} + +- (IBAction)togglePreference:(id)sender { + + if (sender == self.enableCloudButton) { + if (([MPMacAppDelegate get].storeManager.cloudEnabled = self.enableCloudButton.state == NSOnState)) { + NSAlert *alert = [NSAlert new]; + alert.messageText = @"iCloud Enabled"; + alert.informativeText = @"If you already have a user on another iCloud-enabled device, " + @"it may take a moment for that user to sync down to this device."; + [alert runModal]; + } + } + if (sender == self.openAtLoginButton) + [[MPMacAppDelegate get] setLoginItemEnabled:self.openAtLoginButton.state == NSOnState]; +} + +@end diff --git a/MasterPassword/ObjC/Mac/MPMacAppDelegate.h b/MasterPassword/ObjC/Mac/MPMacAppDelegate.h index 180920c0..08630db6 100644 --- a/MasterPassword/ObjC/Mac/MPMacAppDelegate.h +++ b/MasterPassword/ObjC/Mac/MPMacAppDelegate.h @@ -10,11 +10,13 @@ #import "MPAppDelegate_Shared.h" #import "RHStatusItemView.h" #import "MPPasswordWindowController.h" +#import "MPInitialWindowController.h" @interface MPMacAppDelegate : MPAppDelegate_Shared @property(nonatomic, strong) RHStatusItemView *statusView; -@property(nonatomic, strong) MPPasswordWindowController *passwordWindow; +@property(nonatomic, strong) MPPasswordWindowController *passwordWindowController; +@property(nonatomic, strong) MPInitialWindowController *initialWindowController; @property(nonatomic, weak) IBOutlet NSMenuItem *lockItem; @property(nonatomic, weak) IBOutlet NSMenuItem *showItem; @property(nonatomic, strong) IBOutlet NSMenu *statusMenu; @@ -26,17 +28,15 @@ @property(nonatomic, weak) IBOutlet NSMenuItem *createUserItem; @property(nonatomic, weak) IBOutlet NSMenuItem *deleteUserItem; @property(nonatomic, weak) IBOutlet NSMenuItem *usersItem; -@property(nonatomic, weak) IBOutlet NSButton *openAtLoginButton; -@property(nonatomic, weak) IBOutlet NSButton *enableCloudButton; - (IBAction)showPasswordWindow:(id)sender; +- (void)setLoginItemEnabled:(BOOL)enabled; - (IBAction)togglePreference:(id)sender; - (IBAction)newUser:(NSMenuItem *)sender; - (IBAction)lock:(id)sender; - (IBAction)rebuildCloud:(id)sender; - (IBAction)corruptCloud:(id)sender; - (IBAction)terminate:(id)sender; -- (IBAction)iphoneAppStore:(id)sender; - (IBAction)showPopup:(id)sender; @end diff --git a/MasterPassword/ObjC/Mac/MPMacAppDelegate.m b/MasterPassword/ObjC/Mac/MPMacAppDelegate.m index 248f9c49..a8a41a6f 100644 --- a/MasterPassword/ObjC/Mac/MPMacAppDelegate.m +++ b/MasterPassword/ObjC/Mac/MPMacAppDelegate.m @@ -22,11 +22,6 @@ @end -@interface MPMacAppDelegate() - -@property(nonatomic, strong) NSWindowController *initialWindow; -@end - @implementation MPMacAppDelegate #pragma clang diagnostic push @@ -131,10 +126,9 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven err( @"Error registering 'lock' hotkey: %i", (int)status ); // Initial display. - if ([[MPMacConfig get].firstRun boolValue]) { - self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self]; - [self.initialWindow.window setLevel:NSFloatingWindowLevel]; - [self.initialWindow showWindow:self]; + if ([[MPMacConfig get].firstRun boolValue]){ + [(self.initialWindowController = [[MPInitialWindowController alloc] initWithWindowNibName:@"MPInitialWindow"]).window makeKeyAndOrderFront:self]; + [NSApp activateIgnoringOtherApps:YES]; } } @@ -185,7 +179,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven } self.openAtLoginItem.state = loginItemEnabled? NSOnState: NSOffState; - self.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState; + self.initialWindowController.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState; } - (BOOL)loginItemEnabled { @@ -320,23 +314,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (IBAction)togglePreference:(id)sender { - if (sender == self.enableCloudButton) { - if (([self storeManager].cloudEnabled = self.enableCloudButton.state == NSOnState)) { - NSAlert *alert = [NSAlert new]; - alert.messageText = @"iCloud Enabled"; - alert.informativeText = @"If you already have a user on another iCloud-enabled device, " - @"it may take a moment for that user to sync down to this device."; - [alert runModal]; - } - } if (sender == self.useCloudItem) [self storeManager].cloudEnabled = self.useCloudItem.state != NSOnState; if (sender == self.hidePasswordsItem) [MPConfig get].hidePasswords = [NSNumber numberWithBool:![[MPConfig get].hidePasswords boolValue]]; if (sender == self.rememberPasswordItem) [MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]]; - if (sender == self.openAtLoginButton) - [self setLoginItemEnabled:self.openAtLoginButton.state == NSOnState]; if (sender == self.openAtLoginItem) [self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState]; if (sender == self.savePasswordItem) { @@ -432,20 +415,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (IBAction)terminate:(id)sender { - [self.passwordWindow close]; - self.passwordWindow = nil; + [self.passwordWindowController close]; + self.passwordWindowController = nil; [NSApp terminate:nil]; } -- (IBAction)iphoneAppStore:(id)sender { - - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://itunes.apple.com/app/id510296984"]]; - - [self.initialWindow close]; - self.initialWindow = nil; -} - - (IBAction)showPopup:(id)sender { [self.statusView popUpMenu]; @@ -466,12 +441,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven } // Don't show window if we weren't already running (ie. if we haven't been activated before). - PearlProfiler *profiler = [PearlProfiler profilerForTask:@"passwordWindow"]; - if (!self.passwordWindow) - self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"]; + PearlProfiler *profiler = [PearlProfiler profilerForTask:@"passwordWindowController"]; + if (!self.passwordWindowController) + self.passwordWindowController = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"]; [profiler finishJob:@"init"]; - [self.passwordWindow showWindow:self]; + [self.passwordWindowController showWindow:self]; [profiler finishJob:@"show"]; } @@ -607,7 +582,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (void)updateMenuItems { MPUserEntity *activeUser = [self activeUserForMainThread]; -// if (!(self.showItem.enabled = ![self.passwordWindow.window isVisible])) { +// if (!(self.showItem.enabled = ![self.passwordWindowController.window isVisible])) { // self.showItem.title = @"Show (Showing)"; // self.showItem.toolTip = @"Master Password is already showing."; // } @@ -633,7 +608,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven BOOL loginItemEnabled = [self loginItemEnabled]; self.openAtLoginItem.state = loginItemEnabled? NSOnState: NSOffState; - self.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState; + self.initialWindowController.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState; self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState; self.savePasswordItem.state = activeUser.saveKey? NSOnState: NSOffState; @@ -654,7 +629,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven } self.useCloudItem.state = self.storeManager.cloudEnabled? NSOnState: NSOffState; - self.enableCloudButton.state = self.storeManager.cloudEnabled? NSOnState: NSOffState; + self.initialWindowController.enableCloudButton.state = self.storeManager.cloudEnabled? NSOnState: NSOffState; self.useCloudItem.enabled = self.storeManager.cloudAvailable; if (self.storeManager.cloudAvailable) { self.useCloudItem.title = @"Use iCloud"; diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.h b/MasterPassword/ObjC/Mac/MPPasswordWindowController.h index 81d73ca8..49159a7a 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.h +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.h @@ -19,16 +19,21 @@ #import #import "MPElementModel.h" +@class MPMacAppDelegate; + @interface MPPasswordWindowController : NSWindowController -@property(nonatomic, strong) NSMutableArray *elements; +@property(nonatomic) NSMutableArray *elements; +@property(nonatomic) NSString *masterPassword; @property(nonatomic) BOOL alternatePressed; +@property(nonatomic) BOOL locked; @property(nonatomic, weak) IBOutlet NSArrayController *elementsController; @property(nonatomic, weak) IBOutlet NSImageView *blurView; @property(nonatomic, weak) IBOutlet NSTextField *inputLabel; +@property(nonatomic, weak) IBOutlet NSTextField *securePasswordField; +@property(nonatomic, weak) IBOutlet NSTextField *revealPasswordField; @property(nonatomic, weak) IBOutlet NSSearchField *siteField; -@property(nonatomic, weak) IBOutlet NSSecureTextField *passwordField; @property(nonatomic, weak) IBOutlet NSTableView *siteTable; @property(nonatomic, weak) IBOutlet NSProgressIndicator *progressView; diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m index 3c6a3537..66abc447 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m @@ -25,6 +25,7 @@ #import "PearlProfiler.h" #define MPAlertIncorrectMP @"MPAlertIncorrectMP" +#define MPAlertChangeMP @"MPAlertChangeMP" #define MPAlertCreateSite @"MPAlertCreateSite" #define MPAlertChangeType @"MPAlertChangeType" #define MPAlertChangeLogin @"MPAlertChangeLogin" @@ -34,6 +35,7 @@ @interface MPPasswordWindowController() @property(nonatomic, copy) NSString *currentSiteText; +@property(nonatomic, strong) CAGradientLayer *siteGradient; @end @implementation MPPasswordWindowController { BOOL _skipTextChange; } @@ -57,6 +59,12 @@ [self fadeIn]; [self updateUser]; }]; + [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + NSWindow *sheet = [self.window attachedSheet]; + if (sheet) + [NSApp endSheet:sheet]; + }]; [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { [self fadeOut]; @@ -69,20 +77,20 @@ queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { [self updateUser]; }]; - [self.elementsController observeKeyPath:@"selection" - withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { - [self updateSelection]; + [self observeKeyPath:@"elementsController.selection" + withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { + [_self updateSelection]; }]; NSSearchFieldCell *siteFieldCell = self.siteField.cell; siteFieldCell.searchButtonCell = nil; siteFieldCell.cancelButtonCell = nil; - CAGradientLayer *gradient = [CAGradientLayer layer]; - gradient.colors = @[ (__bridge id)[NSColor whiteColor].CGColor, (__bridge id)[NSColor clearColor].CGColor ]; - gradient.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; - gradient.frame = self.siteTable.bounds; - self.siteTable.superview.superview.layer.mask = gradient; + self.siteGradient = [CAGradientLayer layer]; + self.siteGradient.colors = @[ (__bridge id)[NSColor whiteColor].CGColor, (__bridge id)[NSColor clearColor].CGColor ]; + self.siteGradient.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; + self.siteGradient.frame = self.siteTable.bounds; + self.siteTable.superview.superview.layer.mask = self.siteGradient; } - (void)flagsChanged:(NSEvent *)theEvent { @@ -91,6 +99,14 @@ if (alternatePressed != self.alternatePressed) { self.alternatePressed = alternatePressed; [self.selectedElement updateContent]; + + if (self.locked) { + NSTextField *passwordField = self.securePasswordField; + if (self.securePasswordField.isHidden) + passwordField = self.revealPasswordField; + [passwordField becomeFirstResponder]; + [[passwordField currentEditor] moveToEndOfLine:nil]; + } } [super flagsChanged:theEvent]; @@ -107,29 +123,11 @@ - (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector { - if (control == self.passwordField) { + if (control == self.siteField) { if (commandSelector == @selector( insertNewline: )) { - NSString *password = self.passwordField.stringValue; - [self.progressView startAnimation:self]; - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { - MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; - NSString *userName = activeUser.name; - BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:password]; - - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - [self.progressView stopAnimation:self]; - if (!success) - [[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ - NSLocalizedDescriptionKey : PearlString( @"Incorrect master password for user %@", userName ) - }]] beginSheetModalForWindow:self.window modalDelegate:self - didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertIncorrectMP]; - }]; - }]; + [self useSite]; return YES; } - } - - if (control == self.siteField) { if (commandSelector == @selector( moveUp: )) { [self.elementsController selectPrevious:self]; return YES; @@ -155,6 +153,26 @@ return YES; } +- (IBAction)doUnlockUser:(id)sender { + + [self.progressView startAnimation:self]; + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; + NSString *userName = activeUser.name; + BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:self.masterPassword]; + + PearlMainQueue( ^{ + self.masterPassword = nil; + [self.progressView stopAnimation:self]; + if (!success) + [[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey : PearlString( @"Incorrect master password for user %@", userName ) + }]] beginSheetModalForWindow:self.window modalDelegate:self + didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertIncorrectMP]; + } ); + }]; +} + - (IBAction)doSearchElements:(id)sender { [self updateElements]; @@ -180,6 +198,34 @@ if (contextInfo == MPAlertIncorrectMP) return; + if (contextInfo == MPAlertChangeMP) { + switch (returnCode) { + case NSAlertFirstButtonReturn: { + // "Reset" button. + [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context]; + NSString *activeUserName = activeUser.name; + activeUser.keyID = nil; + [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; + [context saveToStore]; + + PearlMainQueue( ^{ + NSAlert *alert_ = [NSAlert new]; + alert_.messageText = @"Master Password Reset"; + alert_.informativeText = strf( @"%@'s master password has been reset.\n\nYou can now set a new one by logging in.", + activeUserName ); + [alert_ beginSheetModalForWindow:self.window modalDelegate:nil didEndSelector:NULL contextInfo:nil]; + + if ([MPMacAppDelegate get].key) + [[MPMacAppDelegate get] signOutAnimated:YES]; + } ); + }]; + } + default: + break; + } + return; + } if (contextInfo == MPAlertCreateSite) { switch (returnCode) { case NSAlertFirstButtonReturn: { @@ -265,7 +311,7 @@ - (NSString *)query { - return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]; + return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]?: @""; } - (void)insertObject:(MPElementModel *)model inElementsAtIndex:(NSUInteger)index { @@ -318,6 +364,21 @@ didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeLogin]; } +- (IBAction)resetMasterPassword:(id)sender { + + MPUserEntity *activeUser = [MPMacAppDelegate get].activeUserForMainThread; + + NSAlert *alert = [NSAlert new]; + [alert addButtonWithTitle:@"Reset"]; + [alert addButtonWithTitle:@"Cancel"]; + [alert setMessageText:@"Reset My Master Password"]; + [alert setInformativeText:strf( @"This will allow you to change %@'s master password.\n\n" + @"WARNING: All your site passwords will change. Do this only if you've forgotten your " + @"master password and are fully prepared to change all your sites' passwords to the new ones.", activeUser.name )]; + [alert beginSheetModalForWindow:self.window modalDelegate:self + didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeMP]; +} + - (IBAction)changePassword:(id)sender { if (!self.selectedElement.stored) @@ -367,10 +428,6 @@ - (BOOL)handleCommand:(SEL)commandSelector { - if (commandSelector == @selector( insertNewline: )) { - [self useSite]; - return YES; - } if (commandSelector == @selector( cancel: ) || commandSelector == @selector( cancelOperation: )) { [self fadeOut]; return YES; @@ -379,29 +436,52 @@ return NO; } +- (void)useSite { + + MPElementModel *selectedElement = [self selectedElement]; + if (selectedElement) { + // Performing action while content is available. Copy it. + [self copyContent:selectedElement.content]; + + [self fadeOut]; + + NSUserNotification *notification = [NSUserNotification new]; + notification.title = @"Password Copied"; + if (selectedElement.loginName.length) + notification.subtitle = PearlString( @"%@ at %@", selectedElement.loginName, selectedElement.siteName ); + else + notification.subtitle = selectedElement.siteName; + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + } + else { + NSString *siteName = [self.siteField stringValue]; + if ([siteName length]) + // Performing action without content but a site name is written. + [self createNewSite:siteName]; + } +} + - (void)updateUser { [MPMacAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) { - self.passwordField.hidden = YES; - self.siteField.hidden = YES; - self.siteTable.hidden = YES; + self.locked = YES; self.inputLabel.stringValue = @""; - self.passwordField.stringValue = @""; self.siteField.stringValue = @""; MPUserEntity *mainActiveUser = [[MPMacAppDelegate get] activeUserInContext:mainContext]; if (mainActiveUser) { if ([MPMacAppDelegate get].key) { self.inputLabel.stringValue = strf( @"%@'s password for:", mainActiveUser.name ); - self.siteField.hidden = NO; - self.siteTable.hidden = NO; + self.locked = NO; [self.siteField becomeFirstResponder]; } else { self.inputLabel.stringValue = strf( @"Enter %@'s master password:", mainActiveUser.name ); - self.passwordField.hidden = NO; - [self.passwordField becomeFirstResponder]; + NSTextField *passwordField = self.securePasswordField; + if (self.securePasswordField.isHidden) + passwordField = self.revealPasswordField; + [passwordField becomeFirstResponder]; } } @@ -464,33 +544,20 @@ NSMakeRange( siteNameQueryRange.length, siteName.length - siteNameQueryRange.length ); } - NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.elementsController.selectionIndex)]; - [[(NSClipView *)self.siteTable.superview animator] setBoundsOrigin:selectedCellFrame.origin]; + [self.siteTable scrollRowToVisible:(NSInteger)self.elementsController.selectionIndex]; + [self updateGradient]; } -- (void)useSite { +- (void)updateGradient { - MPElementModel *selectedElement = [self selectedElement]; - if (selectedElement) { - // Performing action while content is available. Copy it. - [self copyContent:selectedElement.content]; - - [self fadeOut]; - - NSUserNotification *notification = [NSUserNotification new]; - notification.title = @"Password Copied"; - if (selectedElement.loginName.length) - notification.subtitle = PearlString( @"%@ at %@", selectedElement.loginName, selectedElement.siteName ); - else - notification.subtitle = selectedElement.siteName; - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; - } - else { - NSString *siteName = [self.siteField stringValue]; - if ([siteName length]) - // Performing action without content but a site name is written. - [self createNewSite:siteName]; - } + NSView *siteScrollView = self.siteTable.superview.superview; + NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.elementsController.selectionIndex)]; + CGFloat selectedOffset = [siteScrollView convertPoint:selectedCellFrame.origin fromView:self.siteTable].y; + CGFloat gradientOpacity = selectedOffset / siteScrollView.bounds.size.height; + self.siteGradient.colors = @[ + (__bridge id)[NSColor whiteColor].CGColor, + (__bridge id)[NSColor colorWithDeviceWhite:1 alpha:gradientOpacity].CGColor + ]; } - (void)createNewSite:(NSString *)siteName { diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib b/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib index 45c31f49..c96c28c6 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib @@ -1,7 +1,8 @@ - + - + + @@ -9,11 +10,12 @@ - - + + + @@ -23,14 +25,14 @@ - - + + - + - + @@ -41,19 +43,19 @@ - + -