2014-06-22 01:56:28 +00:00
|
|
|
/**
|
|
|
|
* 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 <lhunath@lyndir.com>
|
|
|
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
|
|
*/
|
|
|
|
|
2012-03-05 08:53:32 +00:00
|
|
|
//
|
2014-06-22 01:56:28 +00:00
|
|
|
// MPPasswordWindowController.h
|
|
|
|
// MPPasswordWindowController
|
2012-03-05 08:53:32 +00:00
|
|
|
//
|
2014-06-22 01:56:28 +00:00
|
|
|
// Created by lhunath on 2014-06-18.
|
|
|
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
2012-03-05 08:53:32 +00:00
|
|
|
//
|
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
#import <QuartzCore/QuartzCore.h>
|
2012-03-05 08:53:32 +00:00
|
|
|
#import "MPPasswordWindowController.h"
|
2013-04-25 01:23:53 +00:00
|
|
|
#import "MPMacAppDelegate.h"
|
2012-05-13 08:24:19 +00:00
|
|
|
#import "MPAppDelegate_Store.h"
|
2014-06-24 04:55:08 +00:00
|
|
|
#import "MPElementModel.h"
|
|
|
|
#import "MPAppDelegate_Key.h"
|
2014-06-25 00:30:15 +00:00
|
|
|
#import "PearlProfiler.h"
|
2014-06-24 04:55:08 +00:00
|
|
|
|
2014-06-26 05:19:42 +00:00
|
|
|
#define MPAlertIncorrectMP @"MPAlertIncorrectMP"
|
2014-06-30 03:18:25 +00:00
|
|
|
#define MPAlertChangeMP @"MPAlertChangeMP"
|
2014-06-26 05:19:42 +00:00
|
|
|
#define MPAlertCreateSite @"MPAlertCreateSite"
|
|
|
|
#define MPAlertChangeType @"MPAlertChangeType"
|
|
|
|
#define MPAlertChangeLogin @"MPAlertChangeLogin"
|
|
|
|
#define MPAlertChangeContent @"MPAlertChangeContent"
|
|
|
|
#define MPAlertDeleteSite @"MPAlertDeleteSite"
|
2012-03-05 08:53:32 +00:00
|
|
|
|
2014-06-24 14:47:04 +00:00
|
|
|
@interface MPPasswordWindowController()
|
|
|
|
|
|
|
|
@property(nonatomic, copy) NSString *currentSiteText;
|
2014-06-30 03:18:25 +00:00
|
|
|
@property(nonatomic, strong) CAGradientLayer *siteGradient;
|
2014-06-24 14:47:04 +00:00
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation MPPasswordWindowController { BOOL _skipTextChange; }
|
2012-03-05 08:53:32 +00:00
|
|
|
|
2014-02-20 12:29:05 +00:00
|
|
|
#pragma mark - Life
|
|
|
|
|
2012-03-05 08:53:32 +00:00
|
|
|
- (void)windowDidLoad {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
[super windowDidLoad];
|
2013-05-07 04:45:06 +00:00
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
// [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification object:nil
|
|
|
|
// queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
// [self fadeIn];
|
|
|
|
// }];
|
|
|
|
// [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
|
|
|
|
// queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
// [self fadeOut];
|
|
|
|
// }];
|
2013-06-27 00:23:02 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
|
2014-06-22 01:56:28 +00:00
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
[self fadeIn];
|
2014-06-24 04:55:08 +00:00
|
|
|
[self updateUser];
|
2014-06-22 01:56:28 +00:00
|
|
|
}];
|
2014-06-30 03:18:25 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
|
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
NSWindow *sheet = [self.window attachedSheet];
|
|
|
|
if (sheet)
|
|
|
|
[NSApp endSheet:sheet];
|
|
|
|
}];
|
2014-06-24 14:47:04 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
|
2014-06-22 01:56:28 +00:00
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
[self fadeOut];
|
2014-05-19 18:37:05 +00:00
|
|
|
}];
|
2014-02-22 23:27:14 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
|
2014-06-22 01:56:28 +00:00
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
[self updateUser];
|
2014-02-20 12:29:05 +00:00
|
|
|
}];
|
2014-06-22 01:56:28 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
|
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
[self updateUser];
|
2014-02-12 05:13:12 +00:00
|
|
|
}];
|
2014-06-30 03:18:25 +00:00
|
|
|
[self observeKeyPath:@"elementsController.selection"
|
|
|
|
withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
|
|
|
[_self updateSelection];
|
2014-06-24 14:47:04 +00:00
|
|
|
}];
|
2014-06-25 02:00:33 +00:00
|
|
|
|
|
|
|
NSSearchFieldCell *siteFieldCell = self.siteField.cell;
|
|
|
|
siteFieldCell.searchButtonCell = nil;
|
|
|
|
siteFieldCell.cancelButtonCell = nil;
|
2014-06-26 05:19:42 +00:00
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
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;
|
2013-04-30 05:49:53 +00:00
|
|
|
}
|
2013-04-27 04:34:28 +00:00
|
|
|
|
2014-06-27 03:13:21 +00:00
|
|
|
- (void)flagsChanged:(NSEvent *)theEvent {
|
|
|
|
|
|
|
|
BOOL alternatePressed = (theEvent.modifierFlags & NSAlternateKeyMask) != 0;
|
|
|
|
if (alternatePressed != self.alternatePressed) {
|
|
|
|
self.alternatePressed = alternatePressed;
|
|
|
|
[self.selectedElement updateContent];
|
2014-06-30 03:18:25 +00:00
|
|
|
|
|
|
|
if (self.locked) {
|
|
|
|
NSTextField *passwordField = self.securePasswordField;
|
|
|
|
if (self.securePasswordField.isHidden)
|
|
|
|
passwordField = self.revealPasswordField;
|
|
|
|
[passwordField becomeFirstResponder];
|
|
|
|
[[passwordField currentEditor] moveToEndOfLine:nil];
|
|
|
|
}
|
2014-06-27 03:13:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[super flagsChanged:theEvent];
|
|
|
|
}
|
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
#pragma mark - NSResponder
|
|
|
|
|
|
|
|
- (void)doCommandBySelector:(SEL)commandSelector {
|
|
|
|
|
|
|
|
[self handleCommand:commandSelector];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - NSTextFieldDelegate
|
|
|
|
|
|
|
|
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
|
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
if (control == self.siteField) {
|
2014-06-24 04:55:08 +00:00
|
|
|
if (commandSelector == @selector( insertNewline: )) {
|
2014-06-30 03:18:25 +00:00
|
|
|
[self useSite];
|
2014-06-24 04:55:08 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
if (commandSelector == @selector( moveUp: )) {
|
|
|
|
[self.elementsController selectPrevious:self];
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
if (commandSelector == @selector( moveDown: )) {
|
|
|
|
[self.elementsController selectNext:self];
|
|
|
|
return YES;
|
|
|
|
}
|
2014-06-25 02:00:33 +00:00
|
|
|
if ([NSStringFromSelector( commandSelector ) rangeOfString:@"delete"].location == 0) {
|
|
|
|
_skipTextChange = YES;
|
|
|
|
return NO;
|
|
|
|
}
|
2014-06-24 04:55:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return [self handleCommand:commandSelector];
|
|
|
|
}
|
|
|
|
|
2014-06-27 03:13:21 +00:00
|
|
|
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
|
|
|
|
|
|
|
|
if (control == self.siteField)
|
|
|
|
[fieldEditor replaceCharactersInRange:fieldEditor.selectedRange withString:@""];
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
- (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];
|
|
|
|
} );
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2014-06-24 14:47:04 +00:00
|
|
|
- (IBAction)doSearchElements:(id)sender {
|
|
|
|
|
|
|
|
[self updateElements];
|
|
|
|
}
|
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
#pragma mark - NSTextViewDelegate
|
|
|
|
|
|
|
|
- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
|
|
|
|
|
|
|
|
return [self handleCommand:commandSelector];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - NSTableViewDataSource
|
|
|
|
|
|
|
|
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
|
|
|
|
|
|
|
|
return (NSInteger)[self.elements count];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - NSAlert
|
|
|
|
|
|
|
|
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
|
|
|
|
2014-06-24 14:47:04 +00:00
|
|
|
if (contextInfo == MPAlertIncorrectMP)
|
2014-06-24 04:55:08 +00:00
|
|
|
return;
|
2014-06-30 03:18:25 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-06-24 04:55:08 +00:00
|
|
|
if (contextInfo == MPAlertCreateSite) {
|
|
|
|
switch (returnCode) {
|
|
|
|
case NSAlertFirstButtonReturn: {
|
|
|
|
// "Create" button.
|
|
|
|
[[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element) {
|
|
|
|
if (element)
|
2014-06-26 05:19:42 +00:00
|
|
|
PearlMainQueue( ^{ [self updateElements]; } );
|
|
|
|
}];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (contextInfo == MPAlertChangeType) {
|
|
|
|
switch (returnCode) {
|
|
|
|
case NSAlertFirstButtonReturn: {
|
|
|
|
// "Save" button.
|
|
|
|
MPElementType type = (MPElementType)[self.passwordTypesMatrix.selectedCell tag];
|
|
|
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
MPElementEntity *entity = [[MPMacAppDelegate get] changeElement:[self.selectedElement entityInContext:context]
|
|
|
|
saveInContext:context toType:type];
|
|
|
|
if ([entity isKindOfClass:[MPElementStoredEntity class]] && ![(MPElementStoredEntity *)entity contentObject].length)
|
|
|
|
PearlMainQueue( ^{
|
|
|
|
[self changePassword:nil];
|
|
|
|
} );
|
|
|
|
}];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (contextInfo == MPAlertChangeLogin) {
|
|
|
|
switch (returnCode) {
|
|
|
|
case NSAlertFirstButtonReturn: {
|
|
|
|
// "Save" button.
|
|
|
|
NSString *loginName = [(NSSecureTextField *)alert.accessoryView stringValue];
|
|
|
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
MPElementEntity *entity = [self.selectedElement entityInContext:context];
|
|
|
|
entity.loginName = loginName;
|
|
|
|
[context saveToStore];
|
2014-06-24 04:55:08 +00:00
|
|
|
}];
|
|
|
|
break;
|
|
|
|
}
|
2014-06-26 05:19:42 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (contextInfo == MPAlertChangeContent) {
|
|
|
|
switch (returnCode) {
|
|
|
|
case NSAlertFirstButtonReturn: {
|
|
|
|
// "Save" button.
|
|
|
|
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
|
|
|
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
MPElementEntity *entity = [self.selectedElement entityInContext:context];
|
|
|
|
[entity.algorithm saveContent:password toElement:entity usingKey:[MPMacAppDelegate get].key];
|
|
|
|
[context saveToStore];
|
|
|
|
}];
|
2014-06-24 04:55:08 +00:00
|
|
|
break;
|
2014-06-26 05:19:42 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (contextInfo == MPAlertDeleteSite) {
|
|
|
|
switch (returnCode) {
|
|
|
|
case NSAlertFirstButtonReturn: {
|
|
|
|
// "Delete" button.
|
|
|
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
[context deleteObject:[self.selectedElement entityInContext:context]];
|
|
|
|
[context saveToStore];
|
|
|
|
}];
|
|
|
|
break;
|
|
|
|
}
|
2014-06-24 04:55:08 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - State
|
|
|
|
|
2014-06-25 02:00:33 +00:00
|
|
|
- (NSString *)query {
|
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]?: @"";
|
2014-06-25 02:00:33 +00:00
|
|
|
}
|
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
- (void)insertObject:(MPElementModel *)model inElementsAtIndex:(NSUInteger)index {
|
|
|
|
|
|
|
|
[self.elements insertObject:model atIndex:index];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)removeObjectFromElementsAtIndex:(NSUInteger)index {
|
|
|
|
|
|
|
|
[self.elements removeObjectAtIndex:index];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (MPElementModel *)selectedElement {
|
|
|
|
|
|
|
|
return [self.elementsController.selectedObjects firstObject];
|
|
|
|
}
|
|
|
|
|
2014-06-26 05:19:42 +00:00
|
|
|
#pragma mark - Actions
|
|
|
|
|
2014-06-27 03:13:21 +00:00
|
|
|
- (IBAction)settings:(id)sender {
|
|
|
|
|
|
|
|
[self fadeOut:NO];
|
|
|
|
[[MPMacAppDelegate get] showPopup:sender];
|
|
|
|
}
|
|
|
|
|
2014-06-26 05:19:42 +00:00
|
|
|
- (IBAction)deleteElement:(id)sender {
|
|
|
|
|
|
|
|
NSAlert *alert = [NSAlert new];
|
|
|
|
[alert addButtonWithTitle:@"Delete"];
|
|
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
|
|
[alert setMessageText:@"Delete Site?"];
|
|
|
|
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedElement.siteName )];
|
|
|
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
|
|
|
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)changeLogin:(id)sender {
|
|
|
|
|
|
|
|
NSAlert *alert = [NSAlert new];
|
|
|
|
[alert addButtonWithTitle:@"Save"];
|
|
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
|
|
[alert setMessageText:@"Change Login Name"];
|
2014-06-27 03:13:21 +00:00
|
|
|
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedElement.siteName )];
|
2014-06-26 05:19:42 +00:00
|
|
|
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
|
|
|
loginField.stringValue = self.selectedElement.loginName?: @"";
|
|
|
|
[loginField selectText:self];
|
|
|
|
[alert setAccessoryView:loginField];
|
|
|
|
[alert layout];
|
|
|
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
|
|
|
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeLogin];
|
|
|
|
}
|
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
- (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];
|
|
|
|
}
|
|
|
|
|
2014-06-26 05:19:42 +00:00
|
|
|
- (IBAction)changePassword:(id)sender {
|
|
|
|
|
|
|
|
if (!self.selectedElement.stored)
|
|
|
|
return;
|
|
|
|
|
|
|
|
NSAlert *alert = [NSAlert new];
|
|
|
|
[alert addButtonWithTitle:@"Save"];
|
|
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
|
|
[alert setMessageText:@"Change Password"];
|
2014-06-27 03:13:21 +00:00
|
|
|
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedElement.siteName )];
|
2014-06-26 05:19:42 +00:00
|
|
|
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
|
|
|
|
[alert layout];
|
|
|
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
|
|
|
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeContent];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)changeType:(id)sender {
|
|
|
|
|
|
|
|
MPElementModel *element = self.selectedElement;
|
|
|
|
NSArray *types = [element.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN];
|
|
|
|
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
|
|
|
|
for (NSUInteger t = 0; t < [types count]; ++t) {
|
|
|
|
MPElementType type = [types[t] unsignedIntegerValue];
|
|
|
|
NSString *title = [element.algorithm nameOfType:type];
|
|
|
|
if (type & MPElementTypeClassGenerated)
|
|
|
|
title = [element.algorithm generateContentNamed:element.siteName ofType:type
|
|
|
|
withCounter:element.counter usingKey:[MPMacAppDelegate get].key];
|
|
|
|
|
|
|
|
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
|
|
|
|
cell.tag = type;
|
|
|
|
cell.state = type == element.type? NSOnState: NSOffState;
|
|
|
|
cell.title = title;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSAlert *alert = [NSAlert new];
|
|
|
|
[alert addButtonWithTitle:@"Save"];
|
|
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
|
|
[alert setMessageText:@"Change Password Type"];
|
2014-06-27 03:13:21 +00:00
|
|
|
[alert setInformativeText:strf( @"Choose a new password type for: %@", element.siteName )];
|
2014-06-26 05:19:42 +00:00
|
|
|
[alert setAccessoryView:self.passwordTypesBox];
|
|
|
|
[alert layout];
|
|
|
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
|
|
|
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeType];
|
|
|
|
}
|
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
#pragma mark - Private
|
|
|
|
|
|
|
|
- (BOOL)handleCommand:(SEL)commandSelector {
|
|
|
|
|
|
|
|
if (commandSelector == @selector( cancel: ) || commandSelector == @selector( cancelOperation: )) {
|
|
|
|
[self fadeOut];
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
- (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];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
- (void)updateUser {
|
2014-02-20 12:29:05 +00:00
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
[MPMacAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) {
|
2014-06-30 03:18:25 +00:00
|
|
|
self.locked = YES;
|
2014-06-25 02:00:33 +00:00
|
|
|
|
|
|
|
self.inputLabel.stringValue = @"";
|
|
|
|
self.siteField.stringValue = @"";
|
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
MPUserEntity *mainActiveUser = [[MPMacAppDelegate get] activeUserInContext:mainContext];
|
2014-06-24 04:55:08 +00:00
|
|
|
if (mainActiveUser) {
|
|
|
|
if ([MPMacAppDelegate get].key) {
|
|
|
|
self.inputLabel.stringValue = strf( @"%@'s password for:", mainActiveUser.name );
|
2014-06-30 03:18:25 +00:00
|
|
|
self.locked = NO;
|
2014-06-24 04:55:08 +00:00
|
|
|
[self.siteField becomeFirstResponder];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
self.inputLabel.stringValue = strf( @"Enter %@'s master password:", mainActiveUser.name );
|
2014-06-30 03:18:25 +00:00
|
|
|
NSTextField *passwordField = self.securePasswordField;
|
|
|
|
if (self.securePasswordField.isHidden)
|
|
|
|
passwordField = self.revealPasswordField;
|
|
|
|
[passwordField becomeFirstResponder];
|
2014-06-24 04:55:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[self updateElements];
|
2013-05-16 02:42:21 +00:00
|
|
|
}];
|
2013-05-07 04:45:06 +00:00
|
|
|
}
|
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
- (void)updateElements {
|
2012-05-04 22:15:51 +00:00
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
if (![MPMacAppDelegate get].key) {
|
|
|
|
self.elements = nil;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-06-25 02:00:33 +00:00
|
|
|
PearlProfiler *profiler = [PearlProfiler profilerForTask:@"updateElements"];
|
|
|
|
NSString *query = [self query];
|
|
|
|
[profiler finishJob:@"query"];
|
2014-06-24 04:55:08 +00:00
|
|
|
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
|
|
|
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
|
|
|
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
|
|
|
|
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
|
|
|
query, query, [[MPMacAppDelegate get] activeUserInContext:context]];
|
2014-06-25 02:00:33 +00:00
|
|
|
[profiler finishJob:@"setup fetch"];
|
2014-06-24 04:55:08 +00:00
|
|
|
|
|
|
|
NSError *error = nil;
|
|
|
|
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
|
|
|
|
if (!siteResults) {
|
|
|
|
err( @"While fetching elements for completion: %@", error );
|
|
|
|
return;
|
|
|
|
}
|
2014-06-25 02:00:33 +00:00
|
|
|
[profiler finishJob:@"do fetch"];
|
2014-06-24 04:55:08 +00:00
|
|
|
|
|
|
|
NSMutableArray *newElements = [NSMutableArray arrayWithCapacity:[siteResults count]];
|
|
|
|
for (MPElementEntity *element in siteResults)
|
|
|
|
[newElements addObject:[[MPElementModel alloc] initWithEntity:element]];
|
2014-06-25 02:00:33 +00:00
|
|
|
[profiler finishJob:@"make models"];
|
2014-06-24 04:55:08 +00:00
|
|
|
self.elements = newElements;
|
2014-06-25 02:00:33 +00:00
|
|
|
[profiler finishJob:@"update elements"];
|
2014-06-24 04:55:08 +00:00
|
|
|
}];
|
2014-06-25 02:00:33 +00:00
|
|
|
[profiler finishJob:@"done"];
|
2014-06-24 04:55:08 +00:00
|
|
|
}
|
|
|
|
|
2014-06-24 14:47:04 +00:00
|
|
|
- (void)updateSelection {
|
|
|
|
|
|
|
|
if (_skipTextChange) {
|
|
|
|
_skipTextChange = NO;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-06-25 02:00:33 +00:00
|
|
|
NSString *siteName = self.selectedElement.siteName;
|
|
|
|
if (!siteName)
|
2014-06-24 14:47:04 +00:00
|
|
|
return;
|
|
|
|
|
2014-06-27 03:13:21 +00:00
|
|
|
if ([self.window isKeyWindow] && [self.siteField isEqual:[self.window firstResponder]]) {
|
|
|
|
NSRange siteNameQueryRange = [siteName rangeOfString:[self query]];
|
|
|
|
self.siteField.stringValue = siteName;
|
2014-06-24 14:47:04 +00:00
|
|
|
|
2014-06-27 03:13:21 +00:00
|
|
|
if (siteNameQueryRange.location == 0)
|
|
|
|
self.siteField.currentEditor.selectedRange =
|
|
|
|
NSMakeRange( siteNameQueryRange.length, siteName.length - siteNameQueryRange.length );
|
|
|
|
}
|
2014-06-26 05:19:42 +00:00
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
[self.siteTable scrollRowToVisible:(NSInteger)self.elementsController.selectionIndex];
|
|
|
|
[self updateGradient];
|
2014-06-24 14:47:04 +00:00
|
|
|
}
|
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
- (void)updateGradient {
|
2014-06-24 04:55:08 +00:00
|
|
|
|
2014-06-30 03:18:25 +00:00
|
|
|
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
|
|
|
|
];
|
2014-06-24 04:55:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)createNewSite:(NSString *)siteName {
|
|
|
|
|
|
|
|
PearlMainQueue( ^{
|
|
|
|
NSAlert *alert = [NSAlert new];
|
|
|
|
[alert addButtonWithTitle:@"Create"];
|
|
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
|
|
[alert setMessageText:@"Create site?"];
|
|
|
|
[alert setInformativeText:PearlString( @"Do you want to create a new site named:\n\n%@", siteName )];
|
|
|
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
|
|
|
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertCreateSite];
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)copyContent:(NSString *)content {
|
|
|
|
|
|
|
|
[[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
|
|
|
|
if (![[NSPasteboard generalPasteboard] setString:content forType:NSPasteboardTypeString]) {
|
|
|
|
wrn( @"Couldn't copy password to pasteboard." );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
|
|
|
[[self.selectedElement entityInContext:moc] use];
|
|
|
|
[moc saveToStore];
|
|
|
|
}];
|
|
|
|
}
|
2014-02-19 05:59:01 +00:00
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
- (void)fadeIn {
|
2014-02-20 12:29:05 +00:00
|
|
|
|
2014-06-24 14:47:04 +00:00
|
|
|
if ([self.window isOnActiveSpace] && self.window.alphaValue)
|
|
|
|
return;
|
|
|
|
|
2014-06-25 02:00:33 +00:00
|
|
|
PearlProfiler *profiler = [PearlProfiler profilerForTask:@"fadeIn"];
|
2014-06-28 04:04:40 +00:00
|
|
|
CGDirectDisplayID displayID = [self.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
|
|
|
|
CGImageRef capturedImage = CGDisplayCreateImage( displayID );
|
|
|
|
if (!capturedImage || CGImageGetWidth( capturedImage ) <= 1) {
|
|
|
|
wrn( @"Failed to capture screen image for display: %d", displayID );
|
2014-06-26 05:19:42 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-06-28 04:04:40 +00:00
|
|
|
[profiler finishJob:@"captured window: %d, on screen: %@", displayID, self.window.screen.deviceDescription];
|
2014-06-22 01:56:28 +00:00
|
|
|
NSImage *screenImage = [[NSImage alloc] initWithCGImage:capturedImage size:NSMakeSize(
|
|
|
|
CGImageGetWidth( capturedImage ) / self.window.backingScaleFactor,
|
|
|
|
CGImageGetHeight( capturedImage ) / self.window.backingScaleFactor )];
|
2014-06-26 05:19:42 +00:00
|
|
|
[profiler finishJob:@"image size: %@, bytes: %ld", NSStringFromSize( screenImage.size ), screenImage.TIFFRepresentation.length];
|
2014-02-20 12:29:05 +00:00
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
NSImage *smallImage = [[NSImage alloc] initWithSize:NSMakeSize(
|
|
|
|
CGImageGetWidth( capturedImage ) / 20,
|
|
|
|
CGImageGetHeight( capturedImage ) / 20 )];
|
2014-06-28 04:04:40 +00:00
|
|
|
CFRelease( capturedImage );
|
2014-06-22 01:56:28 +00:00
|
|
|
[smallImage lockFocus];
|
|
|
|
[screenImage drawInRect:(NSRect){ .origin = CGPointZero, .size = smallImage.size, }
|
|
|
|
fromRect:NSZeroRect
|
|
|
|
operation:NSCompositeSourceOver
|
|
|
|
fraction:1.0];
|
|
|
|
[smallImage unlockFocus];
|
2014-06-25 00:30:15 +00:00
|
|
|
[profiler finishJob:@"small image size: %@, bytes: %ld", NSStringFromSize( screenImage.size ), screenImage.TIFFRepresentation.length];
|
2014-02-20 12:29:05 +00:00
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
self.blurView.image = smallImage;
|
2014-06-25 00:30:15 +00:00
|
|
|
[profiler finishJob:@"assigned image"];
|
2014-02-12 05:13:12 +00:00
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
[self.window setFrame:self.window.screen.frame display:YES];
|
2014-06-25 00:30:15 +00:00
|
|
|
[profiler finishJob:@"assigned frame"];
|
2014-06-27 03:13:21 +00:00
|
|
|
[NSAnimationContext currentContext].timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
|
|
|
|
self.window.animator.alphaValue = 1.0;
|
2014-06-25 00:30:15 +00:00
|
|
|
[profiler finishJob:@"animating window"];
|
2014-02-12 05:13:12 +00:00
|
|
|
}
|
|
|
|
|
2014-06-22 01:56:28 +00:00
|
|
|
- (void)fadeOut {
|
2014-02-12 05:13:12 +00:00
|
|
|
|
2014-06-27 03:13:21 +00:00
|
|
|
[self fadeOut:YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)fadeOut:(BOOL)hide {
|
|
|
|
|
2014-06-24 14:47:04 +00:00
|
|
|
if (![NSApp isActive] && !self.window.alphaValue)
|
|
|
|
return;
|
|
|
|
|
2014-06-24 04:55:08 +00:00
|
|
|
[[NSAnimationContext currentContext] setCompletionHandler:^{
|
|
|
|
[self close];
|
2014-06-27 03:13:21 +00:00
|
|
|
|
|
|
|
if (hide)
|
|
|
|
[NSApp hide:self];
|
2014-06-24 04:55:08 +00:00
|
|
|
}];
|
2014-06-27 03:13:21 +00:00
|
|
|
[NSAnimationContext currentContext].timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
|
|
|
|
[self.window animator].alphaValue = 0.0;
|
2014-02-12 05:13:12 +00:00
|
|
|
}
|
|
|
|
|
2012-03-05 08:53:32 +00:00
|
|
|
@end
|