SelectIdentityController.m   [plain text]


/*
 * Copyright 2008 Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 * require a specific license from the United States Government.
 * It is the responsibility of any person or organization contemplating
 * export to obtain such a license before exporting.
 * 
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */

#import "SelectIdentityController.h"
#import "IPCClient.h"
#import "KerberosFormatters.h"

@implementation SelectIdentityController

@synthesize associatedClient;

// ---------------------------------------------------------------------------

- (id) init
{
    return [self initWithWindowNibName: @"SelectIdentity"];
}

// ---------------------------------------------------------------------------

- (void) dealloc
{
    [identityOptionsController removeObserver:self forKeyPath:identity_string_keypath];
    [refreshTimer release];
    [identities release];
    [super dealloc];
}

// ---------------------------------------------------------------------------

- (void) awakeFromNib 
{
    NSString *key = nil;
    NSString *message = nil;
    
    [[self window] center];
    [[self window] setLevel:NSModalPanelWindowLevel];
    
    longTimeFormatter.displaySeconds = NO;
    longTimeFormatter.displayShortFormat = NO;
    
    [identityTableView setDoubleAction:@selector(select:)];
    identities = [[Identities alloc] init];
    [identitiesController setContent:identities];
    refreshTimer = [NSTimer scheduledTimerWithTimeInterval:60.0 target:self selector:@selector(timedRefresh:) userInfo:nil repeats:true];    

    [kerberosIconImageView setBadgePath:associatedClient.path];
    
    if ([associatedClient.name isEqualToString:[[NSBundle mainBundle] bundlePath]]) {
        key = @"SelectIdentityRequest";
        message = NSLocalizedStringFromTable(key, @"SelectIdentity", NULL);
    }
    else {
        key = @"SelectIdentityApplicationRequest";
        message = [NSString stringWithFormat:
                   NSLocalizedStringFromTable(key, @"SelectIdentity", NULL),
                   associatedClient.name];
    }
    [headerTextField setStringValue:message];
    
    optionsBoxHeight = [ticketOptionsBox frame].size.height + [ticketOptionsBox frame].origin.y - [ticketOptionsToggleButton frame].origin.y - [ticketOptionsToggleButton frame].size.height;
    [self toggleOptionsVisibility:nil];
    
    [identityOptionsController addObserver:self
                                forKeyPath:identity_string_keypath
                                   options:NSKeyValueObservingOptionNew
                                   context:NULL];
}

- (void)  observeValueForKeyPath:(NSString *) keyPath ofObject: (id) object change: (NSDictionary *) change context:(void *) context
{
    if (object == identityOptionsController && [keyPath isEqualToString:identity_string_keypath]) {
        BOOL enabled = [KIMUtilities validateIdentity:[identityOptionsController valueForKeyPath:identity_string_keypath]];
        [identityOptionsController setValue:[NSNumber numberWithBool:enabled] 
                                 forKeyPath:@"content.canClickOK"];
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

// ---------------------------------------------------------------------------

- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
{
    NSRect newFrame = [window frame];
    CGFloat oldHeight = [[identityTableScrollView contentView] frame].size.height;
    CGFloat newHeight = [identityTableView numberOfRows] * 
                        ([identityTableView rowHeight] + [identityTableView intercellSpacing].height);
    CGFloat yDelta = newHeight - oldHeight;
    
    newFrame.origin.y -= yDelta;
    newFrame.size.height += yDelta;
    
    return newFrame;
}

// ---------------------------------------------------------------------------

- (void) setContent: (NSMutableDictionary *) newContent
{
    [self window]; // wake up the nib connections
    [glueController setContent:newContent];
}

// ---------------------------------------------------------------------------

- (IBAction) newIdentity: (id) sender
{
    identityOptionsController.content = [[[glueController valueForKeyPath:@"content.hints.options"] 
                                          mutableCopy] autorelease];

    [self showOptions:@"new"];
}

// ---------------------------------------------------------------------------

- (IBAction) addToFavorites: (id) sender
{
    Identity *anIdentity = [identityArrayController.selectedObjects lastObject];
    identityOptionsController.content = nil;

    anIdentity.favorite = TRUE;
    
    [self saveOptions];
}

// ---------------------------------------------------------------------------

- (IBAction) removeFromFavorites: (id) sender
{
    Identity *anIdentity = [identityArrayController.selectedObjects lastObject];
    identityOptionsController.content = nil;

    anIdentity.favorite = FALSE;
    
    [self saveOptions];
}

// ---------------------------------------------------------------------------

- (IBAction) changePassword: (id) sender
{
    Identity *selectedIdentity = nil;
    
    // ignore double-click on header
    if ([sender respondsToSelector:@selector(clickedRow)] && [sender clickedRow] < 0) {
        return;
    }
    selectedIdentity = [[identityArrayController selectedObjects] lastObject];
    
    [associatedClient didSelectIdentity: selectedIdentity.identity
                                options: [identityOptionsController valueForKeyPath:@"content.options"]
                    wantsChangePassword: YES];
}

// ---------------------------------------------------------------------------

- (IBAction) select: (id) sender
{
    Identity *selectedIdentity = nil;
    
    // ignore double-click on header
    if ([sender respondsToSelector:@selector(clickedRow)] && [sender clickedRow] < 0) {
        return;
    }
    selectedIdentity = [[identityArrayController selectedObjects] lastObject];

    [associatedClient didSelectIdentity: selectedIdentity.identity
                                options: [identityOptionsController content]
                    wantsChangePassword: NO];
}

// ---------------------------------------------------------------------------

- (IBAction) cancel: (id) sender
{
    [associatedClient didCancel];
}

// ---------------------------------------------------------------------------

- (IBAction) editOptions: (id) sender
{
    Identity *anIdentity = [identityArrayController.selectedObjects lastObject];
    anIdentity.favorite = TRUE;
    
    [identityOptionsController setContent:anIdentity.options];
    
    [self showOptions:@"edit"];
}

// ---------------------------------------------------------------------------

- (IBAction) cancelOptions: (id) sender
{
    identityOptionsController.content = nil;
    [NSApp endSheet:ticketOptionsWindow returnCode:NSUserCancelledError];
    
    // dump changed settings
    [identities reload];
}

// ---------------------------------------------------------------------------

- (IBAction) doneOptions: (id) sender
{
//    Identity *anIdentity = identityOptionsController.content;
    
    
    [NSApp endSheet: ticketOptionsWindow];
}

// ---------------------------------------------------------------------------

- (IBAction) checkboxDidChange: (id) sender
{
    if ([[identityOptionsController valueForKeyPath:uses_default_options_keypath] boolValue]) {
        // merge defaults onto current options
        NSMutableDictionary *currentOptions = [identityOptionsController content];
        NSDictionary *defaultOptions = [KIMUtilities dictionaryForKimOptions:NULL];
        NSLog(@"using default ticket options");
        [currentOptions addEntriesFromDictionary:defaultOptions];
        // update the sliders, since their values aren't bound
        [validLifetimeSlider setDoubleValue:[[identityOptionsController valueForKeyPath:valid_lifetime_keypath] doubleValue]];
        [renewableLifetimeSlider setDoubleValue:[[identityOptionsController valueForKeyPath:renewal_lifetime_keypath] doubleValue]];
    }    
}

// ---------------------------------------------------------------------------

- (IBAction) sliderDidChange: (id) sender
{
    NSInteger increment = 0;
    NSInteger newValue = 0;
    NSString *keyPath = nil;
    if ([sender isEqual:validLifetimeSlider]) {
        increment = VALID_LIFETIME_INCREMENT;
        keyPath = valid_lifetime_keypath;
    }
    else if ([sender isEqual:renewableLifetimeSlider]) {
        increment = RENEWABLE_LIFETIME_INCREMENT;
        keyPath = renewal_lifetime_keypath;
    }
    if (increment > 0) {
        newValue = ([sender integerValue] / increment) * increment;
        [identityOptionsController setValue:[NSNumber numberWithInteger:
                                             (newValue < increment) ? increment : newValue] 
                                 forKeyPath:keyPath];
    }
}

// ---------------------------------------------------------------------------

- (void) showOptions: (NSString *) contextInfo
{
    Identity *anIdentity = [[identityArrayController selectedObjects] lastObject];
    BOOL isIdentityNameNotEditable = (!anIdentity.hasCCache || [contextInfo isEqualToString:@"new"]);
    NSString *identityString = ([contextInfo isEqualToString:@"new"]) ? @"" : anIdentity.identity;
    
    [identityOptionsController setValue:identityString
                             forKeyPath:identity_string_keypath];    
    [identityOptionsController setValue:[NSNumber numberWithInteger:[KIMUtilities minValidLifetime]]
                           forKeyPath:min_valid_keypath];
    [identityOptionsController setValue:[NSNumber numberWithInteger:[KIMUtilities maxValidLifetime]]
                           forKeyPath:max_valid_keypath];
    [identityOptionsController setValue:[NSNumber numberWithInteger:[KIMUtilities minRenewableLifetime]]
                           forKeyPath:min_renewable_keypath];
    [identityOptionsController setValue:[NSNumber numberWithInteger:[KIMUtilities maxRenewableLifetime]]
                           forKeyPath:max_renewable_keypath];
    [identityOptionsController setValue:[NSNumber numberWithBool:!isIdentityNameNotEditable]
                             forKeyPath:@"content.hasCCache"];
    
    [validLifetimeSlider setIntegerValue:
     [[identityOptionsController valueForKeyPath:valid_lifetime_keypath] integerValue]];
    [renewableLifetimeSlider setIntegerValue:
     [[identityOptionsController valueForKeyPath:renewal_lifetime_keypath] integerValue]];
    [self sliderDidChange:validLifetimeSlider];
    [self sliderDidChange:renewableLifetimeSlider];
    
    [NSApp beginSheet: ticketOptionsWindow 
       modalForWindow: [self window] 
        modalDelegate: self 
       didEndSelector: @selector(didEndSheet:returnCode:contextInfo:)
          contextInfo: contextInfo];
}

// ---------------------------------------------------------------------------

- (void) didEndSheet: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
{
    kim_error err = KIM_NO_ERROR;
    if (returnCode != NSUserCancelledError) {
        if ([(NSString *)contextInfo isEqualToString:@"new"]) {
            Identity *newIdentity = [[Identity alloc] 
                                     initWithIdentity:[identityOptionsController valueForKeyPath:identity_string_keypath] 
                                     options:identityOptionsController.content];
            newIdentity.favorite = YES;

            err = [identities addIdentity:newIdentity];

            if (err) {
                NSLog(@"%s received error %@ trying to add identity %@", _cmd, [KIMUtilities stringForLastKIMError:err], [newIdentity description]);
            }
            [newIdentity release];
            [self saveOptions];

        }
        else if ([(NSString *)contextInfo isEqualToString:@"edit"]) {
            Identity *editedIdentity = [[identityArrayController selectedObjects] lastObject];
            editedIdentity.favorite = YES;
            editedIdentity.identity = [identityOptionsController valueForKeyPath:identity_string_keypath];
            editedIdentity.options = identityOptionsController.content;

            [self saveOptions];
            
        }
    } else {
        [identityOptionsController setContent:nil];
    }
    [sheet orderOut:self];

}

// ---------------------------------------------------------------------------

- (void) saveOptions
{
    // attempt to preserve the selection
    Identity *anIdentity = [[identityArrayController selectedObjects] lastObject];
    NSUInteger a, b, c;
    
    a = [identityArrayController.content indexOfObject: anIdentity];
    b = NSNotFound;
    
    [identities synchronizePreferences];
    
    /*
     * select same object as before if it's still in the array
     * if not, select same index as before or end of array, whichever is less
     */
    
    b = [identityArrayController.content indexOfObject:anIdentity];
    c = [identityArrayController.content count] - 1;


    [identityArrayController setSelectionIndex: (b == NSNotFound) ? (a > c) ? c : a : b];
}

// ---------------------------------------------------------------------------

- (IBAction) toggleOptionsVisibility: (id) sender
{
    NSRect newFrame = [NSWindow contentRectForFrameRect:[ticketOptionsWindow frame] styleMask:[ticketOptionsWindow styleMask]];
    CGFloat newHeight;
    
    if ([ticketOptionsBox isHidden]) {
        newHeight = newFrame.size.height + optionsBoxHeight;
        newFrame.origin.y += newFrame.size.height;
        newFrame.origin.y -= newHeight;
        newFrame.size.height = newHeight;
        newFrame = [NSWindow frameRectForContentRect:newFrame styleMask:[ticketOptionsWindow styleMask]];

        [ticketOptionsWindow setFrame:newFrame display:YES animate:YES];
        [ticketOptionsBox setHidden:NO];
        [sender setTitle:NSLocalizedStringFromTable(@"SelectIdentityHideOptions", @"SelectIdentity", NULL)];
    }
    else {
        newHeight = newFrame.size.height - optionsBoxHeight;
        newFrame.origin.y += newFrame.size.height;
        newFrame.origin.y -= newHeight;
        newFrame.size.height = newHeight;
        newFrame = [NSWindow frameRectForContentRect:newFrame styleMask:[ticketOptionsWindow styleMask]];
        
        [ticketOptionsBox setHidden:YES];
        [ticketOptionsWindow setFrame:newFrame display:YES animate:YES];
        [sender setTitle:NSLocalizedStringFromTable(@"SelectIdentityShowOptions", @"SelectIdentity", NULL)];
    }
}

// ---------------------------------------------------------------------------

- (void) timedRefresh:(NSTimer *)timer
{
    // refetch data to update expiration times
    [identityArrayController rearrangeObjects];
}

@end