2
0

Improved input handling.

This commit is contained in:
Maarten Billemont 2014-02-20 07:29:05 -05:00
parent 775a6fd4ea
commit 43c32e0f4c
3 changed files with 160 additions and 157 deletions

View File

@ -40,7 +40,6 @@
__weak MPElementCollectionView *wSelf = self;
_representedObjectObserver = [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
dispatch_async( dispatch_get_main_queue(), ^{
dbg(@"updating login name of %@ to: %@", wSelf.representedObject.site, wSelf.representedObject.loginName);
wSelf.typeTitle = PearlString( @"Type:\n%@", wSelf.representedObject.typeName );
wSelf.loginNameTitle = PearlString( @"Login Name:\n%@", wSelf.representedObject.loginName );

View File

@ -28,6 +28,8 @@
@implementation MPPasswordWindowController
#pragma mark - Life
- (void)windowDidLoad {
if ([[MPMacConfig get].dialogStyleHUD boolValue]) {
@ -95,85 +97,7 @@
[super close];
}
- (BOOL)ensureLoadedAndUnlockedOrCloseIfLoggedOut:(BOOL)closeIfLoggedOut {
if (![self ensureStoreLoaded])
return NO;
if (self.closing || self.inProgress || !self.window.isKeyWindow)
return NO;
return [self ensureUnlocked:closeIfLoggedOut];
}
- (BOOL)ensureStoreLoaded {
if ([MPMacAppDelegate managedObjectContextForMainThreadIfReady]) {
// Store loaded.
if (self.loadingDataAlert.window)
[NSApp endSheet:self.loadingDataAlert.window];
return YES;
}
[self.loadingDataAlert = [NSAlert alertWithMessageText:@"Opening Your Data" defaultButton:@"..." alternateButton:nil otherButton:nil
informativeTextWithFormat:@""]
beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:nil];
return NO;
}
- (BOOL)ensureUnlocked:(BOOL)closeIfLoggedOut {
__block BOOL unlocked = NO;
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
NSString *userName = activeUser.name;
if (!activeUser) {
// No user to sign in with.
if (closeIfLoggedOut)
[self close];
return;
}
if ([MPMacAppDelegate get].key) {
// Already logged in.
unlocked = YES;
return;
}
if (activeUser.saveKey && closeIfLoggedOut) {
// App was locked, don't instantly unlock again.
[self close];
return;
}
if ([[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:nil]) {
// Loaded the key from the keychain.
unlocked = YES;
return;
}
// Ask the user to set the key through his master password.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if ([MPMacAppDelegate get].key)
return;
[self.siteField setStringValue:@""];
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel"
informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@",
userName];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP];
}];
}];
return unlocked;
}
#pragma mark - NSAlert
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
@ -264,55 +188,40 @@
#pragma mark - NSCollectionViewDelegate
- (void)setElementSelectionIndexes:(NSIndexSet *)elementSelectionIndexes {
// First reset bounds.
PearlMainThread(^{
NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex;
if (selectedIndex != NSNotFound && selectedIndex < self.elements.count)
[[self selectedView].animator setBoundsOrigin:NSZeroPoint];
} );
_elementSelectionIndexes = elementSelectionIndexes;
}
#pragma mark - NSTextFieldDelegate
- (void)doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(insertNewline:))
[self useSite];
}
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(cancel:)) { // Escape without completion.
if (commandSelector == @selector(cancel:)) {
[self close];
return YES;
}
if (self.elements.count) {
if (commandSelector == @selector(moveUp:)) {
self.elementSelectionIndexes =
[NSIndexSet indexSetWithIndex:(self.elementSelectionIndexes.firstIndex - 1 + self.elements.count) % self.elements.count];
return NO;
}
if (commandSelector == @selector(moveDown:)) {
self.elementSelectionIndexes =
[NSIndexSet indexSetWithIndex:(self.elementSelectionIndexes.firstIndex + 1) % self.elements.count];
return NO;
}
if (commandSelector == @selector(moveLeft:)) {
[[self selectedView].animator setBoundsOrigin:NSZeroPoint];
return NO;
}
if (commandSelector == @selector(moveRight:)) {
NSBox *selectedView = [self selectedView];
[selectedView.animator setBoundsOrigin:NSMakePoint( selectedView.bounds.size.width / 2, 0 )];
return NO;
}
if (commandSelector == @selector(moveUp:)) {
self.elementSelectionIndexes =
[NSIndexSet indexSetWithIndex:MAX(self.elementSelectionIndexes.firstIndex, (NSUInteger)1) - 1];
return YES;
}
// if ((self.siteFieldPreventCompletion = [NSStringFromSelector( commandSelector ) hasPrefix:@"delete"])) { // Backspace any time.
// _activeElementOID = nil;
// [self trySiteWithAction:NO];
// return NO;
// }
if (commandSelector == @selector(insertNewline:)) { // Return without completion.
if (commandSelector == @selector(moveDown:)) {
self.elementSelectionIndexes =
[NSIndexSet indexSetWithIndex:MIN(self.elementSelectionIndexes.firstIndex + 1, self.elements.count - 1)];
return YES;
}
if (commandSelector == @selector(moveLeft:)) {
[[self selectedView].animator setBoundsOrigin:NSZeroPoint];
return YES;
}
if (commandSelector == @selector(moveRight:)) {
[[self selectedView].animator setBoundsOrigin:NSMakePoint( self.siteCollectionView.frame.size.width / 2, 0 )];
return YES;
}
if (commandSelector == @selector(insertNewline:)) {
[self useSite];
return NO;
return YES;
}
return NO;
@ -341,7 +250,88 @@
#pragma mark - Private
- (BOOL)ensureLoadedAndUnlockedOrCloseIfLoggedOut:(BOOL)closeIfLoggedOut {
if (![self ensureStoreLoaded])
return NO;
if (self.closing || self.inProgress || !self.window.isKeyWindow)
return NO;
return [self ensureUnlocked:closeIfLoggedOut];
}
- (BOOL)ensureStoreLoaded {
if ([MPMacAppDelegate managedObjectContextForMainThreadIfReady]) {
// Store loaded.
if (self.loadingDataAlert.window)
[NSApp endSheet:self.loadingDataAlert.window];
return YES;
}
[self.loadingDataAlert = [NSAlert alertWithMessageText:@"Opening Your Data" defaultButton:@"..." alternateButton:nil otherButton:nil
informativeTextWithFormat:@""]
beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:nil];
return NO;
}
- (BOOL)ensureUnlocked:(BOOL)closeIfLoggedOut {
__block BOOL unlocked = NO;
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
NSString *userName = activeUser.name;
if (!activeUser) {
// No user to sign in with.
if (closeIfLoggedOut)
[self close];
return;
}
if ([MPMacAppDelegate get].key) {
// Already logged in.
unlocked = YES;
return;
}
if (activeUser.saveKey && closeIfLoggedOut) {
// App was locked, don't instantly unlock again.
[self close];
return;
}
if ([[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:nil]) {
// Loaded the key from the keychain.
unlocked = YES;
return;
}
// Ask the user to set the key through his master password.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if ([MPMacAppDelegate get].key)
return;
[self.siteField setStringValue:@""];
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel"
informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@",
userName];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP];
}];
}];
return unlocked;
}
- (void)updateElements {
NSString *query = [self.siteField.currentEditor string];
if (![query length] || ![MPMacAppDelegate get].key) {
self.elements = nil;
@ -397,20 +387,21 @@
if (selectedElement) {
// Performing action while content is available. Copy it.
[self copyContent:selectedElement.content];
NSBox *selectedView = [self selectedView];
dispatch_async( dispatch_get_main_queue(), ^{
[selectedView.animator setFillColor:[NSColor controlHighlightColor]];
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(0.3f * NSEC_PER_SEC) ), dispatch_get_main_queue(), ^{
dispatch_async( dispatch_get_main_queue(), ^{
[selectedView.animator setFillColor:[NSColor selectedControlColor]];
} );
} );
} );
[self close];
NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied";
if (selectedElement.loginName.length)
notification.subtitle = PearlString( @"%@ at %@", selectedElement.loginName, selectedElement.site );
else
notification.subtitle = selectedElement.site;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
else {
NSString *siteName = [self.siteField stringValue];
if ([siteName length])
// Performing action without content but a site name is written.
// Performing action without content but a site name is written.
[self createNewSite:siteName];
}
}
@ -442,6 +433,18 @@
#pragma mark - KVO
- (void)setElementSelectionIndexes:(NSIndexSet *)elementSelectionIndexes {
// First reset bounds.
PearlMainThread(^{
NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex;
if (selectedIndex != NSNotFound && selectedIndex < self.elements.count)
[[self selectedView].animator setBoundsOrigin:NSZeroPoint];
} );
_elementSelectionIndexes = elementSelectionIndexes;
}
- (void)insertObject:(MPElementModel *)model inElementsAtIndex:(NSUInteger)index {
[self.elements insertObject:model atIndex:index];

View File

@ -19,7 +19,7 @@
<window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="22" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" texturedBackground="YES"/>
<rect key="contentRect" x="600" y="530" width="480" height="320"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<value key="minSize" type="size" width="480" height="320"/>
<value key="maxSize" type="size" width="480" height="320"/>
<view key="contentView" wantsLayer="YES" id="23">
@ -31,11 +31,14 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="182">
<rect key="frame" x="140" y="253" width="200" height="22"/>
<rect key="frame" x="140" y="256" width="200" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="258"/>
</constraints>
<shadow key="shadow" blurRadius="2">
<color key="color" name="controlShadowColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" alignment="center" title="apple.com" placeholderString="Site name" usesSingleLineMode="YES" bezelStyle="round" id="185">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
@ -46,37 +49,35 @@
</connections>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="216">
<rect key="frame" x="130" y="283" width="221" height="17"/>
<rect key="frame" x="146" y="286" width="188" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<shadow key="shadow" blurRadius="1">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlShadowColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="center" title="Maarten Billemont's password for:" usesSingleLineMode="YES" id="217">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<font key="font" metaFont="palette"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tjI-mV-s8H">
<rect key="frame" x="0.0" y="0.0" width="480" height="245"/>
<rect key="frame" x="0.0" y="0.0" width="480" height="248"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="mfh-QT-ClZ">
<rect key="frame" x="1" y="1" width="478" height="243"/>
<rect key="frame" x="1" y="1" width="478" height="246"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView selectable="YES" maxNumberOfColumns="1" id="pr9-BO-vQV">
<rect key="frame" x="0.0" y="0.0" width="478" height="243"/>
<rect key="frame" x="0.0" y="0.0" width="478" height="246"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="secondaryBackgroundColor" name="controlAlternatingRowColor" catalog="System" colorSpace="catalog"/>
<connections>
<binding destination="jTN-Q9-Ajn" name="content" keyPath="arrangedObjects" id="dtF-fs-Fpe"/>
<binding destination="-2" name="selectionIndexes" keyPath="elementSelectionIndexes" previousBinding="dtF-fs-Fpe" id="UFy-9F-18H"/>
<outlet property="delegate" destination="-2" id="4cD-GP-imR"/>
<outlet property="itemPrototype" destination="QBZ-sO-HQn" id="QAi-HN-YUc"/>
</connections>
</collectionView>
</subviews>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="6vP-Im-BRg">
<rect key="frame" x="-100" y="-100" width="233" height="15"/>
@ -129,15 +130,15 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xwJ-Pu-glP">
<rect key="frame" x="6" y="36" width="70" height="17"/>
<rect key="frame" x="6" y="35" width="60" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<shadow key="shadow" blurRadius="1">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.59999999999999998" colorSpace="calibratedWhite"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="apple.com" id="ymH-M0-M5d">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="apple.com" usesSingleLineMode="YES" id="ymH-M0-M5d">
<font key="font" size="12" name="Exo2.0-Light"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@ -145,15 +146,15 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Cn5-nt-e6X">
<rect key="frame" x="342" y="36" width="131" height="17"/>
<rect key="frame" x="362" y="35" width="111" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<shadow key="shadow" blurRadius="1">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.59999999999999998" colorSpace="calibratedWhite"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="lhunath@lyndir.com" id="Px4-pS-kwG">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="lhunath@lyndir.com" usesSingleLineMode="YES" id="Px4-pS-kwG">
<font key="font" size="12" name="Exo2.0-Light"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@ -161,31 +162,31 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="a1n-Sf-Mw6">
<rect key="frame" x="6" y="8" width="207" height="35"/>
<rect key="frame" x="6" y="8" width="206" height="38"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<shadow key="shadow" blurRadius="1">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.59999999999999998" colorSpace="calibratedWhite"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="RutuTutnTeni4," id="7jb-Fa-xX3">
<font key="font" size="24" name="Menlo-Regular"/>
<color key="textColor" name="headerColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<font key="font" size="24" name="SourceCodePro-Light"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="QBZ-sO-HQn" name="value" keyPath="representedObject.content" id="19u-Zx-3vP"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mp4-r1-7Xg">
<rect key="frame" x="440" y="8" width="33" height="35"/>
<rect key="frame" x="440" y="8" width="33" height="38"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<shadow key="shadow" blurRadius="1">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.59999999999999998" colorSpace="calibratedWhite"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="12" id="Vb9-nO-cWY">
<font key="font" size="24" name="Menlo-Regular"/>
<color key="textColor" name="headerColor" catalog="System" colorSpace="catalog"/>
<font key="font" size="24" name="SourceCodePro-Light"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>