Source/RAFBlockEditorWindowController.m
author Zachary West <zacw@adium.im>
Thu Nov 26 19:47:14 2009 -0500 (2009-11-26)
changeset 2831 fa096eef7c0f
parent 1109 092c44a2cfbb
permissions -rw-r--r--
When loading the Privacy window, resize when it opens appropriately. Fixes #13307.
     1 //
     2 //  RAFBlockEditorWindow.m
     3 //  Adium
     4 //
     5 //  Created by Augie Fackler on 5/26/05.
     6 //  Copyright 2006 The Adium Team. All rights reserved.
     7 //
     8 
     9 #import "RAFBlockEditorWindowController.h"
    10 #import <Adium/AIAccountControllerProtocol.h>
    11 #import <Adium/AIContactControllerProtocol.h>
    12 #import <AIUtilities/AICompletingTextField.h>
    13 #import <AIUtilities/AIPopUpButtonAdditions.h>
    14 #import <AIUtilities/AIMenuAdditions.h>
    15 #import <Adium/AIAccount.h>
    16 #import <Adium/AIListContact.h>
    17 #import <Adium/AIMetaContact.h>
    18 #import <Adium/AIListGroup.h>
    19 #import <Adium/AIService.h>
    20 
    21 @interface RAFBlockEditorWindowController ()
    22 - (NSMenu *)privacyOptionsMenu;
    23 - (AIAccount<AIAccount_Privacy> *)selectedAccount;
    24 - (void)configureTextField;
    25 - (NSSet *)contactsFromTextField;
    26 - (AIPrivacyOption)selectedPrivacyOption;
    27 @end
    28 
    29 @implementation RAFBlockEditorWindowController
    30 
    31 static RAFBlockEditorWindowController *sharedInstance = nil;
    32 
    33 + (void)showWindow
    34 {	
    35 	if (!sharedInstance) {
    36 		sharedInstance = [[self alloc] initWithWindowNibName:@"BlockEditorWindow"];
    37 	}
    38 
    39 	[sharedInstance showWindow:nil];
    40 	[[sharedInstance window] makeKeyAndOrderFront:nil];
    41 }
    42 
    43 - (void)windowDidLoad
    44 {
    45 	[[self window] setTitle:AILocalizedString(@"Privacy Settings", nil)];
    46 	[cancelButton setLocalizedString:AILocalizedString(@"Cancel","Cancel button for Privacy Settings")];
    47 	[blockButton setLocalizedString:AILocalizedString(@"Add","Add button for Privacy Settings")];
    48 	[[buddyCol headerCell] setTitle:AILocalizedString(@"Contact","Title of column containing user IDs of blocked contacts")];
    49 	[[accountCol headerCell] setTitle:AILocalizedString(@"Account","Title of column containing blocking accounts")];
    50 	[accountText setLocalizedString:AILocalizedString(@"Account:",nil)];
    51 
    52 	{
    53 		//Let the min X margin be resizeable while label_account and label_privacyLevel localize in case the window moves
    54 		[stateChooser setAutoresizingMask:(NSViewMinYMargin | NSViewMinXMargin)];
    55 		[popUp_accounts setAutoresizingMask:(NSViewMinYMargin | NSViewMinXMargin)];
    56 
    57 		//Keep label_privacyLevel in place, too, while label_account potentially resizes the window
    58 		[label_privacyLevel setAutoresizingMask:(NSViewMinYMargin | NSViewMinXMargin)];
    59 		[label_account setLocalizedString:AILocalizedString(@"Account:",nil)];
    60 		[label_privacyLevel setAutoresizingMask:(NSViewMinYMargin | NSViewMaxXMargin)];
    61 		//Account is in place; popUp_accounts can width-resize again
    62 		[popUp_accounts setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
    63 
    64 		[label_privacyLevel setLocalizedString:AILocalizedString(@"Privacy level:", nil)];		
    65 		[stateChooser setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
    66 	}
    67 
    68 	accountColumnsVisible = YES;
    69 	[accountCol retain];
    70 
    71 	listContents = [[NSMutableArray alloc] init];
    72 
    73 	[stateChooser setMenu:[self privacyOptionsMenu]];
    74 
    75 	[[table tableColumnWithIdentifier:@"icon"] setDataCell:[[[NSImageCell alloc] init] autorelease]];
    76 	
    77 	accountMenu = [[AIAccountMenu accountMenuWithDelegate:self
    78 											  submenuType:AIAccountNoSubmenu
    79 										   showTitleVerbs:NO] retain];
    80 	[table registerForDraggedTypes:[NSArray arrayWithObjects:@"AIListObject", @"AIListObjectUniqueIDs",nil]];
    81 
    82 	[[NSNotificationCenter defaultCenter] addObserver:self
    83 								   selector:@selector(privacySettingsChangedExternally:)
    84 									   name:@"AIPrivacySettingsChangedOutsideOfPrivacyWindow"
    85 									 object:nil];
    86 
    87 	// Force an update, so the window will resize properly.
    88 	[self accountMenu:accountMenu didSelectAccount:[self selectedAccount]];	
    89 	
    90 	[[AIContactObserverManager sharedManager] registerListObjectObserver:self];
    91 
    92 	[super windowDidLoad];
    93 }
    94 
    95 - (void)windowWillClose:(id)sender
    96 {
    97 	[super windowWillClose:sender];
    98 
    99 	[[AIContactObserverManager sharedManager] unregisterListObjectObserver:self];
   100 
   101 	[[NSNotificationCenter defaultCenter] removeObserver:self];
   102 	[sharedInstance release]; sharedInstance = nil;
   103 }
   104 
   105 - (NSString *)adiumFrameAutosaveName
   106 {
   107 	return @"PrivacyWindow";
   108 }
   109 
   110 - (void)dealloc
   111 {
   112 	[accountCol release];
   113 	[accountMenu release];
   114 	[listContents release];
   115 	[listContentsAllAccounts release];
   116 	
   117 	[super dealloc];
   118 }
   119 
   120 - (NSMutableArray*)listContents
   121 {
   122 	return listContents;
   123 }
   124 
   125 - (void)setListContents:(NSArray*)newList
   126 {
   127 	if (newList != listContents) {
   128 		[listContents release];
   129 		listContents = [newList mutableCopy];
   130 	}
   131 }
   132 
   133 #pragma mark Adding a contact to the list
   134 
   135 - (void)selectAccountInSheet:(AIAccount *)inAccount
   136 {
   137 	[popUp_sheetAccounts selectItemWithRepresentedObject:inAccount];
   138 	[self configureTextField];
   139 	
   140 	NSString	*userNameLabel = [inAccount.service userNameLabel];
   141 	
   142 	[accountText setAutoresizingMask:NSViewMinXMargin];
   143 	[buddyText setLocalizedString:[(userNameLabel ?
   144 									userNameLabel : AILocalizedString(@"Contact ID",nil)) stringByAppendingString:AILocalizedString(@":", "Colon which will be appended after a label such as 'User Name', before an input field")]];
   145 	[accountText setAutoresizingMask:NSViewMaxXMargin];
   146 }
   147 
   148 - (IBAction)runBlockSheet:(id)sender
   149 {
   150 	[field setStringValue:@""];
   151 	
   152 	sheetAccountMenu = [[AIAccountMenu accountMenuWithDelegate:self
   153 												   submenuType:AIAccountNoSubmenu
   154 												showTitleVerbs:NO] retain];
   155 	[self selectAccountInSheet:[[popUp_sheetAccounts selectedItem] representedObject]];
   156 	
   157 	[NSApp beginSheet:sheet 
   158 	   modalForWindow:[self window]
   159 		modalDelegate:self 
   160 	   didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
   161 		  contextInfo:nil];
   162 }
   163 
   164 
   165 - (IBAction)cancelBlockSheet:(id)sender
   166 {
   167     [NSApp endSheet:sheet];
   168 }
   169 
   170 - (void)addObject:(AIListContact *)inContact
   171 {
   172 	if (inContact) {
   173 		if (![listContents containsObject:inContact]) {
   174 			[listContents addObject:inContact];
   175 		}
   176 		
   177 		[inContact setIsOnPrivacyList:YES updateList:YES privacyType:(([self selectedPrivacyOption] == AIPrivacyOptionAllowUsers) ?
   178 																	  AIPrivacyTypePermit :
   179 																	  AIPrivacyTypeDeny)];	
   180 	}
   181 }
   182 
   183 - (IBAction)didBlockSheet:(id)sender
   184 {
   185 	NSSet *contactArray = [self contactsFromTextField];
   186 
   187 	//Add the contact immediately
   188 	if (contactArray && [contactArray count]) {
   189 		AIListContact *contact;
   190 		
   191 		for (contact in contactArray) {
   192 			[self addObject:contact];
   193 		}
   194 		
   195 		[table reloadData];
   196 	}
   197 
   198     [NSApp endSheet:sheet];
   199 }
   200 
   201 
   202 - (void)didEndSheet:(NSWindow *)theSheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
   203 {
   204 	[sheetAccountMenu release]; sheetAccountMenu = nil;
   205     [theSheet orderOut:self];
   206 }
   207 
   208 /*!
   209  * @brief Get a set of all contacts which are represented by the currently selected account and UID field
   210  *
   211  * @result A set of AIListContact objects
   212  */
   213 - (NSSet *)contactsFromTextField
   214 {
   215 	AIListContact	*contact = nil;
   216 	NSString		*UID = nil;
   217 	AIAccount		*account = [[popUp_sheetAccounts selectedItem] representedObject];;
   218 	NSArray			*accountArray;
   219 	NSMutableSet	*contactsSet = [NSMutableSet set];
   220 	NSEnumerator	*enumerator;
   221 	id				impliedValue = [field impliedValue];
   222 
   223 	if (account) {
   224 		accountArray = [NSArray arrayWithObject:account];
   225 	} else {
   226 		//All accounts
   227 		NSMutableArray	*tempArray = [NSMutableArray array];
   228 		NSMenuItem		*menuItem;
   229 		
   230 		enumerator = [[[popUp_sheetAccounts menu] itemArray] objectEnumerator];
   231 		while ((menuItem = [enumerator nextObject])) {
   232 			AIAccount *anAccount;
   233 			
   234 			if ((anAccount = [menuItem representedObject])) {
   235 				[tempArray addObject:anAccount];
   236 			}
   237 		}
   238 		
   239 		accountArray = tempArray;
   240 	}
   241 
   242 	for (account in accountArray) {
   243 		if ([impliedValue isKindOfClass:[AIMetaContact class]]) {
   244 			AIListContact *containedContact;
   245 			NSEnumerator *contactEnumerator = [[(AIMetaContact *)impliedValue listContactsIncludingOfflineAccounts] objectEnumerator];
   246 			
   247 			while ((containedContact = [contactEnumerator nextObject])) {
   248 				/* For each contact contained my the metacontact, check if its service class matches the current account's.
   249 				 * If it does, add that contact to our list, using the contactController to get an AIListContact specific for the account.
   250 				 */
   251 				if ([containedContact.service.serviceClass isEqualToString:account.service.serviceClass]) {
   252 					if ((contact = [adium.contactController contactWithService:account.service
   253 																		 account:account
   254 																			 UID:containedContact.UID])) {
   255 						[contactsSet addObject:contact];
   256 					}
   257 				}
   258 			}
   259 			
   260 		} else {
   261 			if ([impliedValue isKindOfClass:[AIListContact class]]) {
   262 				UID = [(AIListContact *)impliedValue UID];
   263 			
   264 			} else  if ([impliedValue isKindOfClass:[NSString class]]) {
   265 				UID = [account.service normalizeUID:impliedValue removeIgnoredCharacters:YES];
   266 			}
   267 			
   268 			if (UID) {
   269 				//Get a contact with this UID on the current account
   270 				if ((contact = [adium.contactController contactWithService:account.service
   271 																	 account:account 
   272 																		 UID:UID])) {
   273 					[contactsSet addObject:contact];
   274 				}
   275 			}
   276 		}
   277 			
   278 	}
   279 	
   280 	return contactsSet;
   281 }
   282 
   283 - (void)configureTextField
   284 {
   285 	AIAccount *account = [[popUp_sheetAccounts selectedItem] representedObject];
   286 	NSEnumerator		*enumerator;
   287     AIListContact		*contact;
   288 	
   289 	//Clear the completing strings
   290 	[field setCompletingStrings:nil];
   291 	
   292 	//Configure the auto-complete view to autocomplete for contacts matching the selected account's service
   293     enumerator = [adium.contactController.allContacts objectEnumerator];
   294     while ((contact = [enumerator nextObject])) {
   295 		if (!account ||
   296 			contact.service == account.service) {
   297 			NSString *UID = contact.UID;
   298 			[field addCompletionString:contact.formattedUID withImpliedCompletion:UID];
   299 			[field addCompletionString:contact.displayName withImpliedCompletion:UID];
   300 			[field addCompletionString:UID];
   301 		}
   302     }
   303 }
   304 
   305 #pragma mark Removing a contact from the  list
   306 
   307 - (IBAction)removeSelection:(id)sender
   308 {
   309 	NSIndexSet		*selectedItems = [table selectedRowIndexes];
   310 	
   311 	// If there's anything selected..
   312 	if ([selectedItems count]) {
   313 		AIListContact	*contact;
   314 		
   315 		// Iterate through the selected rows (backwards)
   316 		for (NSInteger selection = [selectedItems lastIndex]; selection != NSNotFound; selection = [selectedItems indexLessThanIndex:selection]) {
   317 			contact = [listContents objectAtIndex:selection];
   318 			// Remove from the serverside list
   319 			[contact setIsOnPrivacyList:NO updateList:YES privacyType:(([self selectedPrivacyOption] == AIPrivacyOptionAllowUsers) ?
   320 																	   AIPrivacyTypePermit :
   321 																	   AIPrivacyTypeDeny)];
   322 			[listContents removeObject:contact];
   323 		}
   324 		
   325 		[table reloadData];
   326 		[table deselectAll:nil];
   327 	}
   328 
   329 }
   330 
   331 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView
   332 {
   333 	[self removeSelection:tableView];
   334 }
   335 
   336 - (void)setAccountColumnsVisible:(BOOL)visible
   337 {
   338 	if (accountColumnsVisible != visible) {
   339 		if (visible) {
   340 			[table addTableColumn:accountCol];
   341 		} else {
   342 			[table removeTableColumn:accountCol];			
   343 		}
   344 
   345 		[table sizeToFit];
   346 		accountColumnsVisible = visible;
   347 	}
   348 }
   349 #pragma mark Privacy options menu
   350 
   351 - (NSMenu *)privacyOptionsMenu
   352 {
   353 	//build the menu of states
   354 	NSMenu *stateMenu = [[NSMenu alloc] init];
   355 
   356 	NSMenuItem *menuItem;
   357 	
   358 	menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Allow anyone", nil) 
   359 										  action:NULL
   360 								   keyEquivalent:@""];
   361 	[menuItem setTag:AIPrivacyOptionAllowAll];
   362 	[stateMenu addItem:menuItem];
   363 	[menuItem release];
   364 
   365 	menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Allow only contacts on my contact list", nil) 
   366 										  action:NULL
   367 								   keyEquivalent:@""];
   368 	[menuItem setTag:AIPrivacyOptionAllowContactList];
   369 	[stateMenu addItem:menuItem];
   370 	[menuItem release];
   371 
   372 	menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Allow only certain contacts", nil) 
   373 										  action:NULL
   374 								   keyEquivalent:@""];
   375 	[menuItem setTag:AIPrivacyOptionAllowUsers];
   376 	[stateMenu addItem:menuItem];
   377 	[menuItem release];
   378 
   379 	menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Block certain contacts", nil) 
   380 										  action:NULL
   381 								   keyEquivalent:@""];
   382 	[menuItem setTag:AIPrivacyOptionDenyUsers];
   383 	[stateMenu addItem:menuItem];
   384 	[menuItem release];
   385 
   386 	/*
   387 	tmpItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Custom settings for each account", nil) action:NULL keyEquivalent:@""];
   388 	[tmpItem setRepresentedObject:[NSNumber numberWithInt:AIPrivacyOptionCustom]];
   389 	[stateMenu addItem:[tmpItem autorelease]];
   390 	*/
   391 
   392 	return [stateMenu autorelease];
   393 }
   394 
   395 - (AIPrivacyOption)selectedPrivacyOption
   396 {
   397 	return [[stateChooser selectedItem] tag];
   398 }
   399 
   400 /*!
   401  * @brief Set a privacy option and update our view for it
   402  *
   403  * @param sender If nil, we update our display without attempting to change anything on our account
   404  */
   405 - (IBAction)setPrivacyOption:(id)sender
   406 {
   407 	AIAccount<AIAccount_Privacy> *account = [self selectedAccount];
   408 	AIPrivacyOption privacyOption = [self selectedPrivacyOption];
   409 
   410 	//First, let's get the right tab view selected
   411 	switch (privacyOption) {
   412 		case AIPrivacyOptionAllowAll:
   413 		case AIPrivacyOptionAllowContactList:
   414 		case AIPrivacyOptionCustom:
   415 			if (![[[tabView_contactList selectedTabViewItem] identifier] isEqualToString:@"empty"]) {
   416 				[tabView_contactList selectTabViewItemWithIdentifier:@"empty"];
   417 				[tabView_contactList setHidden:YES];
   418 
   419 				NSRect frame = [[self window] frame];
   420 				CGFloat tabViewHeight = [tabView_contactList frame].size.height;
   421 				frame.size.height -= tabViewHeight;
   422 				frame.origin.y += tabViewHeight;
   423 				
   424 				//Don't resize vertically now...
   425 				[tabView_contactList setAutoresizingMask:NSViewWidthSizable];
   426 
   427 				[[self window] setMinSize:NSMakeSize(250, frame.size.height)];
   428 				[[self window] setMaxSize:NSMakeSize(CGFLOAT_MAX, frame.size.height)];
   429 				
   430 				AILog(@"Because of privacy option %i, resizing from %@ to %@",privacyOption,
   431 					  NSStringFromRect([[self window] frame]),NSStringFromRect(frame));
   432 				[[self window] setFrame:frame display:YES animate:YES];
   433 			}
   434 			break;
   435 			
   436 		case AIPrivacyOptionAllowUsers:
   437 		case AIPrivacyOptionDenyUsers:
   438 			if (![[[tabView_contactList selectedTabViewItem] identifier] isEqualToString:@"list"]) {
   439 				[tabView_contactList selectTabViewItemWithIdentifier:@"list"];
   440 
   441 				NSRect frame = [[self window] frame];
   442 				CGFloat tabViewHeight = [tabView_contactList frame].size.height;
   443 				frame.size.height += tabViewHeight;
   444 				frame.origin.y -= tabViewHeight;
   445 				
   446 				[[self window] setMinSize:NSMakeSize(250, 320)];
   447 				[[self window] setMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
   448 				
   449 				//Set frame after fixing our min/max size so the resize won't fail
   450 				AILog(@"Because of privacy option %i, resizing from %@ to %@",privacyOption,
   451 					  NSStringFromRect([[self window] frame]),NSStringFromRect(frame));
   452 				[[self window] setFrame:frame display:YES animate:YES];
   453 
   454 				[tabView_contactList setHidden:NO];
   455 
   456 				//Allow resizing vertically again
   457 				[tabView_contactList setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
   458 			}
   459 			break;
   460 		case AIPrivacyOptionDenyAll:
   461 		case AIPrivacyOptionUnknown:
   462 			NSLog(@"We should never see these...");
   463 			break;
   464 	}
   465 	
   466 	if (sender) {
   467 		if (account) {
   468 			[account setPrivacyOptions:privacyOption];
   469 			
   470 		} else {
   471 			NSEnumerator	*enumerator = [[[popUp_accounts menu] itemArray] objectEnumerator];
   472 			NSMenuItem						*menuItem;
   473 			AIAccount<AIAccount_Privacy>	*representedAccount;
   474 
   475 			while ((menuItem = [enumerator nextObject])) {
   476 				if ((representedAccount = [menuItem representedObject])) {
   477 					[representedAccount setPrivacyOptions:privacyOption];
   478 				}
   479 			}
   480 		}
   481 	}
   482 	
   483 	//Now make our listContents array match the serverside arrays for the selected account(s)
   484 	[listContents removeAllObjects];
   485 	if ((privacyOption == AIPrivacyOptionAllowUsers) ||
   486 		(privacyOption == AIPrivacyOptionDenyUsers)) {
   487 		if (account) {
   488 			[listContents addObjectsFromArray:[account listObjectsOnPrivacyList:((privacyOption == AIPrivacyOptionAllowUsers) ?
   489 																				 AIPrivacyTypePermit :
   490 																				 AIPrivacyTypeDeny)]];		
   491 		} else {
   492 			NSEnumerator					*enumerator = [[[popUp_accounts menu] itemArray] objectEnumerator];
   493 			NSMenuItem						*menuItem;
   494 			AIAccount<AIAccount_Privacy>	*representedAccount;
   495 
   496 			while ((menuItem = [enumerator nextObject])) {
   497 				if ((representedAccount = [menuItem representedObject])) {
   498 					[listContents addObjectsFromArray:[representedAccount listObjectsOnPrivacyList:((privacyOption == AIPrivacyOptionAllowUsers) ?
   499 																									AIPrivacyTypePermit :
   500 																									AIPrivacyTypeDeny)]];		
   501 				}
   502 			}
   503 		}
   504 	}
   505 
   506 	[table reloadData];
   507 }
   508 
   509 - (void)selectPrivacyOption:(AIPrivacyOption)privacyOption
   510 {
   511 	BOOL success = [stateChooser selectItemWithTag:privacyOption];
   512 	if (privacyOption == AIPrivacyOptionCustom) {
   513 		if (!success) {
   514 			NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"(Multiple privacy levels are active)", nil) 
   515 															  action:NULL
   516 													   keyEquivalent:@""];
   517 			[menuItem setTag:AIPrivacyOptionCustom];
   518 			[[stateChooser menu] addItem:menuItem];
   519 			[menuItem release];
   520 			
   521 			success = [stateChooser selectItemWithTag:privacyOption];
   522 		}
   523 
   524 	} else {
   525 		//Not on custom; make sure custom isn't still in the menu
   526 		NSInteger customItemIndex = [stateChooser indexOfItemWithTag:AIPrivacyOptionCustom];
   527 		if (customItemIndex != -1) {
   528 			[[stateChooser menu] removeItemAtIndex:customItemIndex];
   529 		}
   530 	}
   531 
   532 	//Now update our view for this privacy option
   533 	[self setPrivacyOption:nil];
   534 }
   535 
   536 #pragma mark Account menu
   537 /*!
   538  * @brief Return the currently selected account, or nil if the 'All' item is selected
   539  */
   540 - (AIAccount<AIAccount_Privacy> *)selectedAccount
   541 {
   542 	return [[popUp_accounts selectedItem] representedObject];
   543 }
   544 
   545 /*!
   546  * @brief Action called when the account selection changes
   547  *
   548  * Update our view and the privacy option menu to be appropriate for the newly selected account.
   549  * This may be called with a sender of nil by code elsewhere to force an update
   550  */
   551 - (void)accountMenu:(AIAccountMenu *)inAccountMenu didSelectAccount:(AIAccount *)inAccount
   552 {
   553 	if (inAccountMenu == accountMenu) {
   554 		AIAccount<AIAccount_Privacy> *account = [self selectedAccount];
   555 		if (account) {
   556 			//Selected an account
   557 			AIPrivacyOption privacyOption = [account privacyOptions];
   558 			
   559 			//Don't need the account column when we're showing for just one account
   560 			[self setAccountColumnsVisible:NO];
   561 
   562 			[self selectPrivacyOption:privacyOption];			
   563 
   564 		} else {
   565 			//Selected 'All'. We need to determine what privacy option to display for the set of all accounts.
   566 			AIPrivacyOption currentState = AIPrivacyOptionUnknown;
   567 			NSEnumerator	*enumerator = [[[popUp_accounts menu] itemArray] objectEnumerator];
   568 			NSMenuItem		*menuItem;
   569 			
   570 			while ((menuItem = [enumerator nextObject])) {
   571 				if ((account = [menuItem representedObject])) {
   572 					AIPrivacyOption accountState = [account privacyOptions];
   573 					
   574 					if (currentState == AIPrivacyOptionUnknown) {
   575 						//We don't know the state of an account yet
   576 						currentState = accountState;
   577 					} else if (accountState != currentState) {
   578 						currentState = AIPrivacyOptionCustom;
   579 					}				
   580 				}
   581 			}
   582 			
   583 			[self setAccountColumnsVisible:YES];
   584 
   585 			[self selectPrivacyOption:currentState];
   586 		}
   587 
   588 	} else if (inAccountMenu == sheetAccountMenu) {
   589 		//Update our sheet for the current account
   590 		[self selectAccountInSheet:inAccount];
   591 	}
   592 }
   593 
   594 /*!
   595  * @brief The 'All' menu item for accounts was selected
   596  *
   597  * We simulate an AIAccountMenu delegate call, since the All item was added by RAFBLockEditorWindowController.
   598  */
   599 - (IBAction)selectedAllAccountItem:(id)sender
   600 {
   601 	AIAccountMenu *relevantAccountMenu = (([sender menu] == [popUp_accounts menu]) ?
   602 										  accountMenu :
   603 										  sheetAccountMenu);
   604 
   605 	[self accountMenu:relevantAccountMenu didSelectAccount:nil];
   606 }
   607 
   608 /*!
   609  * @brief Select an account in our account menu, then update everything else to be appropriate for it
   610  */
   611 - (void)selectAccount:(AIAccount *)inAccount
   612 {
   613 	[popUp_accounts selectItemWithRepresentedObject:inAccount];
   614 	
   615 	[self accountMenu:accountMenu didSelectAccount:inAccount];
   616 }
   617 
   618 /*!
   619  * @brief Add account menu items to our location
   620  *
   621  * Implemented as required by the AccountMenuPlugin protocol.
   622  *
   623  * @param menuItemArray An <tt>NSArray</tt> of <tt>NSMenuItem</tt> objects to be added to the menu
   624  */
   625 - (void)accountMenu:(AIAccountMenu *)inAccountMenu didRebuildMenuItems:(NSArray *)menuItems
   626 {
   627 	AIAccount	 *previouslySelectedAccount = nil;
   628 	NSMenuItem	 *menuItem;
   629 	NSMenu		 *menu = [[NSMenu alloc] init];
   630 
   631 	/*
   632 	 * accountMenu isn't set the first time we get here as the accountMenu is created. Similarly, sheetAccountMenu isn't created its first time.
   633 	 * This code makes the (true) assumption that accountMenu is _always_ created before sheetAccountMenu.
   634 	 */	
   635 	BOOL isPrimaryAccountMenu = (!accountMenu || (inAccountMenu == accountMenu));
   636 
   637 	if (isPrimaryAccountMenu) {
   638 		if ([popUp_accounts menu]) {
   639 			previouslySelectedAccount = [[popUp_accounts selectedItem] representedObject];
   640 		}
   641 	} else if (inAccountMenu == sheetAccountMenu) {
   642 		if ([popUp_sheetAccounts menu]) {
   643 			previouslySelectedAccount = [[popUp_sheetAccounts selectedItem] representedObject];
   644 		}		
   645 	}
   646 
   647 	//Add the All menu item first if we have more than one account listed
   648 	if ([menuItems count] > 1) {
   649 		[menu addItemWithTitle:AILocalizedString(@"All", nll)
   650 						target:self
   651 						action:@selector(selectedAllAccountItem:)
   652 				 keyEquivalent:@""];
   653 	}
   654 
   655 	/*
   656 	 * As we enumerate, we:
   657 	 *	1) Determine what state the accounts within the menu are in
   658 	 *  2) Add the menu items to our menu
   659 	 */
   660 	for (menuItem in menuItems) {		
   661 		[menu addItem:menuItem];
   662 	}
   663 
   664 	if (isPrimaryAccountMenu) {
   665 		[popUp_accounts setMenu:menu];
   666 
   667 		/* Restore the previous account selection if there was one.
   668 		 * Whether there was one or not, this will cause the rest of our view update to match the new/current selection
   669 		 */
   670 		[self selectAccount:previouslySelectedAccount];
   671 
   672 	} else {
   673 		[popUp_sheetAccounts setMenu:menu];
   674 		
   675 		[self selectAccountInSheet:previouslySelectedAccount];
   676 	}
   677 
   678 	[menu release];
   679 }
   680 
   681 - (BOOL)accountMenu:(AIAccountMenu *)inAccountMenu shouldIncludeAccount:(AIAccount *)inAccount
   682 {
   683 	BOOL isPrimaryAccountMenu = (!accountMenu || (inAccountMenu == accountMenu));
   684 
   685 	if (isPrimaryAccountMenu) {
   686 		return (inAccount.online &&
   687 				[inAccount conformsToProtocol:@protocol(AIAccount_Privacy)]);
   688 	} else {
   689 		AIAccount *selectedPrimaryAccount = self.selectedAccount;
   690 		if (selectedPrimaryAccount) {
   691 			//An account is selected in the main window; only incldue that account in our sheet
   692 			return (inAccount == selectedPrimaryAccount);
   693 
   694 		} else {
   695 			//'All' is selected in the main window; include all accounts which are online and support privacy
   696 			return (inAccount.online &&
   697 					[inAccount conformsToProtocol:@protocol(AIAccount_Privacy)]);			
   698 		}
   699 	}
   700 }
   701 
   702 - (void)privacySettingsChangedExternally:(NSNotification *)inNotification
   703 {
   704 	[self accountMenu:accountMenu didSelectAccount:[self selectedAccount]];	
   705 }
   706 
   707 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
   708 {
   709 	if ([inModifiedKeys containsObject:KEY_IS_BLOCKED]) {
   710 		[self privacySettingsChangedExternally:nil];
   711 	}
   712 	
   713 	return nil;
   714 }
   715 
   716 #pragma mark Table view
   717 
   718 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
   719 {
   720 	return [listContents count];
   721 }
   722 
   723 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
   724 {
   725 	NSString		*identifier = [aTableColumn identifier];
   726 	AIListContact	*contact = [listContents objectAtIndex:rowIndex];
   727 
   728 	if ([identifier isEqualToString:@"icon"]) {
   729 		return [contact menuIcon];
   730 		
   731 	} else if ([identifier isEqualToString:@"contact"]) {
   732 		return contact.formattedUID;
   733 
   734 	} else if ([identifier isEqualToString:@"account"]) {
   735 		return contact.account.formattedUID;
   736 	}
   737 	
   738 	return nil;
   739 }
   740 
   741 - (BOOL)writeListObjects:(NSArray *)inArray toPasteboard:(NSPasteboard*)pboard
   742 {
   743 	[pboard declareTypes:[NSArray arrayWithObjects:@"AIListObject",@"AIListObjectUniqueIDs",nil] owner:self];
   744 	[pboard setString:@"Private" forType:@"AIListObject"];
   745 
   746 	if (dragItems != inArray) {
   747 		[dragItems release];
   748 		dragItems = [inArray retain];
   749 	}
   750 	
   751 	return YES;
   752 }
   753 
   754 - (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
   755 {	
   756 	NSMutableArray 	*itemArray = [NSMutableArray array];
   757 	NSNumber		*rowNumber;
   758 	for (rowNumber in rows) {
   759 		[itemArray addObject:[listContents objectAtIndex:[rowNumber integerValue]]];
   760 	}
   761 
   762 	return [self writeListObjects:itemArray toPasteboard:pboard];
   763 }
   764 
   765 - (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
   766 {
   767 	NSMutableArray 	*itemArray = [NSMutableArray array];
   768 	id 				item;
   769 	
   770 	NSUInteger bufSize = [rowIndexes count];
   771 	NSUInteger *buf = malloc(bufSize * sizeof(NSUInteger));
   772 	NSUInteger i;
   773 	
   774 	NSRange range = NSMakeRange([rowIndexes firstIndex], ([rowIndexes lastIndex]-[rowIndexes firstIndex]) + 1);
   775 	[rowIndexes getIndexes:buf maxCount:bufSize inIndexRange:&range];
   776 	
   777 	for (i = 0; i != bufSize; i++) {
   778 		if ((item = [listContents objectAtIndex:buf[i]])) {
   779 			[itemArray addObject:item];
   780 		}
   781 	}
   782 	
   783 	free(buf);
   784 	
   785 	return [self writeListObjects:itemArray toPasteboard:pboard];
   786 }
   787 
   788 - (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
   789 {
   790 	//Provide an array of internalObjectIDs which can be used to reference all the dragged contacts
   791 	if ([type isEqualToString:@"AIListObjectUniqueIDs"]) {
   792 		
   793 		if (dragItems) {
   794 			NSMutableArray	*dragItemsArray = [NSMutableArray array];
   795 			AIListObject	*listObject;
   796 			
   797 			for (listObject in dragItems) {
   798 				[dragItemsArray addObject:listObject.internalObjectID];
   799 			}
   800 			
   801 			[sender setPropertyList:dragItemsArray forType:@"AIListObjectUniqueIDs"];
   802 		}
   803 	}
   804 }
   805 
   806 - (NSDragOperation)tableView:(NSTableView*)tv
   807 				validateDrop:(id <NSDraggingInfo>)info
   808 				 proposedRow:(NSInteger)row
   809 	   proposedDropOperation:(NSTableViewDropOperation)op
   810 {
   811     
   812     NSDragOperation dragOp = NSDragOperationCopy;
   813 	
   814     if ([info draggingSource] == table) {
   815 		dragOp =  NSDragOperationMove;
   816     }
   817     [tv setDropRow:row dropOperation:NSTableViewDropAbove];
   818 	
   819     return dragOp;
   820 }
   821 
   822 - (void)addListObjectToList:(AIListObject *)listObject
   823 {
   824 	AIListObject *containedObject;
   825 	NSEnumerator *enumerator;
   826 
   827 	if ([listObject isKindOfClass:[AIListGroup class]]) {
   828 		enumerator = [[(AIListGroup *)listObject uniqueContainedObjects] objectEnumerator];
   829 		while ((containedObject = [enumerator nextObject])) {
   830 			[self addListObjectToList:containedObject];
   831 		}
   832 
   833 	} else if ([listObject isKindOfClass:[AIMetaContact class]]) {
   834 		enumerator = [[(AIMetaContact *)listObject uniqueContainedObjects] objectEnumerator];
   835 		while ((containedObject = [enumerator nextObject])) {
   836 			[self addListObjectToList:containedObject];
   837 		}
   838 
   839 	} else if ([listObject isKindOfClass:[AIListContact class]]) {
   840 		//if the account for this contact is connected...
   841 		if ([(AIListContact *)listObject account].online) {
   842 			[self addObject:(AIListContact *)listObject];
   843 		}
   844 	}
   845 }
   846 
   847 - (BOOL)tableView:(NSTableView*)tv acceptDrop:(id <NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)op
   848 {
   849 	BOOL accept = NO;
   850     if (row < 0)
   851 		row = 0;
   852 	
   853 	if ([info.draggingPasteboard.types containsObject:@"AIListObjectUniqueIDs"]) {
   854 		for (NSString *uniqueUID in [info.draggingPasteboard propertyListForType:@"AIListObjectUniqueIDs"])
   855 			[self addListObjectToList:[adium.contactController existingListObjectWithUniqueID:uniqueUID]];
   856 		accept = YES;
   857 	}
   858 	
   859     return accept;
   860 }
   861 
   862 @end