/* * TicketListController.m * * $Header$ * * Copyright 2004 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 "TicketListController.h" #import "CacheCollection.h" #import "Cache.h" #import "Utilities.h" #import "ErrorAlert.h" #import "Preferences.h" NSString *ActiveUserMenuToolbarItemIdentifier = @"ActiveUserMenu"; NSString *GetTicketsToolbarItemIdentifier = @"GetTickets"; NSString *RenewTicketsToolbarItemIdentifier = @"RenewTickets"; NSString *DestroyTicketsToolbarItemIdentifier = @"DestroyTickets"; NSString *ChangePasswordToolbarItemIdentifier = @"ChangePassword"; NSString *TicketInfoToolbarItemIdentifier = @"TicketInfo"; NSString *TicketListFrameAutosaveName = @"KATicketListWindowPosition"; @interface ActiveUserToolbarItem : NSToolbarItem { } - (id) initWithItemIdentifier: (NSString *) itemIdentifier; - (void) validate; - (void) menuNeedsUpdate: (NSMenu *) menu; @end @implementation ActiveUserToolbarItem // --------------------------------------------------------------------------- - (id) initWithItemIdentifier: (NSString *) itemIdentifier { if ((self = [super initWithItemIdentifier: itemIdentifier])) { NSRect frameRect = { { 0, 0 }, { 150, 22 } }; NSPopUpButton *button = [[[NSPopUpButton alloc] initWithFrame: frameRect pullsDown: NO] autorelease]; if (button == NULL) { [self release]; return NULL; } [button setAutoenablesItems: NO]; // item state set by menuNeedsUpdate: [[button menu] setDelegate: self]; [[button cell] setControlSize: NSSmallControlSize]; NSSize minimumSize = [button bounds].size; NSSize maximumSize = [button bounds].size; maximumSize.width += 200; [self setView: button]; [self setMinSize: minimumSize]; [self setMaxSize: maximumSize]; } return self; } // --------------------------------------------------------------------------- - (void) validate { NSMenu *menu = [[self view] menu]; if (menu != NULL) { [self menuNeedsUpdate: menu]; } } // --------------------------------------------------------------------------- - (void) menuNeedsUpdate: (NSMenu *) menu { // Assume all menus are the active user popup menu // If we get a second menu type, we'll have to figure out something else int defaultCacheIndex = 0; [Utilities synchronizeCacheMenu: menu fontSize: 11 // default size staticPrefixItemsCount: 0 headerItem: NO checkDefaultCache: YES defaultCacheIndex: &defaultCacheIndex selector: @selector (changeActiveUser:) sender: [self target]]; NSPopUpButton *popUpButton = (NSPopUpButton *) [self view]; if ((popUpButton != NULL) && [popUpButton isKindOfClass: [NSPopUpButton class]]) { [popUpButton selectItemAtIndex: defaultCacheIndex]; } } @end #pragma mark - @implementation TicketListController // --------------------------------------------------------------------------- - (id) init { if ((self = [super initWithWindowNibName: @"TicketList"])) { cacheCollection = NULL; toolbar = NULL; minuteTimer = NULL; cacheNameString = NULL; cascadePoint = NSZeroPoint; cacheCollection = [[CacheCollection sharedCacheCollection] retain]; if (cacheCollection == NULL) { [self release]; return NULL; } toolbar = [[NSToolbar alloc] initWithIdentifier: @"TicketListToolbar"]; if (toolbar == NULL) { [self release]; return NULL; } [toolbar setAutosavesConfiguration: YES]; } return self; } // --------------------------------------------------------------------------- - (void) dealloc { dprintf ("Ticket List window %lx releasing...", (long) self); [[NSNotificationCenter defaultCenter] removeObserver: self]; if (minuteTimer != NULL) { // invalidate a TargetOwnedTimer before releasing it [minuteTimer invalidate]; [minuteTimer release]; } if (cacheCollection != NULL) { [cacheCollection release]; } if (cacheNameString != NULL) { [cacheNameString release]; } if (toolbar != NULL) { [toolbar release]; } [super dealloc]; } // --------------------------------------------------------------------------- - (void) windowDidLoad { [super windowDidLoad]; dprintf ("TicketListController %lx entering windowDidLoad:...", (long) self); minuteTimer = [[TargetOwnedTimer scheduledTimerWithTimeInterval: 30 // controls the minute counters in the window target: self selector: @selector (minuteTimer:) userInfo: NULL repeats: YES] retain]; // toolbar [toolbar setDelegate: self]; [[self window] setToolbar: toolbar]; // Automatically resize columns in the tables. // Do not move columns when disclosure triangles are selected [cacheCollectionTableView setColumnAutoresizingStyle: NSTableViewSequentialColumnAutoresizingStyle]; [ticketsOutlineView setColumnAutoresizingStyle: NSTableViewSequentialColumnAutoresizingStyle]; [ticketsOutlineView setAutoresizesOutlineColumn: NO]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector (cacheCollectionDidChange:) name: CacheCollectionDidChangeNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector (preferencesDidChange:) name: PreferencesDidChangeNotification object: nil]; // load the first time manually. After that the notifications will do the work [self cacheCollectionDidChange: NULL]; } // --------------------------------------------------------------------------- - (IBAction) showWindow: (id) sender { // Check to see if the window was closed before. ([self window] will load the window) if (![[self window] isVisible]) { dprintf ("TicketListController %lx displaying window...", (long) self); [[self window] center]; [self preferencesDidChange: NULL]; [self synchronizeCascadePoint]; } [super showWindow: sender]; } #pragma mark --- Actions -- // --------------------------------------------------------------------------- - (IBAction) getTickets: (id) sender { KLStatus err = [Principal getTickets]; if (err == klNoErr) { [cacheCollection update]; } else if (err != klUserCanceledErr) { [ErrorAlert alertForError: err action: KerberosGetTicketsAction modalForWindow: [self window]]; } } // --------------------------------------------------------------------------- - (IBAction) changePasswordForSelectedCache: (id) sender { Cache *cache = [self selectedCache]; if (cache != NULL) { KLStatus err = [[cache principal] changePassword]; if (err == klNoErr) { [cacheCollection update]; } else if (err != klUserCanceledErr) { [ErrorAlert alertForError: err action: KerberosChangePasswordAction modalForWindow: [self window]]; } } } // --------------------------------------------------------------------------- - (IBAction) destroyTicketsForSelectedCache: (id) sender { Cache *cache = [self selectedCache]; if (cache != NULL) { KLStatus err = [[cache principal] destroyTickets]; if (err == klNoErr) { [cacheCollection update]; } else if (err != klUserCanceledErr) { [ErrorAlert alertForError: err action: KerberosDestroyTicketsAction modalForWindow: [self window]]; } } } // --------------------------------------------------------------------------- - (IBAction) renewTicketsForSelectedCache: (id) sender { Cache *cache = [self selectedCache]; if (cache != NULL) { KLStatus err = [[cache principal] renewTickets]; if (err == klNoErr) { [cacheCollection update]; } else if (err != klUserCanceledErr) { [ErrorAlert alertForError: err action: KerberosRenewTicketsAction modalForWindow: [self window]]; } } } // --------------------------------------------------------------------------- - (IBAction) validateTicketsForSelectedCache: (id) sender { Cache *cache = [self selectedCache]; if (cache != NULL) { KLStatus err = [[cache principal] validateTickets]; if (err == klNoErr) { [cacheCollection update]; } else if (err != klUserCanceledErr) { [ErrorAlert alertForError: err action: KerberosValidateTicketsAction modalForWindow: [self window]]; } } } // --------------------------------------------------------------------------- - (IBAction) changeActiveUser: (id) sender { if ([sender isMemberOfClass: [NSMenuItem class]]) { NSMenuItem *item = sender; NSMenu *menu = [item menu]; if ([cacheCollection numberOfCaches] > 0) { Cache *cache = [cacheCollection cacheAtIndex: [menu indexOfItem: item]]; if (![cache isDefault]) { KLStatus err = [[cache principal] setDefault]; if (err == klNoErr) { [cacheCollection update]; } else if (err != klUserCanceledErr) { [ErrorAlert alertForError: err action: KerberosChangeActiveUserAction modalForWindow: [self window]]; } } } } } // --------------------------------------------------------------------------- - (IBAction) showTicketInfo: (id) sender { Credential *credential = [self selectedCredential]; if (credential != NULL) { cascadePoint = [credential showInfoWindowCascadingFromPoint: cascadePoint]; } } #pragma mark --- Data Source Methods -- // --------------------------------------------------------------------------- - (int) numberOfRowsInTableView: (NSTableView *) tableView { return [cacheCollection numberOfCaches]; } // --------------------------------------------------------------------------- - (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex { if (tableColumn == cacheCollectionTableColumn) { return [cacheCollection stringValueForTicketColumnAtIndex: rowIndex]; } else if (tableColumn == cacheCollectionTimeRemainingTableColumn) { return [cacheCollection stringValueForLifetimeColumnAtIndex: rowIndex]; } else { return @""; } } // --------------------------------------------------------------------------- - (id) outlineView: (NSOutlineView *) outlineView child: (int) rowIndex ofItem: (id) item { Cache *cache = [self selectedCache]; if (cache != NULL) { if (item == NULL) { return [cache credentialsVersionAtIndex: rowIndex]; } else { return [item childAtIndex: rowIndex]; } } return NULL; } // --------------------------------------------------------------------------- - (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item { if ([self haveSelectedCache]) { if (item == NULL) { return YES; } else { return ([item numberOfChildren] > 0); } } return NO; } // --------------------------------------------------------------------------- - (int) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item { Cache *cache = [self selectedCache]; if (cache != NULL) { if (item == NULL) { return [cache numberOfCredentialsVersions]; } else { return [item numberOfChildren]; } } return 0; } // --------------------------------------------------------------------------- - (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item { if ([self haveSelectedCache]) { if (item != NULL) { if (tableColumn == ticketsTableColumn) { return [item stringValueForTicketColumn]; } else if (tableColumn == ticketsTimeRemainingTableColumn) { if ([outlineView isItemExpanded: item]) { return @""; } else { return [item stringValueForLifetimeColumn]; } } } } return @""; } #pragma mark --- Delegate Methods -- // --------------------------------------------------------------------------- - (BOOL) outlineView: (NSOutlineView *) outlineView shouldEditTableColumn: (NSTableColumn *) tableColumn item: (id) item { return NO; } // --------------------------------------------------------------------------- - (BOOL) splitView: (NSSplitView *) sender canCollapseSubview: (NSView *) subview { return !([[sender subviews] objectAtIndex: 0] == subview); } // --------------------------------------------------------------------------- - (float) splitView: (NSSplitView *) sender constrainMinCoordinate: (float) proposedMin ofSubviewAt: (int) offset { return (proposedMin + ([[cacheCollectionTableView headerView] frame].size.height + ([cacheCollectionTableView rowHeight] * 2) + ([cacheCollectionTableView intercellSpacing].height * 3))); } // --------------------------------------------------------------------------- - (float) splitView: (NSSplitView *) sender constrainMaxCoordinate: (float) proposedMax ofSubviewAt: (int) offset { float m = (proposedMax - ([[ticketsOutlineView headerView] frame].size.height + ([ticketsOutlineView rowHeight] * 2) + ([ticketsOutlineView intercellSpacing].height * 3))); return m; } // --------------------------------------------------------------------------- - (NSToolbarItem *) toolbar: (NSToolbar *) aToolbar itemForItemIdentifier: (NSString *) itemIdentifier willBeInsertedIntoToolbar: (BOOL) flag { NSToolbarItem *toolbarItem = NULL; if ([itemIdentifier isEqual: ActiveUserMenuToolbarItemIdentifier]) { toolbarItem = [[[ActiveUserToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease]; [toolbarItem setLabel: NSLocalizedString (@"ActiveUserMenuToolbarItemLabel", NULL)]; [toolbarItem setPaletteLabel: NSLocalizedString (@"ActiveUserMenuToolbarItemLabel", NULL)]; [toolbarItem setToolTip: NSLocalizedString (@"ActiveUserMenuToolbarItemToolTip", NULL)]; [toolbarItem setTarget: self]; } else { toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease]; [toolbarItem setTarget: self]; if ([itemIdentifier isEqual: GetTicketsToolbarItemIdentifier]) { [toolbarItem setLabel: NSLocalizedString (@"GetTicketsToolbarItemLabel", NULL)]; [toolbarItem setPaletteLabel: NSLocalizedString (@"GetTicketsToolbarItemLabel", NULL)]; [toolbarItem setToolTip: NSLocalizedString (@"GetTicketsToolbarItemToolTip", NULL)]; [toolbarItem setImage: [NSImage imageNamed: @"GetTickets"]]; [toolbarItem setAction: @selector (getTickets:)]; } else if ([itemIdentifier isEqual: RenewTicketsToolbarItemIdentifier]) { [toolbarItem setLabel: NSLocalizedString (@"RenewTicketsToolbarItemLabel", NULL)]; [toolbarItem setPaletteLabel: NSLocalizedString (@"RenewTicketsToolbarItemLabel", NULL)]; [toolbarItem setToolTip: NSLocalizedString (@"RenewTicketsToolbarItemToolTip", NULL)]; [toolbarItem setImage: [NSImage imageNamed: @"RenewTickets"]]; [toolbarItem setAction: @selector (renewTicketsForSelectedCache:)]; } else if ([itemIdentifier isEqual: DestroyTicketsToolbarItemIdentifier]) { [toolbarItem setLabel: NSLocalizedString (@"DestroyTicketsToolbarItemLabel", NULL)]; [toolbarItem setPaletteLabel: NSLocalizedString (@"DestroyTicketsToolbarItemLabel", NULL)]; [toolbarItem setToolTip: NSLocalizedString (@"DestroyTicketsToolbarItemToolTip", NULL)]; [toolbarItem setImage: [NSImage imageNamed: @"DestroyTickets"]]; [toolbarItem setAction: @selector (destroyTicketsForSelectedCache:)]; } else if ([itemIdentifier isEqual: ChangePasswordToolbarItemIdentifier]) { [toolbarItem setLabel: NSLocalizedString (@"ChangePasswordToolbarItemLabel", NULL)]; [toolbarItem setPaletteLabel: NSLocalizedString (@"ChangePasswordToolbarItemLabel", NULL)]; [toolbarItem setToolTip: NSLocalizedString (@"ChangePasswordToolbarItemToolTip", NULL)]; [toolbarItem setImage: [NSImage imageNamed: @"ChangePassword"]]; [toolbarItem setAction: @selector (changePasswordForSelectedCache:)]; } else if ([itemIdentifier isEqual: TicketInfoToolbarItemIdentifier]) { [toolbarItem setLabel: NSLocalizedString (@"TicketInfoToolbarItemLabel", NULL)]; [toolbarItem setPaletteLabel: NSLocalizedString (@"TicketInfoToolbarItemLabel", NULL)]; [toolbarItem setToolTip: NSLocalizedString (@"TicketInfoToolbarItemToolTip", NULL)]; [toolbarItem setImage: [NSImage imageNamed: @"TicketInfo"]]; [toolbarItem setAction: @selector (showTicketInfo:)]; } else { // Unsupported item type toolbarItem = NULL; } } return toolbarItem; } // --------------------------------------------------------------------------- - (BOOL) validateToolbarItem: (NSToolbarItem *) theItem { NSString *itemIdentifier = [theItem itemIdentifier]; if ([itemIdentifier isEqual: ActiveUserMenuToolbarItemIdentifier]) { return YES; } else if ([itemIdentifier isEqual: GetTicketsToolbarItemIdentifier]) { return YES; } else if ([itemIdentifier isEqual: RenewTicketsToolbarItemIdentifier]) { return [self haveSelectedCache]; } else if ([itemIdentifier isEqual: DestroyTicketsToolbarItemIdentifier]) { return [self haveSelectedCache]; } else if ([itemIdentifier isEqual: ChangePasswordToolbarItemIdentifier]) { return [self haveSelectedCache]; } else if ([itemIdentifier isEqual: TicketInfoToolbarItemIdentifier]) { return [self haveSelectedCredential]; } // Unsupported item type or error return NO; } // --------------------------------------------------------------------------- - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) aToolbar { return [NSArray arrayWithObjects: ActiveUserMenuToolbarItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, GetTicketsToolbarItemIdentifier, NSToolbarSeparatorItemIdentifier, RenewTicketsToolbarItemIdentifier, DestroyTicketsToolbarItemIdentifier, ChangePasswordToolbarItemIdentifier, TicketInfoToolbarItemIdentifier, NULL]; } // --------------------------------------------------------------------------- - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) aToolbar { return [NSArray arrayWithObjects: ActiveUserMenuToolbarItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, GetTicketsToolbarItemIdentifier, NSToolbarSeparatorItemIdentifier, RenewTicketsToolbarItemIdentifier, DestroyTicketsToolbarItemIdentifier, ChangePasswordToolbarItemIdentifier, NSToolbarSeparatorItemIdentifier, TicketInfoToolbarItemIdentifier, NULL]; } #pragma mark -- Callbacks -- // --------------------------------------------------------------------------- - (void) minuteTimer: (TargetOwnedTimer *) timer { // Update things with time values in them: [cacheCollectionTableView reloadData]; [ticketsOutlineView reloadData]; [self synchronizeWindowTitle]; } #pragma mark --- Notifications -- // --------------------------------------------------------------------------- - (void) cacheCollectionDidChange: (NSNotification *) notification { dprintf ("Ticket List window %lx got CacheCollectionDidChangeNotification", (long) self); [cacheCollectionTableView reloadData]; [ticketsOutlineView reloadData]; // Attempt to select the cache that was previously selected // Make sure we don't select anything while we are still looking at // cacheNameString because selecting entries invalidates it unsigned int newSelectedIndex = NSNotFound; if (cacheNameString != NULL) { Cache *cache = [cacheCollection findCacheForName: cacheNameString]; if (cache != NULL) { newSelectedIndex = [cacheCollection indexOfCache: cache]; } } if (newSelectedIndex != NSNotFound) { [cacheCollectionTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: newSelectedIndex] byExtendingSelection: NO]; } else if ([cacheCollection numberOfCaches] > 0) { [cacheCollectionTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0] byExtendingSelection: NO]; // select first one } [self tableViewSelectionDidChange: NULL]; [self outlineViewSelectionDidChange: NULL]; // Now that the ticket list is up to date, let other objects know about it [[NSNotificationCenter defaultCenter] postNotificationName: TicketListDidChangeNotification object: self]; dprintf ("Ticket List window %lx done with CacheCollectionDidChangeNotification", (long) self); } // --------------------------------------------------------------------------- - (void) preferencesDidChange: (NSNotification *) notification { if ([[self window] isVisible]) { [[self window] saveFrameUsingName: TicketListFrameAutosaveName]; } if ([[Preferences sharedPreferences] ticketWindowDefaultPosition]) { [self setWindowFrameAutosaveName: @""]; // Don't save frame position } else { [self setWindowFrameAutosaveName: TicketListFrameAutosaveName]; } } // --------------------------------------------------------------------------- - (void) tableViewSelectionDidChange: (NSNotification *) notification { [ticketsOutlineView reloadData]; Cache *cache = [self selectedCache]; if (cache != NULL) { int i; // Used when cacheCollection changes out from under us // so the user's selection follows the cache if (cacheNameString != NULL) { [cacheNameString release]; } cacheNameString = [[cache ccacheName] retain]; for (i = 0; i < [cache numberOfCredentialsVersions]; i++) { [ticketsOutlineView expandItem: [cache credentialsVersionAtIndex: i]]; } } else { if (cacheNameString != NULL) { [cacheNameString release]; } cacheNameString = NULL; } [self synchronizeToolbar]; [self synchronizeWindowTitle]; [[NSNotificationCenter defaultCenter] postNotificationName: CacheSelectionDidChangeNotification object: self]; } // --------------------------------------------------------------------------- - (void) outlineViewSelectionDidChange: (NSNotification *) notification { [self synchronizeToolbar]; [self synchronizeWindowTitle]; [[NSNotificationCenter defaultCenter] postNotificationName: TicketSelectionDidChangeNotification object: self]; } // --------------------------------------------------------------------------- - (void) windowDidMove: (NSNotification *) notification { [self synchronizeCascadePoint]; } #pragma mark -- Utility Functions -- // --------------------------------------------------------------------------- - (void) synchronizeWindowTitle { Cache *defaultCache = [cacheCollection defaultCache]; if (defaultCache != NULL) { [[self window] setTitle: [defaultCache stringValueForWindowTitle]]; } else { [[self window] setTitle: NSLocalizedString (@"KAppStringNoTicketsAvailable", NULL)]; } } // --------------------------------------------------------------------------- - (void) synchronizeCascadePoint { NSRect frameRect = [[self window] frame]; NSPoint frameUpperLeft = frameRect.origin; frameUpperLeft.y += frameRect.size.height; NSRect contentRect = [[[self window] contentView] frame]; NSPoint contentUpperLeft = [[self window] convertBaseToScreen: contentRect.origin]; contentUpperLeft.y += contentRect.size.height; float titleBarHeight = frameUpperLeft.y - contentUpperLeft.y; cascadePoint.x = frameUpperLeft.x + titleBarHeight; cascadePoint.y = frameUpperLeft.y - titleBarHeight; //NSLog (@"framePoint is (%f, %f)\n", frameUpperLeft.x, frameUpperLeft.y); //NSLog (@"cascadePoint is (%f, %f)\n", cascadePoint.x, cascadePoint.y); } // --------------------------------------------------------------------------- - (void) synchronizeToolbar { // Update the toolbar items [toolbar validateVisibleItems]; } // --------------------------------------------------------------------------- - (BOOL) haveSelectedCache { return ([cacheCollectionTableView selectedRow] >= 0); } // --------------------------------------------------------------------------- - (Cache *) selectedCache { if ([self haveSelectedCache]) { return [cacheCollection cacheAtIndex: [cacheCollectionTableView selectedRow]]; } else { return NULL; } } // --------------------------------------------------------------------------- - (BOOL) selectedCacheNeedsValidation { Cache *selectedCache = [self selectedCache]; return ((selectedCache != NULL) && [selectedCache needsValidation]); } // --------------------------------------------------------------------------- - (BOOL) haveSelectedCredential { int selectedTicketRow = [ticketsOutlineView selectedRow]; if ((selectedTicketRow >= 0) && ([[ticketsOutlineView itemAtRow: selectedTicketRow] isMemberOfClass: [Credential class]])) { return YES; } return NO; } // --------------------------------------------------------------------------- - (Credential *) selectedCredential { if ([self haveSelectedCredential]) { return [ticketsOutlineView itemAtRow: [ticketsOutlineView selectedRow]]; } else { return NULL; } } @end