mirror of
https://github.com/MacPass/MacPass.git
synced 2025-12-15 13:32:26 +00:00
443 lines
17 KiB
Objective-C
443 lines
17 KiB
Objective-C
//
|
|
// MPEntryInspectorViewController.m
|
|
// MacPass
|
|
//
|
|
// Created by Michael Starke on 27.07.13.
|
|
// Copyright (c) 2013 HicknHack Software GmbH. All rights reserved.
|
|
//
|
|
|
|
#import "MPEntryInspectorViewController.h"
|
|
#import "MPAttachmentTableViewDelegate.h"
|
|
#import "MPCustomFieldTableViewDelegate.h"
|
|
#import "MPPasswordCreatorViewController.h"
|
|
#import "MPAttachmentTableDataSource.h"
|
|
#import "MPWindowAssociationsTableViewDelegate.h"
|
|
|
|
#import "NSString+MPPasswordCreation.h"
|
|
|
|
#import "MPDocument.h"
|
|
#import "MPIconHelper.h"
|
|
#import "MPValueTransformerHelper.h"
|
|
#import "MPTemporaryFileStorage.h"
|
|
#import "MPTemporaryFileStorageCenter.h"
|
|
#import "MPActionHelper.h"
|
|
#import "MPSettingsHelper.h"
|
|
|
|
#import "KPKEntry.h"
|
|
#import "KPKBinary.h"
|
|
#import "KPKAutotype.h"
|
|
#import "KPKTimeInfo.h"
|
|
#import "KPKWindowAssociation.h"
|
|
|
|
#import "HNHScrollView.h"
|
|
#import "HNHRoundedSecureTextField.h"
|
|
|
|
typedef NS_ENUM(NSUInteger, MPEntryTab) {
|
|
MPEntryTabGeneral,
|
|
MPEntryTabFiles,
|
|
MPEntryTabCustomFields,
|
|
MPEntryTabAutotype
|
|
};
|
|
|
|
@interface MPEntryInspectorViewController () {
|
|
@private
|
|
NSArrayController *_attachmentsController;
|
|
NSArrayController *_customFieldsController;
|
|
NSArrayController *_windowAssociationsController;
|
|
MPAttachmentTableViewDelegate *_attachmentTableDelegate;
|
|
MPCustomFieldTableViewDelegate *_customFieldTableDelegate;
|
|
MPAttachmentTableDataSource *_attachmentDataSource;
|
|
MPWindowAssociationsTableViewDelegate *_windowAssociationsTableDelegate;
|
|
}
|
|
|
|
@property (nonatomic, assign) BOOL showPassword;
|
|
@property (nonatomic, assign) MPEntryTab activeTab;
|
|
@property (strong) NSPopover *activePopover;
|
|
|
|
@property (nonatomic, weak) KPKEntry *entry;
|
|
@property (strong) MPTemporaryFileStorage *quicklookStorage;
|
|
|
|
@end
|
|
|
|
@implementation MPEntryInspectorViewController
|
|
|
|
- (id)init {
|
|
return [self initWithNibName:@"EntryInspectorView" bundle:nil];
|
|
}
|
|
|
|
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
|
|
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
|
if (self) {
|
|
_showPassword = NO;
|
|
_attachmentsController = [[NSArrayController alloc] init];
|
|
_customFieldsController = [[NSArrayController alloc] init];
|
|
_windowAssociationsController = [[NSArrayController alloc] init];
|
|
_attachmentTableDelegate = [[MPAttachmentTableViewDelegate alloc] init];
|
|
_customFieldTableDelegate = [[MPCustomFieldTableViewDelegate alloc] init];
|
|
_attachmentDataSource = [[MPAttachmentTableDataSource alloc] init];
|
|
_windowAssociationsTableDelegate = [[MPWindowAssociationsTableViewDelegate alloc] init];
|
|
_attachmentTableDelegate.viewController = self;
|
|
_customFieldTableDelegate.viewController = self;
|
|
_activeTab = MPEntryTabGeneral;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)didLoadView {
|
|
|
|
[self _addScrollViewWithView:self.generalView atTab:MPEntryTabGeneral];
|
|
[self _addScrollViewWithView:self.autotypView atTab:MPEntryTabAutotype];
|
|
|
|
[self.infoTabControl bind:NSSelectedIndexBinding toObject:self withKeyPath:NSStringFromSelector(@selector(activeTab)) options:nil];
|
|
[self.tabView bind:NSSelectedIndexBinding toObject:self withKeyPath:NSStringFromSelector(@selector(activeTab)) options:nil];
|
|
|
|
/* Set background to clearcolor so we can draw in the scrollview */
|
|
[self.attachmentTableView setBackgroundColor:[NSColor clearColor]];
|
|
[self.attachmentTableView bind:NSContentBinding toObject:_attachmentsController withKeyPath:NSStringFromSelector(@selector(arrangedObjects)) options:nil];
|
|
[self.attachmentTableView setDelegate:_attachmentTableDelegate];
|
|
[self.attachmentTableView setDataSource:_attachmentDataSource];
|
|
[self.attachmentTableView registerForDraggedTypes:@[NSFilenamesPboardType]];
|
|
/* Set background to clearcolor so we can draw in the scrollview */
|
|
[self.customFieldsTableView setBackgroundColor:[NSColor clearColor]];
|
|
[self.customFieldsTableView bind:NSContentBinding toObject:_customFieldsController withKeyPath:NSStringFromSelector(@selector(arrangedObjects)) options:nil];
|
|
[self.customFieldsTableView setDelegate:_customFieldTableDelegate];
|
|
|
|
[self.windowAssociationsTableView setBackgroundColor:[NSColor clearColor]];
|
|
[self.windowAssociationsTableView setDelegate:_windowAssociationsTableDelegate];
|
|
[self.windowAssociationsTableView bind:NSContentBinding toObject:_windowAssociationsController withKeyPath:NSStringFromSelector(@selector(arrangedObjects)) options:nil];
|
|
|
|
[self.passwordTextField bind:NSStringFromSelector(@selector(showPassword)) toObject:self withKeyPath:NSStringFromSelector(@selector(showPassword)) options:nil];
|
|
[self.togglePassword bind:NSValueBinding toObject:self withKeyPath:NSStringFromSelector(@selector(showPassword)) options:nil];
|
|
}
|
|
|
|
- (void)setupBindings:(MPDocument *)document {
|
|
[self bind:NSStringFromSelector(@selector(entry)) toObject:document withKeyPath:NSStringFromSelector(@selector(selectedEntry)) options:nil];
|
|
}
|
|
|
|
- (void)setEntry:(KPKEntry *)entry {
|
|
if(_entry != entry) {
|
|
_entry = entry;
|
|
[self _updateContent];
|
|
}
|
|
}
|
|
|
|
- (void)regsiterNotificationsForDocument:(MPDocument *)document {
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(_didAddItem:)
|
|
name:MPDocumentItemAddedNotification
|
|
object:document];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Actions
|
|
|
|
- (void)addCustomField:(id)sender {
|
|
MPDocument *document = [[self windowController] document];
|
|
[document createCustomAttribute:self.entry];
|
|
}
|
|
- (void)removeCustomField:(id)sender {
|
|
NSUInteger index = [sender tag];
|
|
KPKAttribute *attribute = self.entry.customAttributes[index];
|
|
[self.entry removeCustomAttribute:attribute];
|
|
}
|
|
|
|
- (void)saveAttachment:(id)sender {
|
|
NSInteger row = [self.attachmentTableView selectedRow];
|
|
if(row < 0) {
|
|
return; // No selection
|
|
}
|
|
KPKBinary *binary = self.entry.binaries[row];
|
|
NSSavePanel *savePanel = [NSSavePanel savePanel];
|
|
[savePanel setCanCreateDirectories:YES];
|
|
[savePanel setNameFieldStringValue:binary.name];
|
|
|
|
[savePanel beginSheetModalForWindow:[[self windowController] window] completionHandler:^(NSInteger result) {
|
|
if(result == NSFileHandlingPanelOKButton) {
|
|
[binary saveToLocation:[savePanel URL]];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)addAttachment:(id)sender {
|
|
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
|
[openPanel setCanChooseDirectories:NO];
|
|
[openPanel setCanChooseFiles:YES];
|
|
[openPanel setAllowsMultipleSelection:YES];
|
|
[openPanel beginSheetModalForWindow:[[self windowController] window] completionHandler:^(NSInteger result) {
|
|
if(result == NSFileHandlingPanelOKButton) {
|
|
for (NSURL *attachmentURL in [openPanel URLs]) {
|
|
KPKBinary *binary = [[KPKBinary alloc] initWithContentsOfURL:attachmentURL];
|
|
[self.entry addBinary:binary];
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)removeAttachment:(id)sender {
|
|
NSInteger row = [self.attachmentTableView selectedRow];
|
|
if(row < 0) {
|
|
return; // no selection
|
|
}
|
|
KPKBinary *binary = self.entry.binaries[row];
|
|
[self.entry removeBinary:binary];
|
|
}
|
|
|
|
- (void)addWindowAssociation:(id)sender {
|
|
KPKWindowAssociation *associtation = [[KPKWindowAssociation alloc] initWithWindow:NSLocalizedString(@"DEFAULT_WINDOW_TITLE", "") keystrokeSequence:nil];
|
|
[self.entry.autotype addAssociation:associtation];
|
|
}
|
|
|
|
- (void)removeWindowAssociation:(id)sender {
|
|
NSInteger row = [self.windowAssociationsTableView selectedRow];
|
|
if(row > - 1 && row < [self.entry.autotype.associations count]) {
|
|
[self.entry.autotype removeAssociation:self.entry.autotype.associations[row]];
|
|
}
|
|
}
|
|
|
|
- (void)toggleQuicklookPreview:(id)sender {
|
|
if([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
|
|
QLPreviewPanel *panel = [QLPreviewPanel sharedPreviewPanel];
|
|
if([self acceptsPreviewPanelControl:nil]) {
|
|
[self _updatePreviewItemForPanel:panel];
|
|
[panel reloadData];
|
|
}
|
|
else {
|
|
[panel orderOut:sender];
|
|
}
|
|
}
|
|
else {
|
|
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:sender];
|
|
}
|
|
}
|
|
|
|
- (void)beginEditing {
|
|
[self _toggleEditing:YES];
|
|
|
|
}
|
|
- (void)endEditing {
|
|
[self _toggleEditing:NO];
|
|
}
|
|
|
|
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
|
|
switch([MPActionHelper typeForAction:[menuItem action]]) {
|
|
case MPActionToggleQuicklook: {
|
|
BOOL enabled = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyEnableQuicklookPreview];
|
|
return enabled ? [self acceptsPreviewPanelControl:nil] : NO;
|
|
}
|
|
default:
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark QLPreviewPanelDelegate
|
|
|
|
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel {
|
|
if(self.activeTab == MPEntryTabFiles) {
|
|
return ([self.attachmentTableView selectedRow] != -1);
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel {
|
|
[self _updatePreviewItemForPanel:panel];
|
|
}
|
|
|
|
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
|
|
MPTemporaryFileStorage *storage = (MPTemporaryFileStorage *)panel.dataSource;
|
|
[[MPTemporaryFileStorageCenter defaultCenter] unregisterStorage:storage];
|
|
}
|
|
|
|
- (void)_updatePreviewItemForPanel:(QLPreviewPanel *)panel {
|
|
NSInteger row = [self.attachmentTableView selectedRow];
|
|
NSAssert(row > -1, @"Row needs to be selected");
|
|
KPKBinary *binary = self.entry.binaries[row];
|
|
MPTemporaryFileStorage *oldStorage = (MPTemporaryFileStorage *)panel.dataSource;
|
|
[[MPTemporaryFileStorageCenter defaultCenter] unregisterStorage:oldStorage];
|
|
panel.dataSource = [[MPTemporaryFileStorageCenter defaultCenter] storageForBinary:binary];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Popovers
|
|
|
|
- (IBAction)_popUpPasswordGenerator:(id)sender {
|
|
[self.generatePasswordButton setEnabled:NO];
|
|
[self _showPopopver:[[MPPasswordCreatorViewController alloc] init] atView:self.passwordTextField onEdge:NSMinYEdge];
|
|
}
|
|
|
|
- (void)_showPopopver:(NSViewController *)viewController atView:(NSView *)view onEdge:(NSRectEdge)edge {
|
|
if(_activePopover.contentViewController == viewController) {
|
|
return; // Do nothing, we already did show the controller
|
|
}
|
|
[_activePopover close];
|
|
NSAssert(_activePopover == nil, @"Popover hast to be niled out");
|
|
_activePopover = [[NSPopover alloc] init];
|
|
_activePopover.delegate = self;
|
|
_activePopover.behavior = NSPopoverBehaviorTransient;
|
|
if([viewController respondsToSelector:@selector(setCloseTarget:)]) {
|
|
[(id)viewController setCloseTarget:_activePopover];
|
|
}
|
|
_activePopover.contentViewController = viewController;
|
|
|
|
[_activePopover showRelativeToRect:NSZeroRect ofView:view preferredEdge:edge];
|
|
}
|
|
|
|
- (void)popoverDidClose:(NSNotification *)notification {
|
|
/* We do not enable the button all the time, but it's wokring find this way */
|
|
[self.generatePasswordButton setEnabled:YES];
|
|
id controller = _activePopover.contentViewController;
|
|
/* Check for password wizzard */
|
|
if([controller respondsToSelector:@selector(generatedPassword)]) {
|
|
NSString *password = [controller generatedPassword];
|
|
/* We should only use the password if there is actally one */
|
|
if([password length] > 0) {
|
|
self.entry.password = [controller generatedPassword];
|
|
}
|
|
}
|
|
/* TODO: Check for Icon wizzard */
|
|
|
|
_activePopover = nil;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark UI Setup
|
|
- (void)_addScrollViewWithView:(NSView *)view atTab:(MPEntryTab)tab {
|
|
/* ScrollView setup for the General Tab */
|
|
|
|
HNHScrollView *scrollView = [[HNHScrollView alloc] init];
|
|
scrollView.actAsFlipped = NO;
|
|
scrollView.showBottomShadow = NO;
|
|
[scrollView setHasVerticalScroller:YES];
|
|
[scrollView setDrawsBackground:NO];
|
|
[scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
NSView *clipView = [scrollView contentView];
|
|
|
|
NSTabViewItem *tabViewItem = [self.tabView tabViewItemAtIndex:tab];
|
|
NSView *tabView = [tabViewItem view];
|
|
/*
|
|
DO NEVER SET setTranslatesAutoresizingMaskIntoConstraints on NSTabViewItem's view
|
|
[tabView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
*/
|
|
[scrollView setDocumentView:view];
|
|
[tabView addSubview:scrollView];
|
|
[tabViewItem setInitialFirstResponder:scrollView];
|
|
|
|
NSDictionary *views = NSDictionaryOfVariableBindings(view, scrollView);
|
|
[[scrollView superview] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|"
|
|
options:0
|
|
metrics:nil
|
|
views:views ]];
|
|
[[scrollView superview] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|"
|
|
options:0
|
|
metrics:nil
|
|
views:views]];
|
|
[clipView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|"
|
|
options:0
|
|
metrics:nil
|
|
views:views]];
|
|
[[self view] layoutSubtreeIfNeeded];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Entry Selection
|
|
- (void)_updateContent {
|
|
[self _bindEntry];
|
|
[self _bindAttachments];
|
|
[self _bindCustomFields];
|
|
[self _bindAutotype];
|
|
}
|
|
|
|
- (void)_bindEntry {
|
|
if(self.entry) {
|
|
[self.titleTextField bind:NSValueBinding toObject:self.entry withKeyPath:NSStringFromSelector(@selector(title)) options:nil];
|
|
[self.passwordTextField bind:NSValueBinding toObject:self.entry withKeyPath:NSStringFromSelector(@selector(password)) options:nil];
|
|
[self.usernameTextField bind:NSValueBinding toObject:self.entry withKeyPath:NSStringFromSelector(@selector(username)) options:nil];
|
|
[self.URLTextField bind:NSValueBinding toObject:self.entry withKeyPath:NSStringFromSelector(@selector(url)) options:nil];
|
|
[self.expiresCheckButton bind:NSTitleBinding
|
|
toObject:self.entry.timeInfo
|
|
withKeyPath:NSStringFromSelector(@selector(expiryTime))
|
|
options:@{ NSValueTransformerNameBindingOption:MPExpiryDateValueTransformer }];
|
|
[self.expiresCheckButton bind:NSValueBinding toObject:self.entry.timeInfo withKeyPath:NSStringFromSelector(@selector(expires)) options:nil];
|
|
[self.tagsTokenField bind:NSValueBinding toObject:self.entry withKeyPath:NSStringFromSelector(@selector(tags)) options:nil];
|
|
}
|
|
else {
|
|
[self.titleTextField unbind:NSValueBinding];
|
|
[self.passwordTextField unbind:NSValueBinding];
|
|
[self.usernameTextField unbind:NSValueBinding];
|
|
[self.URLTextField unbind:NSValueBinding];
|
|
[self.expiresCheckButton unbind:NSValueBinding];
|
|
[self.expiresCheckButton unbind:NSTitleBinding];
|
|
}
|
|
}
|
|
|
|
- (void)_bindAttachments {
|
|
if(self.entry) {
|
|
[_attachmentsController bind:NSContentArrayBinding toObject:self.entry withKeyPath:NSStringFromSelector(@selector(binaries)) options:nil];
|
|
}
|
|
else if([_attachmentsController content] != nil){
|
|
[_attachmentsController unbind:NSContentArrayBinding];
|
|
[_attachmentsController setContent:nil];
|
|
}
|
|
}
|
|
|
|
- (void)_bindCustomFields {
|
|
if(self.entry) {
|
|
[_customFieldsController bind:NSContentArrayBinding toObject:self.entry withKeyPath:NSStringFromSelector(@selector(customAttributes)) options:nil];
|
|
}
|
|
else if ([_customFieldsController content] != nil ) {
|
|
[_customFieldsController unbind:NSContentArrayBinding];
|
|
[_customFieldsController setContent:nil];
|
|
}
|
|
}
|
|
|
|
- (void)_bindAutotype {
|
|
if(self.entry) {
|
|
[self.enableAutotypeCheckButton bind:NSValueBinding toObject:self.entry.autotype withKeyPath:NSStringFromSelector(@selector(isEnabled)) options:nil];
|
|
[self.customEntrySequenceTextField bind:NSEnabledBinding toObject:self.entry.autotype withKeyPath:NSStringFromSelector(@selector(isEnabled)) options:nil];
|
|
[self.customEntrySequenceTextField bind:NSValueBinding toObject:self.entry.autotype withKeyPath:NSStringFromSelector(@selector(defaultKeystrokeSequence)) options:nil];
|
|
[_windowAssociationsController bind:NSContentArrayBinding toObject:self.entry.autotype withKeyPath:NSStringFromSelector(@selector(associations)) options:nil];
|
|
}
|
|
else {
|
|
[self.enableAutotypeCheckButton unbind:NSValueBinding];
|
|
[self.customEntrySequenceTextField unbind:NSEnabledBinding];
|
|
[self.customEntrySequenceTextField unbind:NSValueBinding];
|
|
if([_windowAssociationsController content] != nil) {
|
|
[_windowAssociationsController unbind:NSContentArrayBinding];
|
|
[_windowAssociationsController setContent:nil];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)_toggleEditing:(BOOL)edit {
|
|
/* TODO: not fully working right now */
|
|
|
|
[_titleTextField setEditable:edit];
|
|
[_titleTextField setSelectable:edit];
|
|
[_usernameTextField setEditable:edit];
|
|
[_usernameTextField setSelectable:edit];
|
|
[_URLTextField setEditable:edit];
|
|
[_URLTextField setSelectable:edit];
|
|
[_passwordTextField setEditable:edit];
|
|
[_passwordTextField setSelectable:edit];
|
|
|
|
[_createdTextField setEditable:edit];
|
|
[_createdTextField setSelectable:edit];
|
|
[_modifiedTextField setEditable:edit];
|
|
[_modifiedTextField setSelectable:edit];
|
|
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark MPDocument Notifications
|
|
|
|
- (void)_didAddItem:(NSNotification *)notification {
|
|
[self.tabView selectTabViewItemAtIndex:MPEntryTabGeneral];
|
|
[self.titleTextField becomeFirstResponder];
|
|
}
|
|
|
|
@end
|