* Adium is the legal property of its developers, whose names are listed in the copyright file included * with this source distribution. * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #import "RAFBlockEditorWindowController.h" #import <Adium/AIAccountControllerProtocol.h> #import <Adium/AIContactControllerProtocol.h> #import <AIUtilities/AICompletingTextField.h> #import <AIUtilities/AIPopUpButtonAdditions.h> #import <AIUtilities/AIMenuAdditions.h> #import <Adium/AIAccount.h> #import <Adium/AIListContact.h> #import <Adium/AIMetaContact.h> #import <Adium/AIListGroup.h> #import <Adium/AIService.h> @interface RAFBlockEditorWindowController () - ( NSMenu * ) privacyOptionsMenu ; - ( AIAccount < AIAccount_Privacy > * ) selectedAccount ; - ( void ) configureTextField ; - ( NSSet * ) contactsFromTextField ; - ( AIPrivacyOption ) selectedPrivacyOption ; - ( void ) privacySettingsChangedExternally: ( NSNotification * ) inNotification ; @implementation RAFBlockEditorWindowController static RAFBlockEditorWindowController * sharedInstance = nil ; sharedInstance = [[ self alloc ] initWithWindowNibName : @"BlockEditorWindow" ]; [ sharedInstance showWindow : nil ]; [[ sharedInstance window ] makeKeyAndOrderFront : nil ]; [[ self window ] setTitle : AILocalizedString ( @"Privacy Settings" , nil )]; [ cancelButton setLocalizedString : AILocalizedString ( @"Cancel" , "Cancel button for Privacy Settings" )]; [ blockButton setLocalizedString : AILocalizedString ( @"Add" , "Add button for Privacy Settings" )]; [[ buddyCol headerCell ] setTitle : AILocalizedString ( @"Contact" , "Title of column containing user IDs of blocked contacts" )]; [[ accountCol headerCell ] setTitle : AILocalizedString ( @"Account" , "Title of column containing blocking accounts" )]; [ accountText setLocalizedString : AILocalizedString ( @"Account:" , nil )]; //Let the min X margin be resizeable while label_account and label_privacyLevel localize in case the window moves [ stateChooser setAutoresizingMask : ( NSViewMinYMargin | NSViewMinXMargin )]; [ popUp_accounts setAutoresizingMask : ( NSViewMinYMargin | NSViewMinXMargin )]; //Keep label_privacyLevel in place, too, while label_account potentially resizes the window [ label_privacyLevel setAutoresizingMask : ( NSViewMinYMargin | NSViewMinXMargin )]; [ label_account setLocalizedString : AILocalizedString ( @"Account:" , nil )]; [ label_privacyLevel setAutoresizingMask : ( NSViewMinYMargin | NSViewMaxXMargin )]; //Account is in place; popUp_accounts can width-resize again [ popUp_accounts setAutoresizingMask : ( NSViewWidthSizable | NSViewMinYMargin )]; [ label_privacyLevel setLocalizedString : AILocalizedString ( @"Privacy level:" , nil )]; [ stateChooser setAutoresizingMask : ( NSViewWidthSizable | NSViewMinYMargin )]; accountColumnsVisible = YES ; listContents = [[ NSMutableArray alloc ] init ]; [ stateChooser setMenu : [ self privacyOptionsMenu ]]; [[ table tableColumnWithIdentifier : @"icon" ] setDataCell : [[[ NSImageCell alloc ] init ] autorelease ]]; accountMenu = [[ AIAccountMenu accountMenuWithDelegate : self submenuType : AIAccountNoSubmenu showTitleVerbs : NO ] retain ]; [ table registerForDraggedTypes : [ NSArray arrayWithObjects : @"AIListObject" , @"AIListObjectUniqueIDs" , nil ]]; [[ NSNotificationCenter defaultCenter ] addObserver : self selector : @selector ( privacySettingsChangedExternally : ) name : @"AIPrivacySettingsChangedOutsideOfPrivacyWindow" // Force an update, so the window will resize properly. [ self accountMenu : accountMenu didSelectAccount : [ self selectedAccount ]]; [[ AIContactObserverManager sharedManager ] registerListObjectObserver : self ]; - ( void ) windowWillClose: ( id ) sender [ super windowWillClose : sender ]; [[ AIContactObserverManager sharedManager ] unregisterListObjectObserver : self ]; [[ NSNotificationCenter defaultCenter ] removeObserver : self ]; [ sharedInstance release ]; sharedInstance = nil ; - ( NSString * ) adiumFrameAutosaveName [ listContentsAllAccounts release ]; - ( NSMutableArray * ) listContents - ( void ) setListContents: ( NSArray * ) newList if ( newList != listContents ) { listContents = [ newList mutableCopy ]; - ( IBAction ) addOrRemoveBlock: ( id ) sender NSInteger selectedSegment = [ sender selectedSegment ]; switch ( selectedSegment ) { #pragma mark Adding a contact to the list - ( void ) selectAccountInSheet: ( AIAccount * ) inAccount [ popUp_sheetAccounts selectItemWithRepresentedObject : inAccount ]; [ self configureTextField ]; NSString * userNameLabel = [ inAccount . service userNameLabel ]; [ accountText setAutoresizingMask : NSViewMinXMargin ]; [ buddyText setLocalizedString : [( userNameLabel ? userNameLabel : AILocalizedString ( @"Contact ID" , nil )) stringByAppendingString : AILocalizedString ( @":" , "Colon which will be appended after a label such as 'User Name', before an input field" )]]; [ accountText setAutoresizingMask : NSViewMaxXMargin ]; [ field setStringValue : @"" ]; sheetAccountMenu = [[ AIAccountMenu accountMenuWithDelegate : self submenuType : AIAccountNoSubmenu showTitleVerbs : NO ] retain ]; [ self selectAccountInSheet : [[ popUp_sheetAccounts selectedItem ] representedObject ]]; modalForWindow :[ self window ] didEndSelector : @selector ( didEndSheet : returnCode : contextInfo : ) - ( IBAction ) cancelBlockSheet: ( id ) sender - ( void ) addObject: ( AIListContact * ) inContact if ( ! [ listContents containsObject : inContact ]) { [ listContents addObject : inContact ]; [ inContact setIsOnPrivacyList : YES updateList : YES privacyType : (([ self selectedPrivacyOption ] == AIPrivacyOptionAllowUsers ) ? - ( IBAction ) didBlockSheet: ( id ) sender NSSet * contactArray = [ self contactsFromTextField ]; //Add the contact immediately if ( contactArray && [ contactArray count ]) { for ( contact in contactArray ) { [ self addObject : contact ]; - ( void ) didEndSheet: ( NSWindow * ) theSheet returnCode: ( NSInteger ) returnCode contextInfo: ( void * ) contextInfo [ sheetAccountMenu release ]; sheetAccountMenu = nil ; [ theSheet orderOut : self ]; * @brief Get a set of all contacts which are represented by the currently selected account and UID field * @result A set of AIListContact objects - ( NSSet * ) contactsFromTextField AIListContact * contact = nil ; AIAccount * account = [[ popUp_sheetAccounts selectedItem ] representedObject ];; NSMutableSet * contactsSet = [ NSMutableSet set ]; NSEnumerator * enumerator ; id impliedValue = [ field impliedValue ]; accountArray = [ NSArray arrayWithObject : account ]; NSMutableArray * tempArray = [ NSMutableArray array ]; enumerator = [[[ popUp_sheetAccounts menu ] itemArray ] objectEnumerator ]; while (( menuItem = [ enumerator nextObject ])) { if (( anAccount = [ menuItem representedObject ])) { [ tempArray addObject : anAccount ]; accountArray = tempArray ; for ( account in accountArray ) { if ([ impliedValue isKindOfClass : [ AIMetaContact class ]]) { AIListContact * containedContact ; NSEnumerator * contactEnumerator = [[( AIMetaContact * ) impliedValue listContactsIncludingOfflineAccounts ] objectEnumerator ]; while (( containedContact = [ contactEnumerator nextObject ])) { /* For each contact contained my the metacontact, check if its service class matches the current account's. * If it does, add that contact to our list, using the contactController to get an AIListContact specific for the account. if ([ containedContact . service . serviceClass isEqualToString : account . service . serviceClass ]) { if (( contact = [ adium . contactController contactWithService : account . service UID : containedContact . UID ])) { [ contactsSet addObject : contact ]; if ([ impliedValue isKindOfClass : [ AIListContact class ]]) { UID = [( AIListContact * ) impliedValue UID ]; } else if ([ impliedValue isKindOfClass : [ NSString class ]]) { UID = [ account . service normalizeUID : impliedValue removeIgnoredCharacters : YES ]; //Get a contact with this UID on the current account if (( contact = [ adium . contactController contactWithService : account . service [ contactsSet addObject : contact ]; - ( void ) configureTextField AIAccount * account = [[ popUp_sheetAccounts selectedItem ] representedObject ]; NSEnumerator * enumerator ; //Clear the completing strings [ field setCompletingStrings : nil ]; //Configure the auto-complete view to autocomplete for contacts matching the selected account's service enumerator = [ adium . contactController . allContacts objectEnumerator ]; while (( contact = [ enumerator nextObject ])) { contact . service == account . service ) { NSString * UID = contact . UID ; [ field addCompletionString : contact . formattedUID withImpliedCompletion : UID ]; [ field addCompletionString : contact . displayName withImpliedCompletion : UID ]; [ field addCompletionString : UID ]; #pragma mark Removing a contact from the list NSIndexSet * selectedItems = [ table selectedRowIndexes ]; // If there's anything selected.. if ([ selectedItems count ]) { // Iterate through the selected rows (backwards) for ( NSInteger selection = [ selectedItems lastIndex ]; selection != NSNotFound ; selection = [ selectedItems indexLessThanIndex : selection ]) { contact = [ listContents objectAtIndex : selection ]; // Remove from the serverside list [ contact setIsOnPrivacyList : NO updateList : YES privacyType : (([ self selectedPrivacyOption ] == AIPrivacyOptionAllowUsers ) ? [ listContents removeObject : contact ]; - ( void ) tableViewDeleteSelectedRows: ( NSTableView * ) tableView - ( void ) setAccountColumnsVisible: ( BOOL ) visible if ( accountColumnsVisible != visible ) { [ table addTableColumn : accountCol ]; [ table removeTableColumn : accountCol ]; accountColumnsVisible = visible ; #pragma mark Privacy options menu - ( NSMenu * ) privacyOptionsMenu //build the menu of states NSMenu * stateMenu = [[ NSMenu alloc ] init ]; menuItem = [[ NSMenuItem alloc ] initWithTitle : AILocalizedString ( @"Allow anyone" , nil ) [ menuItem setTag : AIPrivacyOptionAllowAll ]; [ stateMenu addItem : menuItem ]; menuItem = [[ NSMenuItem alloc ] initWithTitle : AILocalizedString ( @"Allow only contacts on my contact list" , nil ) [ menuItem setTag : AIPrivacyOptionAllowContactList ]; [ stateMenu addItem : menuItem ]; menuItem = [[ NSMenuItem alloc ] initWithTitle : AILocalizedString ( @"Allow only certain contacts" , nil ) [ menuItem setTag : AIPrivacyOptionAllowUsers ]; [ stateMenu addItem : menuItem ]; menuItem = [[ NSMenuItem alloc ] initWithTitle : AILocalizedString ( @"Block certain contacts" , nil ) [ menuItem setTag : AIPrivacyOptionDenyUsers ]; [ stateMenu addItem : menuItem ]; tmpItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Custom settings for each account", nil) action:NULL keyEquivalent:@""]; [tmpItem setRepresentedObject:[NSNumber numberWithInt:AIPrivacyOptionCustom]]; [stateMenu addItem:[tmpItem autorelease]]; return [ stateMenu autorelease ]; - ( AIPrivacyOption ) selectedPrivacyOption return ( AIPrivacyOption )[[ stateChooser selectedItem ] tag ]; * @brief Set a privacy option and update our view for it * @param sender If nil, we update our display without attempting to change anything on our account - ( IBAction ) setPrivacyOption: ( id ) sender AIAccount < AIAccount_Privacy > * account = [ self selectedAccount ]; AIPrivacyOption privacyOption = [ self selectedPrivacyOption ]; //First, let's get the right tab view selected case AIPrivacyOptionAllowAll : case AIPrivacyOptionAllowContactList : case AIPrivacyOptionCustom : if ( ! [[[ tabView_contactList selectedTabViewItem ] identifier ] isEqualToString : @"empty" ]) { [ tabView_contactList selectTabViewItemWithIdentifier : @"empty" ]; [ tabView_contactList setHidden : YES ]; NSRect frame = [[ self window ] frame ]; CGFloat tabViewHeight = [ tabView_contactList frame ]. size . height ; frame . size . height -= tabViewHeight ; frame . origin . y += tabViewHeight ; //Don't resize vertically now... [ tabView_contactList setAutoresizingMask : NSViewWidthSizable ]; [[ self window ] setMinSize : NSMakeSize ( 250 , frame . size . height )]; [[ self window ] setMaxSize : NSMakeSize ( CGFLOAT_MAX , frame . size . height )]; AILog ( @"Because of privacy option %i, resizing from %@ to %@" , privacyOption , NSStringFromRect ([[ self window ] frame ]), NSStringFromRect ( frame )); [[ self window ] setFrame : frame display : YES animate : YES ]; case AIPrivacyOptionAllowUsers : case AIPrivacyOptionDenyUsers : if ( ! [[[ tabView_contactList selectedTabViewItem ] identifier ] isEqualToString : @"list" ]) { [ tabView_contactList selectTabViewItemWithIdentifier : @"list" ]; NSRect frame = [[ self window ] frame ]; CGFloat tabViewHeight = [ tabView_contactList frame ]. size . height ; frame . size . height += tabViewHeight ; frame . origin . y -= tabViewHeight ; [[ self window ] setMinSize : NSMakeSize ( 250 , 320 )]; [[ self window ] setMaxSize : NSMakeSize ( CGFLOAT_MAX , CGFLOAT_MAX )]; //Set frame after fixing our min/max size so the resize won't fail AILog ( @"Because of privacy option %i, resizing from %@ to %@" , privacyOption , NSStringFromRect ([[ self window ] frame ]), NSStringFromRect ( frame )); [[ self window ] setFrame : frame display : YES animate : YES ]; [ tabView_contactList setHidden : NO ]; //Allow resizing vertically again [ tabView_contactList setAutoresizingMask : ( NSViewWidthSizable | NSViewHeightSizable )]; case AIPrivacyOptionDenyAll : case AIPrivacyOptionUnknown : NSLog ( @"We should never see these..." ); [ account setPrivacyOptions : privacyOption ]; NSEnumerator * enumerator = [[[ popUp_accounts menu ] itemArray ] objectEnumerator ]; AIAccount < AIAccount_Privacy > * representedAccount ; while (( menuItem = [ enumerator nextObject ])) { if (( representedAccount = [ menuItem representedObject ])) { [ representedAccount setPrivacyOptions : privacyOption ]; //Now make our listContents array match the serverside arrays for the selected account(s) [ listContents removeAllObjects ]; if (( privacyOption == AIPrivacyOptionAllowUsers ) || ( privacyOption == AIPrivacyOptionDenyUsers )) { [ listContents addObjectsFromArray : [ account listObjectsOnPrivacyList : (( privacyOption == AIPrivacyOptionAllowUsers ) ? NSEnumerator * enumerator = [[[ popUp_accounts menu ] itemArray ] objectEnumerator ]; AIAccount < AIAccount_Privacy > * representedAccount ; while (( menuItem = [ enumerator nextObject ])) { if (( representedAccount = [ menuItem representedObject ])) { [ listContents addObjectsFromArray : [ representedAccount listObjectsOnPrivacyList : (( privacyOption == AIPrivacyOptionAllowUsers ) ? - ( void ) selectPrivacyOption: ( AIPrivacyOption ) privacyOption if ( privacyOption == AIPrivacyOptionCustom ) { if ( ! [ stateChooser selectItemWithTag : privacyOption ]) { NSMenuItem * menuItem = [[ NSMenuItem alloc ] initWithTitle : AILocalizedString ( @"(Multiple privacy levels are active)" , nil ) [ menuItem setTag : AIPrivacyOptionCustom ]; [[ stateChooser menu ] addItem : menuItem ]; [ stateChooser selectItemWithTag : privacyOption ]; //Not on custom; make sure custom isn't still in the menu NSInteger customItemIndex = [ stateChooser indexOfItemWithTag : AIPrivacyOptionCustom ]; if ( customItemIndex != -1 ) { [[ stateChooser menu ] removeItemAtIndex : customItemIndex ]; //Now update our view for this privacy option [ self setPrivacyOption : nil ]; #pragma mark Account menu * @brief Return the currently selected account, or nil if the 'All' item is selected - ( AIAccount < AIAccount_Privacy > * ) selectedAccount return [[ popUp_accounts selectedItem ] representedObject ]; * @brief Action called when the account selection changes * Update our view and the privacy option menu to be appropriate for the newly selected account. * This may be called with a sender of nil by code elsewhere to force an update - ( void ) accountMenu: ( AIAccountMenu * ) inAccountMenu didSelectAccount: ( AIAccount * ) inAccount if ( inAccountMenu == accountMenu ) { AIAccount < AIAccount_Privacy > * account = [ self selectedAccount ]; AIPrivacyOption privacyOption = [ account privacyOptions ]; //Don't need the account column when we're showing for just one account [ self setAccountColumnsVisible : NO ]; [ self selectPrivacyOption : privacyOption ]; //Selected 'All'. We need to determine what privacy option to display for the set of all accounts. AIPrivacyOption currentState = AIPrivacyOptionUnknown ; NSEnumerator * enumerator = [[[ popUp_accounts menu ] itemArray ] objectEnumerator ]; while (( menuItem = [ enumerator nextObject ])) { if (( account = [ menuItem representedObject ])) { AIPrivacyOption accountState = [ account privacyOptions ]; if ( currentState == AIPrivacyOptionUnknown ) { //We don't know the state of an account yet currentState = accountState ; } else if ( accountState != currentState ) { currentState = AIPrivacyOptionCustom ; [ self setAccountColumnsVisible : YES ]; [ self selectPrivacyOption : currentState ]; } else if ( inAccountMenu == sheetAccountMenu ) { //Update our sheet for the current account [ self selectAccountInSheet : inAccount ]; * @brief The 'All' menu item for accounts was selected * We simulate an AIAccountMenu delegate call, since the All item was added by RAFBLockEditorWindowController. - ( IBAction ) selectedAllAccountItem: ( id ) sender AIAccountMenu * relevantAccountMenu = (([ sender menu ] == [ popUp_accounts menu ]) ? [ self accountMenu : relevantAccountMenu didSelectAccount : nil ]; * @brief Select an account in our account menu, then update everything else to be appropriate for it - ( void ) selectAccount: ( AIAccount * ) inAccount [ popUp_accounts selectItemWithRepresentedObject : inAccount ]; [ self accountMenu : accountMenu didSelectAccount : inAccount ]; * @brief Add account menu items to our location * Implemented as required by the AccountMenuPlugin protocol. * @param menuItemArray An <tt>NSArray</tt> of <tt>NSMenuItem</tt> objects to be added to the menu - ( void ) accountMenu: ( AIAccountMenu * ) inAccountMenu didRebuildMenuItems: ( NSArray * ) menuItems AIAccount * previouslySelectedAccount = nil ; NSMenu * menu = [[ NSMenu alloc ] init ]; * accountMenu isn't set the first time we get here as the accountMenu is created. Similarly, sheetAccountMenu isn't created its first time. * This code makes the (true) assumption that accountMenu is _always_ created before sheetAccountMenu. BOOL isPrimaryAccountMenu = ( ! accountMenu || ( inAccountMenu == accountMenu )); if ( isPrimaryAccountMenu ) { if ([ popUp_accounts menu ]) { previouslySelectedAccount = [[ popUp_accounts selectedItem ] representedObject ]; } else if ( inAccountMenu == sheetAccountMenu ) { if ([ popUp_sheetAccounts menu ]) { previouslySelectedAccount = [[ popUp_sheetAccounts selectedItem ] representedObject ]; * 1) Determine what state the accounts within the menu are in * 2) Add the menu items to our menu for ( menuItem in menuItems ) { if ( isPrimaryAccountMenu ) { [ popUp_accounts setMenu : menu ]; /* Restore the previous account selection if there was one. * Whether there was one or not, this will cause the rest of our view update to match the new/current selection [ self selectAccount : previouslySelectedAccount ]; [ popUp_sheetAccounts setMenu : menu ]; [ self selectAccountInSheet : previouslySelectedAccount ]; //Add the All menu item first if we have more than one account listed - ( NSMenuItem * ) accountMenuSpecialMenuItem: ( AIAccountMenu * ) inAccountMenu NSMenuItem * allItem = nil ; int numberOfOnlineAccounts = 0 ; for ( AIAccount * account in adium . accountController . accounts ) { if ([ self accountMenu : inAccountMenu shouldIncludeAccount : account ]) { numberOfOnlineAccounts += 1 ; if ( numberOfOnlineAccounts > 1 ) { allItem = [[[ NSMenuItem alloc ] initWithTitle : AILocalizedString ( @"All" , nil ) action : @selector ( selectedAllAccountItem : ) keyEquivalent : @"" ] autorelease ]; - ( BOOL ) accountMenu: ( AIAccountMenu * ) inAccountMenu shouldIncludeAccount: ( AIAccount * ) inAccount BOOL isPrimaryAccountMenu = ( ! accountMenu || ( inAccountMenu == accountMenu )); if ( isPrimaryAccountMenu ) { return ( inAccount . online && [ inAccount conformsToProtocol : @ protocol ( AIAccount_Privacy )]); AIAccount * selectedPrimaryAccount = self . selectedAccount ; if ( selectedPrimaryAccount ) { //An account is selected in the main window; only incldue that account in our sheet return ( inAccount == selectedPrimaryAccount ); //'All' is selected in the main window; include all accounts which are online and support privacy return ( inAccount . online && [ inAccount conformsToProtocol : @ protocol ( AIAccount_Privacy )]); - ( void ) privacySettingsChangedExternally: ( NSNotification * ) inNotification [ self accountMenu : accountMenu didSelectAccount : [ self selectedAccount ]]; - ( NSSet * ) updateListObject: ( AIListObject * ) inObject keys: ( NSSet * ) inModifiedKeys silent: ( BOOL ) silent if ([ inModifiedKeys containsObject : KEY_IS_BLOCKED ]) { [ self privacySettingsChangedExternally : nil ]; - ( NSInteger ) numberOfRowsInTableView: ( NSTableView * ) aTableView return [ listContents count ]; - ( id ) tableView: ( NSTableView * ) aTableView objectValueForTableColumn: ( NSTableColumn * ) aTableColumn row: ( NSInteger ) rowIndex NSString * identifier = [ aTableColumn identifier ]; AIListContact * contact = [ listContents objectAtIndex : rowIndex ]; if ([ identifier isEqualToString : @"icon" ]) { return [ contact menuIcon ]; } else if ([ identifier isEqualToString : @"contact" ]) { return contact . formattedUID ; } else if ([ identifier isEqualToString : @"account" ]) { return contact . account . formattedUID ; - ( BOOL ) writeListObjects: ( NSArray * ) inArray toPasteboard: ( NSPasteboard * ) pboard [ pboard declareTypes : [ NSArray arrayWithObjects : @"AIListObject" , @"AIListObjectUniqueIDs" , nil ] owner : self ]; [ pboard setString : @"Private" forType : @"AIListObject" ]; if ( dragItems != inArray ) { dragItems = [ inArray retain ]; - ( BOOL ) tableView: ( NSTableView * ) tv writeRows: ( NSArray * ) rows toPasteboard: ( NSPasteboard * ) pboard NSMutableArray * itemArray = [ NSMutableArray array ]; for ( rowNumber in rows ) { [ itemArray addObject : [ listContents objectAtIndex : [ rowNumber integerValue ]]]; return [ self writeListObjects : itemArray toPasteboard : pboard ]; - ( BOOL ) tableView: ( NSTableView * ) aTableView writeRowsWithIndexes: ( NSIndexSet * ) rowIndexes toPasteboard: ( NSPasteboard * ) pboard NSMutableArray * itemArray = [ NSMutableArray array ]; NSUInteger bufSize = [ rowIndexes count ]; NSUInteger * buf = malloc ( bufSize * sizeof ( NSUInteger )); NSRange range = NSMakeRange ([ rowIndexes firstIndex ], ([ rowIndexes lastIndex ] - [ rowIndexes firstIndex ]) + 1 ); [ rowIndexes getIndexes : buf maxCount : bufSize inIndexRange :& range ]; for ( i = 0 ; i != bufSize ; i ++ ) { if (( item = [ listContents objectAtIndex : buf [ i ]])) { [ itemArray addObject : item ]; return [ self writeListObjects : itemArray toPasteboard : pboard ]; - ( void ) pasteboard: ( NSPasteboard * ) sender provideDataForType: ( NSString * ) type //Provide an array of internalObjectIDs which can be used to reference all the dragged contacts if ([ type isEqualToString : @"AIListObjectUniqueIDs" ]) { NSMutableArray * dragItemsArray = [ NSMutableArray array ]; AIListObject * listObject ; for ( listObject in dragItems ) { [ dragItemsArray addObject : listObject . internalObjectID ]; [ sender setPropertyList : dragItemsArray forType : @"AIListObjectUniqueIDs" ]; - ( NSDragOperation ) tableView: ( NSTableView * ) tv validateDrop :( id < NSDraggingInfo > ) info proposedRow :( NSInteger ) row proposedDropOperation :( NSTableViewDropOperation ) op NSDragOperation dragOp = NSDragOperationCopy ; if ([ info draggingSource ] == table ) { dragOp = NSDragOperationMove ; [ tv setDropRow : row dropOperation : NSTableViewDropAbove ]; - ( void ) addListObjectToList: ( AIListObject * ) listObject AIListObject * containedObject ; NSEnumerator * enumerator ; if ([ listObject isKindOfClass : [ AIListGroup class ]]) { enumerator = [[( AIListGroup * ) listObject uniqueContainedObjects ] objectEnumerator ]; while (( containedObject = [ enumerator nextObject ])) { [ self addListObjectToList : containedObject ]; } else if ([ listObject isKindOfClass : [ AIMetaContact class ]]) { enumerator = [[( AIMetaContact * ) listObject uniqueContainedObjects ] objectEnumerator ]; while (( containedObject = [ enumerator nextObject ])) { [ self addListObjectToList : containedObject ]; } else if ([ listObject isKindOfClass : [ AIListContact class ]]) { //if the account for this contact is connected... if ([( AIListContact * ) listObject account ]. online ) { [ self addObject : ( AIListContact * ) listObject ]; - ( BOOL ) tableView: ( NSTableView * ) tv acceptDrop: ( id < NSDraggingInfo > ) info row: ( NSInteger ) row dropOperation: ( NSTableViewDropOperation ) op if ([ info . draggingPasteboard . types containsObject : @"AIListObjectUniqueIDs" ]) { for ( NSString * uniqueUID in [ info . draggingPasteboard propertyListForType : @"AIListObjectUniqueIDs" ]) [ self addListObjectToList : [ adium . contactController existingListObjectWithUniqueID : uniqueUID ]];