2 // RAFBlockEditorWindow.m
5 // Created by Augie Fackler on 5/26/05.
6 // Copyright 2006 The Adium Team. All rights reserved.
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/AIAccountMenu.h>
17 #import <Adium/AIListContact.h>
18 #import <Adium/AIMetaContact.h>
19 #import <Adium/AIService.h>
21 @interface RAFBlockEditorWindowController (PRIVATE)
22 - (NSMenu *)privacyOptionsMenu;
23 - (AIAccount<AIAccount_Privacy> *)selectedAccount;
24 - (void)configureTextField;
25 - (NSSet *)contactsFromTextField;
26 - (AIPrivacyOption)selectedPrivacyOption;
29 @implementation RAFBlockEditorWindowController
31 static RAFBlockEditorWindowController *sharedInstance = nil;
35 if (!sharedInstance) {
36 sharedInstance = [[self alloc] initWithWindowNibName:@"BlockEditorWindow"];
39 [sharedInstance showWindow:nil];
40 [[sharedInstance window] makeKeyAndOrderFront:nil];
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)];
53 //Let the min X margin be resizeable while label_account and label_privacyLevel localize in case the window moves
54 [stateChooser setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin | NSViewMinXMargin)];
55 [popUp_accounts setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin | NSViewMinXMargin)];
57 [label_account setLocalizedString:AILocalizedString(@"Account:",nil)];
58 [label_privacyLevel setLocalizedString:AILocalizedString(@"Privacy level:", nil)];
60 [stateChooser setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
61 [popUp_accounts setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
64 accountColumnsVisible = YES;
67 listContents = [[NSMutableArray alloc] init];
69 [stateChooser setMenu:[self privacyOptionsMenu]];
71 [[table tableColumnWithIdentifier:@"icon"] setDataCell:[[[NSImageCell alloc] init] autorelease]];
73 accountMenu = [[AIAccountMenu accountMenuWithDelegate:self
74 submenuType:AIAccountNoSubmenu
75 showTitleVerbs:NO] retain];
76 [table registerForDraggedTypes:[NSArray arrayWithObjects:@"AIListObject", @"AIListObjectUniqueIDs",nil]];
78 [[adium notificationCenter] addObserver:self
79 selector:@selector(privacySettingsChangedExternally:)
80 name:@"AIPrivacySettingsChangedOutsideOfPrivacyWindow"
83 [[adium contactController] registerListObjectObserver:self];
85 [super windowDidLoad];
88 - (void)windowWillClose:(id)sender
90 [super windowWillClose:sender];
92 [[adium contactController] unregisterListObjectObserver:self];
94 [[adium notificationCenter] removeObserver:self];
95 [sharedInstance release]; sharedInstance = nil;
98 - (NSString *)adiumFrameAutosaveName
100 return @"PrivacyWindow";
105 [accountCol release];
106 [accountMenu release];
107 [listContents release];
108 [listContentsAllAccounts release];
113 - (NSMutableArray*)listContents
118 - (void)setListContents:(NSArray*)newList
120 if (newList != listContents) {
121 [listContents release];
122 listContents = [newList mutableCopy];
126 #pragma mark Adding a contact to the list
127 - (IBAction)runBlockSheet:(id)sender
129 [field setStringValue:@""];
131 sheetAccountMenu = [[AIAccountMenu accountMenuWithDelegate:self
132 submenuType:AIAccountNoSubmenu
133 showTitleVerbs:NO] retain];
135 [NSApp beginSheet:sheet
136 modalForWindow:[self window]
138 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
142 - (void)selectAccountInSheet:(AIAccount *)inAccount
144 [popUp_sheetAccounts selectItemWithRepresentedObject:inAccount];
145 [self configureTextField];
147 NSString *userNameLabel = [[inAccount service] userNameLabel];
149 [buddyText setStringValue:[(userNameLabel ? userNameLabel :
150 AILocalizedString(@"Contact ID",nil)) stringByAppendingString:@":"]];
153 - (IBAction)cancelBlockSheet:(id)sender
155 [NSApp endSheet:sheet];
158 - (void)addObject:(AIListContact *)inContact
161 if (![listContents containsObject:inContact]) {
162 [listContents addObject:inContact];
165 [inContact setIsOnPrivacyList:YES updateList:YES privacyType:(([self selectedPrivacyOption] == AIPrivacyOptionAllowUsers) ?
166 AIPrivacyTypePermit :
171 - (IBAction)didBlockSheet:(id)sender
173 NSSet *contactArray = [self contactsFromTextField];
175 //Add the contact immediately
176 if (contactArray && [contactArray count]) {
177 NSEnumerator *enumerator;
178 AIListContact *contact;
180 enumerator = [contactArray objectEnumerator];
181 while ((contact = [enumerator nextObject])) {
182 [self addObject:contact];
188 [NSApp endSheet:sheet];
192 - (void)didEndSheet:(NSWindow *)theSheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
194 [sheetAccountMenu release]; sheetAccountMenu = nil;
195 [theSheet orderOut:self];
199 * @brief Get a set of all contacts which are represented by the currently selected account and UID field
201 * @result A set of AIListContact objects
203 - (NSSet *)contactsFromTextField
205 AIListContact *contact = nil;
207 AIAccount *account = [[popUp_sheetAccounts selectedItem] representedObject];;
208 NSArray *accountArray;
209 NSMutableSet *contactsSet = [NSMutableSet set];
210 NSEnumerator *enumerator;
211 id impliedValue = [field impliedValue];
214 accountArray = [NSArray arrayWithObject:account];
217 NSMutableArray *tempArray = [NSMutableArray array];
218 NSMenuItem *menuItem;
220 enumerator = [[[popUp_sheetAccounts menu] itemArray] objectEnumerator];
221 while ((menuItem = [enumerator nextObject])) {
222 AIAccount *anAccount;
224 if ((anAccount = [menuItem representedObject])) {
225 [tempArray addObject:anAccount];
229 accountArray = tempArray;
232 enumerator = [accountArray objectEnumerator];
233 while ((account = [enumerator nextObject])) {
234 if ([impliedValue isKindOfClass:[AIMetaContact class]]) {
235 AIListContact *containedContact;
236 NSEnumerator *contactEnumerator = [[(AIMetaContact *)impliedValue listContactsIncludingOfflineAccounts] objectEnumerator];
238 while ((containedContact = [contactEnumerator nextObject])) {
239 /* For each contact contained my the metacontact, check if its service class matches the current account's.
240 * If it does, add that contact to our list, using the contactController to get an AIListContact specific for the account.
242 if ([[[containedContact service] serviceClass] isEqualToString:[[account service] serviceClass]]) {
243 if ((contact = [[adium contactController] contactWithService:[account service]
245 UID:[containedContact UID]])) {
246 [contactsSet addObject:contact];
252 if ([impliedValue isKindOfClass:[AIListContact class]]) {
253 UID = [(AIListContact *)impliedValue UID];
255 } else if ([impliedValue isKindOfClass:[NSString class]]) {
256 UID = [[account service] filterUID:impliedValue removeIgnoredCharacters:YES];
260 //Get a contact with this UID on the current account
261 if ((contact = [[adium contactController] contactWithService:[account service]
264 [contactsSet addObject:contact];
274 - (void)configureTextField
276 AIAccount *account = [[popUp_sheetAccounts selectedItem] representedObject];
277 NSEnumerator *enumerator;
278 AIListContact *contact;
280 //Clear the completing strings
281 [field setCompletingStrings:nil];
283 //Configure the auto-complete view to autocomplete for contacts matching the selected account's service
284 enumerator = [[[adium contactController] allContactsInGroup:nil subgroups:YES onAccount:nil] objectEnumerator];
285 while ((contact = [enumerator nextObject])) {
287 [contact service] == [account service]) {
288 NSString *UID = [contact UID];
289 [field addCompletionString:[contact formattedUID] withImpliedCompletion:UID];
290 [field addCompletionString:[contact displayName] withImpliedCompletion:UID];
291 [field addCompletionString:UID];
296 #pragma mark Removing a contact from the list
298 - (IBAction)removeSelection:(id)sender
300 int selection = [[table selectedRowIndexes] firstIndex];
302 if (selection != NSNotFound) {
303 AIListContact *contact = [listContents objectAtIndex:selection];
305 //Remove from our list
306 [listContents removeObject:contact];
310 [contact setIsOnPrivacyList:NO updateList:YES privacyType:(([self selectedPrivacyOption] == AIPrivacyOptionAllowUsers) ?
311 AIPrivacyTypePermit :
316 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView
318 [self removeSelection:tableView];
321 - (void)setAccountColumnsVisible:(BOOL)visible
323 if (accountColumnsVisible != visible) {
325 [table addTableColumn:accountCol];
327 [table removeTableColumn:accountCol];
331 accountColumnsVisible = visible;
334 #pragma mark Privacy options menu
336 - (NSMenu *)privacyOptionsMenu
338 //build the menu of states
339 NSMenu *stateMenu = [[NSMenu alloc] init];
341 NSMenuItem *menuItem;
343 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Allow anyone", nil)
346 [menuItem setTag:AIPrivacyOptionAllowAll];
347 [stateMenu addItem:menuItem];
350 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Allow only contacts on my contact list", nil)
353 [menuItem setTag:AIPrivacyOptionAllowContactList];
354 [stateMenu addItem:menuItem];
357 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Allow only certain contacts", nil)
360 [menuItem setTag:AIPrivacyOptionAllowUsers];
361 [stateMenu addItem:menuItem];
364 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Block certain contacts", nil)
367 [menuItem setTag:AIPrivacyOptionDenyUsers];
368 [stateMenu addItem:menuItem];
372 tmpItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Custom settings for each account", nil) action:NULL keyEquivalent:@""];
373 [tmpItem setRepresentedObject:[NSNumber numberWithInt:AIPrivacyOptionCustom]];
374 [stateMenu addItem:[tmpItem autorelease]];
377 return [stateMenu autorelease];
380 - (AIPrivacyOption)selectedPrivacyOption
382 return [[stateChooser selectedItem] tag];
386 * @brief Set a privacy option and update our view for it
388 * @param sender If nil, we update our display without attempting to change anything on our account
390 - (IBAction)setPrivacyOption:(id)sender
392 AIAccount<AIAccount_Privacy> *account = [self selectedAccount];
393 AIPrivacyOption privacyOption = [self selectedPrivacyOption];
395 //First, let's get the right tab view selected
396 switch (privacyOption) {
397 case AIPrivacyOptionAllowAll:
398 case AIPrivacyOptionAllowContactList:
399 case AIPrivacyOptionCustom:
400 if (![[[tabView_contactList selectedTabViewItem] identifier] isEqualToString:@"empty"]) {
401 [tabView_contactList selectTabViewItemWithIdentifier:@"empty"];
402 [tabView_contactList setHidden:YES];
404 NSRect frame = [[self window] frame];
405 float tabViewHeight = [tabView_contactList frame].size.height;
406 frame.size.height -= tabViewHeight;
407 frame.origin.y += tabViewHeight;
409 //Don't resize vertically now...
410 [tabView_contactList setAutoresizingMask:NSViewWidthSizable];
412 [[self window] setMinSize:NSMakeSize(250, frame.size.height)];
413 [[self window] setMaxSize:NSMakeSize(FLT_MAX, frame.size.height)];
414 [[self window] setFrame:frame display:YES animate:YES];
418 case AIPrivacyOptionAllowUsers:
419 case AIPrivacyOptionDenyUsers:
420 if (![[[tabView_contactList selectedTabViewItem] identifier] isEqualToString:@"list"]) {
421 [tabView_contactList selectTabViewItemWithIdentifier:@"list"];
423 NSRect frame = [[self window] frame];
424 float tabViewHeight = [tabView_contactList frame].size.height;
425 frame.size.height += tabViewHeight;
426 frame.origin.y -= tabViewHeight;
428 [[self window] setMinSize:NSMakeSize(250, 320)];
429 [[self window] setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
431 //Set frame after fixing our min/max size so the resize won't fail
432 [[self window] setFrame:frame display:YES animate:YES];
434 [tabView_contactList setHidden:NO];
436 //Allow resizing vertically again
437 [tabView_contactList setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
440 case AIPrivacyOptionDenyAll:
441 case AIPrivacyOptionUnknown:
442 NSLog(@"We should never see these...");
448 [account setPrivacyOptions:privacyOption];
451 NSEnumerator *enumerator = [[[popUp_accounts menu] itemArray] objectEnumerator];
452 NSMenuItem *menuItem;
453 AIAccount<AIAccount_Privacy> *representedAccount;
455 while ((menuItem = [enumerator nextObject])) {
456 if ((representedAccount = [menuItem representedObject])) {
457 [representedAccount setPrivacyOptions:privacyOption];
463 //Now make our listContents array match the serverside arrays for the selected account(s)
464 [listContents removeAllObjects];
465 if ((privacyOption == AIPrivacyOptionAllowUsers) ||
466 (privacyOption == AIPrivacyOptionDenyUsers)) {
468 [listContents addObjectsFromArray:[account listObjectsOnPrivacyList:((privacyOption == AIPrivacyOptionAllowUsers) ?
469 AIPrivacyTypePermit :
470 AIPrivacyTypeDeny)]];
472 NSEnumerator *enumerator = [[[popUp_accounts menu] itemArray] objectEnumerator];
473 NSMenuItem *menuItem;
474 AIAccount<AIAccount_Privacy> *representedAccount;
476 while ((menuItem = [enumerator nextObject])) {
477 if ((representedAccount = [menuItem representedObject])) {
478 [listContents addObjectsFromArray:[representedAccount listObjectsOnPrivacyList:((privacyOption == AIPrivacyOptionAllowUsers) ?
479 AIPrivacyTypePermit :
480 AIPrivacyTypeDeny)]];
489 - (void)selectPrivacyOption:(AIPrivacyOption)privacyOption
491 BOOL success = [stateChooser compatibleSelectItemWithTag:privacyOption];
492 if (privacyOption == AIPrivacyOptionCustom) {
494 NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"(Multiple privacy levels are active)", nil)
497 [menuItem setTag:AIPrivacyOptionCustom];
498 [[stateChooser menu] addItem:menuItem];
501 success = [stateChooser compatibleSelectItemWithTag:privacyOption];
505 //Not on custom; make sure custom isn't still in the menu
506 int customItemIndex = [stateChooser indexOfItemWithTag:AIPrivacyOptionCustom];
507 if (customItemIndex != -1) {
508 [[stateChooser menu] removeItemAtIndex:customItemIndex];
512 //Now update our view for this privacy option
513 [self setPrivacyOption:nil];
516 #pragma mark Account menu
518 * @brief Return the currently selected account, or nil if the 'All' item is selected
520 - (AIAccount<AIAccount_Privacy> *)selectedAccount
522 return [[popUp_accounts selectedItem] representedObject];
526 * @brief Action called when the account selection changes
528 * Update our view and the privacy option menu to be appropriate for the newly selected account.
529 * This may be called with a sender of nil by code elsewhere to force an update
531 - (void)accountMenu:(AIAccountMenu *)inAccountMenu didSelectAccount:(AIAccount *)inAccount
533 if (inAccountMenu == accountMenu) {
534 AIAccount<AIAccount_Privacy> *account = [self selectedAccount];
536 //Selected an account
537 AIPrivacyOption privacyOption = [account privacyOptions];
539 //Don't need the account column when we're showing for just one account
540 [self setAccountColumnsVisible:NO];
542 [self selectPrivacyOption:privacyOption];
545 //Selected 'All'. We need to determine what privacy option to display for the set of all accounts.
546 AIPrivacyOption currentState = AIPrivacyOptionUnknown;
547 NSEnumerator *enumerator = [[[popUp_accounts menu] itemArray] objectEnumerator];
548 NSMenuItem *menuItem;
550 while ((menuItem = [enumerator nextObject])) {
551 if ((account = [menuItem representedObject])) {
552 AIPrivacyOption accountState = [account privacyOptions];
554 if (currentState == AIPrivacyOptionUnknown) {
555 //We don't know the state of an account yet
556 currentState = accountState;
557 } else if (accountState != currentState) {
558 currentState = AIPrivacyOptionCustom;
563 [self setAccountColumnsVisible:YES];
565 [self selectPrivacyOption:currentState];
568 } else if (inAccountMenu == sheetAccountMenu) {
569 //Update our sheet for the current account
570 [self selectAccountInSheet:inAccount];
575 * @brief The 'All' menu item for accounts was selected
577 * We simulate an AIAccountMenu delegate call, since the All item was added by RAFBLockEditorWindowController.
579 - (IBAction)selectedAllAccountItem:(id)sender
581 AIAccountMenu *relevantAccountMenu = (([sender menu] == [popUp_accounts menu]) ?
585 [self accountMenu:relevantAccountMenu didSelectAccount:nil];
589 * @brief Select an account in our account menu, then update everything else to be appropriate for it
591 - (void)selectAccount:(AIAccount *)inAccount
593 [popUp_accounts selectItemWithRepresentedObject:inAccount];
595 [self accountMenu:accountMenu didSelectAccount:inAccount];
599 * @brief Add account menu items to our location
601 * Implemented as required by the AccountMenuPlugin protocol.
603 * @param menuItemArray An <tt>NSArray</tt> of <tt>NSMenuItem</tt> objects to be added to the menu
605 - (void)accountMenu:(AIAccountMenu *)inAccountMenu didRebuildMenuItems:(NSArray *)menuItems
607 AIAccount *previouslySelectedAccount = nil;
608 NSEnumerator *enumerator;
609 NSMenuItem *menuItem;
610 NSMenu *menu = [[NSMenu alloc] init];
613 * accountMenu isn't set the first time we get here as the accountMenu is created. Similarly, sheetAccountMenu isn't created its first time.
614 * This code makes the (true) assumption that accountMenu is _always_ created before sheetAccountMenu.
616 BOOL isPrimaryAccountMenu = (!accountMenu || (inAccountMenu == accountMenu));
618 if (isPrimaryAccountMenu) {
619 if ([popUp_accounts menu]) {
620 previouslySelectedAccount = [[popUp_accounts selectedItem] representedObject];
622 } else if (inAccountMenu == sheetAccountMenu) {
623 if ([popUp_sheetAccounts menu]) {
624 previouslySelectedAccount = [[popUp_sheetAccounts selectedItem] representedObject];
628 //Add the All menu item first if we have more than one account listed
629 if ([menuItems count] > 1) {
630 [menu addItemWithTitle:AILocalizedString(@"All", nll)
632 action:@selector(selectedAllAccountItem:)
637 * As we enumerate, we:
638 * 1) Determine what state the accounts within the menu are in
639 * 2) Add the menu items to our menu
641 enumerator = [menuItems objectEnumerator];
642 while ((menuItem = [enumerator nextObject])) {
643 [menu addItem:menuItem];
646 if (isPrimaryAccountMenu) {
647 [popUp_accounts setMenu:menu];
649 /* Restore the previous account selection if there was one.
650 * Whether there was one or not, this will cause the rest of our view update to match the new/current selection
652 [self selectAccount:previouslySelectedAccount];
655 [popUp_sheetAccounts setMenu:menu];
657 [self selectAccountInSheet:previouslySelectedAccount];
663 - (BOOL)accountMenu:(AIAccountMenu *)inAccountMenu shouldIncludeAccount:(AIAccount *)inAccount
665 BOOL isPrimaryAccountMenu = (!accountMenu || (inAccountMenu == accountMenu));
667 if (isPrimaryAccountMenu) {
668 return ([inAccount online] &&
669 [inAccount conformsToProtocol:@protocol(AIAccount_Privacy)]);
671 AIAccount *selectedPrimaryAccount = [self selectedAccount];
672 if (selectedPrimaryAccount) {
673 //An account is selected in the main window; only incldue that account in our sheet
674 return (inAccount == selectedPrimaryAccount);
677 //'All' is selected in the main window; include all accounts which are online and support privacy
678 return ([inAccount online] &&
679 [inAccount conformsToProtocol:@protocol(AIAccount_Privacy)]);
684 - (void)privacySettingsChangedExternally:(NSNotification *)inNotification
686 [self accountMenu:accountMenu didSelectAccount:[self selectedAccount]];
689 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
691 if ([inModifiedKeys containsObject:KEY_IS_BLOCKED]) {
692 [self privacySettingsChangedExternally:nil];
698 #pragma mark Table view
700 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
702 return [listContents count];
705 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
707 NSString *identifier = [aTableColumn identifier];
708 AIListContact *contact = [listContents objectAtIndex:rowIndex];
710 if ([identifier isEqualToString:@"icon"]) {
711 return [contact menuIcon];
713 } else if ([identifier isEqualToString:@"contact"]) {
714 return [contact formattedUID];
716 } else if ([identifier isEqualToString:@"account"]) {
717 return [[contact account] formattedUID];
723 - (BOOL)writeListObjects:(NSArray *)inArray toPasteboard:(NSPasteboard*)pboard
725 [pboard declareTypes:[NSArray arrayWithObjects:@"AIListObject",@"AIListObjectUniqueIDs",nil] owner:self];
726 [pboard setString:@"Private" forType:@"AIListObject"];
728 if (dragItems != inArray) {
730 dragItems = [inArray retain];
736 - (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
738 NSMutableArray *itemArray = [NSMutableArray array];
739 NSEnumerator *enumerator = [rows objectEnumerator];
741 while ((rowNumber = [enumerator nextObject])) {
742 [itemArray addObject:[listContents objectAtIndex:[rowNumber intValue]]];
745 return [self writeListObjects:itemArray toPasteboard:pboard];
748 - (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
750 NSMutableArray *itemArray = [NSMutableArray array];
753 unsigned int bufSize = [rowIndexes count];
754 unsigned int *buf = malloc(bufSize * sizeof(unsigned int));
757 NSRange range = NSMakeRange([rowIndexes firstIndex], ([rowIndexes lastIndex]-[rowIndexes firstIndex]) + 1);
758 [rowIndexes getIndexes:buf maxCount:bufSize inIndexRange:&range];
760 for (i = 0; i != bufSize; i++) {
761 if ((item = [listContents objectAtIndex:buf[i]])) {
762 [itemArray addObject:item];
768 return [self writeListObjects:itemArray toPasteboard:pboard];
771 - (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
773 //Provide an array of internalObjectIDs which can be used to reference all the dragged contacts
774 if ([type isEqualToString:@"AIListObjectUniqueIDs"]) {
777 NSMutableArray *dragItemsArray = [NSMutableArray array];
778 NSEnumerator *enumerator = [dragItems objectEnumerator];
779 AIListObject *listObject;
781 while ((listObject = [enumerator nextObject])) {
782 [dragItemsArray addObject:[listObject internalObjectID]];
785 [sender setPropertyList:dragItemsArray forType:@"AIListObjectUniqueIDs"];
790 - (NSDragOperation)tableView:(NSTableView*)tv
791 validateDrop:(id <NSDraggingInfo>)info
793 proposedDropOperation:(NSTableViewDropOperation)op
796 NSDragOperation dragOp = NSDragOperationCopy;
798 if ([info draggingSource] == table) {
799 dragOp = NSDragOperationMove;
801 [tv setDropRow:row dropOperation:NSTableViewDropAbove];
806 - (void)addListObjectToList:(AIListObject *)listObject
808 AIListObject *containedObject;
809 NSEnumerator *enumerator;
811 if ([listObject isKindOfClass:[AIListGroup class]]) {
812 enumerator = [[(AIListGroup *)listObject listContacts] objectEnumerator];
813 while ((containedObject = [enumerator nextObject])) {
814 [self addListObjectToList:containedObject];
817 } else if ([listObject isKindOfClass:[AIMetaContact class]]) {
818 enumerator = [[(AIMetaContact *)listObject listContacts] objectEnumerator];
819 while ((containedObject = [enumerator nextObject])) {
820 [self addListObjectToList:containedObject];
823 } else if ([listObject isKindOfClass:[AIListContact class]]) {
824 //if the account for this contact is connected...
825 if ([[(AIListContact *)listObject account] online]) {
826 [self addObject:(AIListContact *)listObject];
831 - (BOOL)tableView:(NSTableView*)tv acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)op
837 if ([[[info draggingPasteboard] types] containsObject:@"AIListObjectUniqueIDs"]) {
838 NSArray *dragItemsUniqueIDs = [[info draggingPasteboard] propertyListForType:@"AIListObjectUniqueIDs"];
840 NSEnumerator *idEnumerator = [dragItemsUniqueIDs objectEnumerator];
841 while ((uniqueUID = [idEnumerator nextObject]))
842 [self addListObjectToList:[[adium contactController] existingListObjectWithUniqueID:uniqueUID]];