Prompting the user to logon for encrypted documents - cocoa

I have encrypted documents. After unarchiving each document, I need to prompt the user to enter the document password from a logon sheet. I have means to validate password entry against file contents (this part is done). If the password is incorrect the document shall be closed. If the password is correct the document window shall be presented with document contents.
When I attempt to load the logon sheet (via its controller) in the document's windowControllerDidLoadNib method I have unrecognized selector error as shown below:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
if (!newPasswordController){
newPasswordController = [[NewPasswordController alloc] init];
newPasswordSheet = [newPasswordController window];
}
[NSApp beginSheet:newPasswordSheet modalForWindow:[self window]
modalDelegate:self
didEndSelector:#selector(didNewPasswordEnd:returnCode:contextInfo:)
contextInfo:nil];
}
[_NSControllerObjectProxy copyWithZone:]: unrecognized selector sent to instance
The method [NewPasswordController init] is implemented as follows:
-(id)init
{
self = [super initWithWindowNibName:#"NewPassword"];
if (self) {
}
return self;
}
where the logon sheet nib file is called NewPassword.
I wonder what went wrong. What is the best way to solve this problem?

Aside from unrecognized selector issue (which I left with Apple) I managed to address the original issue Prompting the user to logon for encrypted documents and here is the solution.
Note every document is password protected and they could potentially respond to different passwords (so the issue is not 'password protected application' but 'password protected documents').
Simply inject the following code when we are about to read document contents to pop up an application modal window to verify the document password:
- (BOOL)readFromData:(NSData *)data
ofType:(NSString *)typeName
error:(NSError **)outError
{
PasswordController *passwordController = [[PasswordController alloc] init];
NSWindow *passwordSheet = [passwordController window];
NSApplication* app = [NSApplication sharedApplication];
NSInteger iret = [app runModalForWindow:passwordSheet];
NSLog(#"password dialog returned = %ld", iret);
if (iret != 0)
{
[app stop:self];
return NO;
}
[passwordController release];
...
You may also pop up another kind of window when the document is saved the first time in dataOfType, forcing the user to set the document's password.
This issue is answered now.

The issue unrecognized selector was resolved after I constructed the xib file from scratch. This indicates a serious problem though concerning IB in XCode 4, as properties of the sheet and steps taken to create bindings, key-payths etc seemed identical in both cases. Something I have done during interface construction from IB caused a corruption in the xib file in my first attempt.
I'll leave it to forum administration to delete or keep this issue. If it is value to anyone I'll file a bug report with Apple (see below)

Related

force NSDocument to save after creation

In its documents, my application uses a lot of assets that are relative to the document path. So the document must be saved before assets can be added. How can I force-call a [NSDocument saveDocumentAs] ?
I managed to do parts of it : by creating my own document controller, and inside openUntitledDocumentAndDisplay: force a call like this :
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError
{
NSDocument * res = [super openUntitledDocumentAndDisplay:displayDocument error:outError];
[res saveDocumentAs:self];
return res;
}
This forces the save dialog to appear, but unfortunately I can not check whether the user pressed cancel : the saveDocumentAs call is asynchronous and continues immediately !
Is there a way to fix this ?
I had a similar problem. By using:
saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo
you can defer your processing (or not) until after the document save dialogue has completed. This means you can find out whether the user cancelled or not. You split your processing in two, do whatever preparation you need and put the rest (that depends upon a successful save) into another method. If you use something like:
[self saveDocumentWithDelegate:self didSaveSelector:#selector(actuallyDoIt: didSave: contextInfo:) contextInfo:nil];
The document will be saved but, critically, if it has not been saved before, the Save dialogue will appear so the user can input a file name. Once he/she has done that, or cancelled, your method actuallyDoIt: (or whatever) is invoked. The didSave: parameter tells you whether the save actually happened (essentially, did the user cancel) so you can either continue or offer an alert explaining politely to the user that nothing's going to happen until they save.
I have a similar thing in my application, in my case if the user tries to do something, I pull up a prompt to say 'This requires you to save the document first' with buttons to cancel or save.
If you want to absolutely force it, then instead of using saveDocumentAs, just display your own NSSavePanel. Run it modally, check the result, save the document with the result, and if this doesn;t go smoothly, call it again. You can check if the document is saved by looking for a valid value for it's file path.
NSSavePanel can run modally. Here is how it can/should look like.
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError *__autoreleasing *)outError
{
Document *document;
NSSavePanel *panel = [NSSavePanel savePanel];
panel.prompt = #"Create";
NSInteger modalCode = [panel runModal];
if (modalCode == NSModalResponseOK) {
NSURL *URL = [panel URL];
NSError *docError;
document = [[Document alloc] initWithType:nil & docError];
[document saveToURL:URL ofType:nil forSaveOperation:NSSaveOperation completionHandler:^(NSError *error){
if(error) {
return nil;
}
[self addDocument:document];
[document makeWindowControllers];
if (displayDocument) {
[document showWindows];
}
}];
}
return document;
}
To sum up for reference:
Create custom nsdocumentsubclass in XIB (no XIB -> app did finish launching)
override openUntitledDocumentAndDisplay
(NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError;

Parse - No placeholder and field email doesn't work in SignUpViewController

I have a problem in Parse with my signUpView.
When I try with LogInAndSignUpDemo code (the Parse tutorial) it works well.
For my project, I have a button "log in" in my view, when the user selects "log in", I open the LogInviewController. If the user doesn't have an account, he can "sign up".
When he sign up, he has this view : Image of mysignupview
My problem is on this view because I don't have placeholder for password and email fields, and I can't select the email field.
My code :
-(IBAction) bouton_log_in_action:(id)sender
{
if (![PFUser currentUser]) { // No user logged in
// Create the log in view controller
PFLogInViewController *logInViewController = [[PFLogInViewController alloc] init];
[logInViewController setDelegate:self]; // Set ourselves as the delegate
// Create the sign up view controller
PFSignUpViewController *signUpViewController = [[PFSignUpViewController alloc] init];
[signUpViewController setDelegate:self]; // Set ourselves as the delegate
// Assign our sign up controller to be displayed from the login controller
[logInViewController setSignUpController:signUpViewController];
// Present the log in view controller
[self presentViewController:logInViewController animated:YES completion:NULL];
}
}
I don't understand where is the problem, because I use the code I found on Parse documentation...
I found the problem.
It's a bug, it'll be fix in 1.2.21 release...

Multiwindows problem, cocoa

I have a simple application, not document-based. I want to have a login window that allows people to login or add a user, and when they logged in successfully I want it to load the main page. If from the main page you click log out, it should destroy the main page and take you back to login page.
sounds like a simple plan, but for some reason I have a problem.
The way I have it right now, I check if the customer logged in or not in the main file AppDelegate and load different window controller. When customer logs in, I send a notification back to AppDelegate from Login Conntroller and load another window controller for main window.
Something like this:
if([[settings get:#"isLoggedIn"] isEqualToString:#"Yes"])
{
MainController *tmpMainController = [[MainController alloc] initWithWindowNibName:#"MainWindow"];
self.mainController = tmpMainController;
NSWindow *mainWindow = [tmpMainController window];
[mainWindow makeKeyAndOrderFront:self];
[tmpMainController release];
} else {
LoginController *tmpViewController = [[LoginController alloc] initWithWindowNibName:#"LoginWindow"];
self.loginController = tmpViewController;
loginWindow = [tmpViewController window];
[loginWindow makeKeyAndOrderFront:self];
[tmpViewController release];
}
Everything works fine, it displays the correct window. But the weird part happens when I log out from the main page, log in again and log out again. If I do it several times, instead of showing me 1 login window, it draws 2. If I continue the login process, on the second try I get 2 main windows. If I log out again, I see 4 cascade login windows, then I see 5 or 7 main windows. After all windows gets loaded all extra windows start getting destroyed one-by-one. It looks like when new window gets created it draws all old windows, then the new one and then destroys all old ones. I don't know why it happens. Would like some help.
Here is the code from my main controller when customer clicks log out:
-(IBAction)logOutClick:(id) sender
{
[settings set:#"isLoggedIn" value:#"No"];
[[self window] orderOut:self];
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSUserLoggedOutNotification" object: self userInfo: nil];
}
the same thing for login controller:
if ([users verifyUser]) {
[settings set:#"isLoggedIn" value:#"Yes"];
[loginView removeFromSuperview];
[[self window] orderOut:self];
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSUserLoggedInNotification" object: self userInfo: nil];
}
I have "Released when closed" checked off for both windows.
I added new nsnotification center observer every time I log out.
That was the problem.

Execute an action from a text field ONLY when enter is pressed [duplicate]

I have two NSTextFields: textFieldUserID and textFieldPassword.
For textFieldPassword, I have a delegate as follows:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
This delegate gets called when textFieldPassword has focus and I hit the enter key. This is exactly what I want.
My problem is that controlTextDidEndEditing also gets called when textFieldPassword has focus and I move the focus to textFieldUserID (via mouse or tab key). This is NOT what I want.
I tried using controlTextDidChange notification (which is getting called once per key press) but I was unable to figure out how to detect enter key ( [textFieldPassword stringValue] does not include the enter key). Can someone please help me figure this one out?
I also tried to detect if textFieldUserID was a firstResponder, but it did not work for me. Here is the code I tried out:
if ( [[[self window] firstResponder] isKindOfClass:[NSTextView class]] &&
[[self window] fieldEditor:NO forObject:nil] != nil ) {
NSTextField *field = [[[self window] firstResponder] delegate];
if (field == textFieldUserID) {
// do something based upon first-responder status
NSLog(#"is true");
}
}
I sure could use some help here!
If I understood you correctly, you could set an action for the password text field and tell the field to send its action only when the user types Return. Firstly, declare and implement an action in the class responsible for the behaviour when the user types Return on the password field. For example:
#interface SomeClass …
- (IBAction)returnOnPasswordField:(id)sender;
#end
#implementation SomeClass
- (IBAction)returnOnPasswordField:(id)sender {
// do something
}
#end
Making the text field send its action on Return only, and linking the action to a given IBAction and target, can be done either in Interface Builder or programatically.
In Interface Builder, use the Attributes Inspector, choose Action: Sent on Enter Only, and then link the text field action to an IBAction in the object that implements it, potentially the File’s Owner or the First Responder.
If you’d rather do it programatically, then:
// Make the text field send its action only when Return is pressed
[passwordTextFieldCell setSendsActionOnEndEditing:NO];
// The action selector according to the action defined in SomeClass
[passwordTextFieldCell setAction:#selector(returnOnPasswordField:)];
// someObject is the object that implements the action
[passwordTextFieldCell setTarget:someObject];
[passwordTextFieldCell setTarget:self];
[passwordTextFieldCell setAction:#selector(someAction:)];
- (void) someAction{
//handle
}

Cocoa QuickLook initiated by NSTableView Cell

I have an NSTableView that contains 2 different Columns - one is an NSImageCell that shows a file icon, and the second is a custom subclass of NSTextFieldCell that contains a quick look button on the right of the text. When I click the Quick Look button, the following code is invoked:
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
This does it's job and shows the blank Quick Look panel saying "No Items Selected." After I did a bit of research on the internet, I implemented a custom NSTableView subclass to be the Delegate and Data Source for the Quick Look panel. I get the notification that Quick Look asks if I want to be the delegate, and I respond with return YES. Even though I implement all methods in both QLPreviewPanelDataSource and QLPreviewPanelDelegate, at runtime I get this error on the console:
2010-12-24 15:32:17.235 BackMeUp[4763:80f] clicked: ~/Desktop/HUDTape.mov
2010-12-24 15:32:17.489 BackMeUp[4763:80f] [QL] QLError(): -[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
2010-12-24 15:32:17.490 BackMeUp[4763:80f] [QL] QLError(): -[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
2010-12-24 15:32:17.491 BackMeUp[4763:80f] We can now receive QL Events.
2010-12-24 15:32:18.291 BackMeUp[4763:80f] -[NSPathStore2 stringValue]: unrecognized selector sent to instance 0x5ecb10
2010-12-24 15:32:18.292 BackMeUp[4763:80f] -[NSPathStore2 stringValue]: unrecognized selector sent to instance 0x5ecb10
And the Quick Look panel does not show up, which I find rather odd. The first line above is just that I know the cell has been clicked. Anyways, here is the .m file of the custom table view subclass:
//
// BackupListTableView.m
// BackMeUp
//
// Created by Tristan Seifert on 12/24/10.
// Copyright 2010 24/7 Server. All rights reserved.
//
#import "BackupListTableView.h"
#implementation BackupListTableView
- (void) awakeFromNib {
}
// Quick Look Delegates
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
{
[QLPreviewPanel sharedPreviewPanel].delegate = self;
[QLPreviewPanel sharedPreviewPanel].dataSource = self;
NSLog(#"We can now receive QL Events.");
return YES;
}
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document is now responsible of the preview panel
// It is allowed to set the delegate, data source and refresh panel.
[QLPreviewPanel sharedPreviewPanel].delegate = self;
[QLPreviewPanel sharedPreviewPanel].dataSource = self;
}
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document loses its responsisibility on the preview panel
// Until the next call to -beginPreviewPanelControl: it must not
// change the panel's delegate, data source or refresh it.
return;
}
// Quick Look panel data source
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
{
return 1;
}
- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index
{
int selectedRow = [self selectedRow];
return [NSURL URLWithString:[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]];
}
// Quick Look panel delegate
- (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event
{
// redirect all key down events to the table view
return NO;
}
// This delegate method provides the rect on screen from which the panel will zoom.
- (NSRect)previewPanel:(QLPreviewPanel *)panel sourceFrameOnScreenForPreviewItem:(id <QLPreviewItem>)item
{
NSRect iconRect = [self rectOfColumn:1];
/*
// check that the icon rect is visible on screen
NSRect visibleRect = [self visibleRect];
// convert icon rect to screen coordinates
iconRect = [self convertRectToBase:iconRect];
iconRect.origin = [[self window] convertBaseToScreen:iconRect.origin];
*/
return iconRect;
}
// This delegate method provides a transition image between the table view and the preview panel
- (id)previewPanel:(QLPreviewPanel *)panel transitionImageForPreviewItem:(id <QLPreviewItem>)item contentRect:(NSRect *)contentRect
{
int selectedRow = [self selectedRow];
NSImage *fileIcon = [[NSWorkspace sharedWorkspace] iconForFile:[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]];
return fileIcon;
}
#end
Thanks for any help.
The documentation isn't the best for this, since it's a new feature that was added in 10.6. (Well, there is obviously the class and protocol references, but in my experience, I've always found the Companion Guides to be more helpful in understanding how the objects are intended to be used in a real-world scenario).
The QLPreviewPanelController Protocol Reference defines 3 methods:
QLPreviewPanelController Protocol Reference
The Quick Look preview panel shows previews for items provided by the first object in the responder chain that implements the methods in this protocol. You typically implement these methods in your window controller or delegate. You should never try to modify preview panel state if you’re not controlling the panel.
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
- (BOOL)beginPreviewPanelControl:(QLPreviewPanel *)panel;
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel;
I'm guessing that your code should look like this:
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
{
return YES;
}
You shouldn't be doing anything in that method besides returning YES. acceptsPreviewPanelControl: is sent to every object in the responder chain until something returns YES. By returning YES, that object effectively becomes "the controller". The latter 2 methods are called on the controller object after it returns YES from the first method. So you should only be setting the delegate and datasource in the beginPreviewPanelControl: method (at which time you will be regarded as the current controller).
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document is now responsible of the preview panel
// It is allowed to set the delegate, data source and refresh panel.
[QLPreviewPanel sharedPreviewPanel].delegate = self;
[QLPreviewPanel sharedPreviewPanel].dataSource = self;
NSLog(#"We can now receive QL Events.");
}
First:
-acceptsPreviewPanelControl should only return YES and not try to set delegate and datasource.
Then, the problem is that you get an exception breaking the panel:
2010-12-24 15:32:18.291 BackMeUp[4763:80f] -[NSPathStore2 stringValue]: unrecognized selector sent to instance 0x5ecb10
The exception is very likely caused by these invocations:
[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]
Very likely, [[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] is a file path (a NSPathStore instance which is a subclass of NSString) so it does not respond to -stringValue
So replace:
[NSURL URLWithString:[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]]
by:
[NSURL fileURLWithPath:[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow]]
Also remove the call to -stringValue in transitionImageForPreviewItem.
As a side note, it seems to be suboptimal to load the image at each call of transitionImageForPreviewItem. I suppose you already have the image computed elsewhere (displayed in your table view), try to use it.

Resources