2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "AIContactAccountsPane.h"
18 #import <Adium/AIAccountControllerProtocol.h>
19 #import <Adium/AIContactControllerProtocol.h>
20 #import <AIUtilities/AIAlternatingRowTableView.h>
21 #import <AIUtilities/AIMenuAdditions.h>
22 #import <AIUtilities/AIArrayAdditions.h>
23 #import <AIUtilities/AIPopUpButtonAdditions.h>
24 #import <Adium/AIAccount.h>
25 #import <Adium/AIListContact.h>
26 #import <Adium/AIListObject.h>
27 #import <Adium/AIListGroup.h>
28 #import <Adium/AILocalizationTextField.h>
29 #import <Adium/AIMetaContact.h>
31 @interface AIContactAccountsPane (PRIVATE)
32 - (void)updateAccountList;
33 - (void)updateGroupList;
36 static NSComparisonResult compareContactsByTheirAccounts(id firstContact, id secondContact, void *context);
39 * @class AIContactAccountsPane
40 * @brief Accounts pane in the contact info window
42 * Provides a list of what accounts list a contact and in what group.
44 @implementation AIContactAccountsPane
46 //Preference pane properties
50 - (AIContactInfoCategory)contactInfoCategory
52 return AIInfo_Accounts;
60 return @"ContactAccounts";
64 * @brief Configure the preference view
68 [label_listedOnTheFollowingOfYourAccounts setLocalizedString:AILocalizedString(@"Listed on the following of your accounts:",nil)];
70 //Configure Table view
71 [tableView_accounts setUsesAlternatingRowBackgroundColors:YES];
72 [tableView_accounts setAcceptsFirstMouse:YES];
74 [[[tableView_accounts tableColumnWithIdentifier:@"account"] headerCell] setTitle:AILocalizedString(@"On Account",nil)];
75 [[[tableView_accounts tableColumnWithIdentifier:@"contact"] headerCell] setTitle:AILocalizedString(@"Individual Contact","This header for the table in the Accounts tab of the Get Info window indicates the name of the contact within a metacontact")];
76 [[[tableView_accounts tableColumnWithIdentifier:@"group"] headerCell] setTitle:AILocalizedString(@"In Group",nil)];
77 contactsColumnIsInAccountsTableView = YES; //It's in the table view in the nib.
79 //Observe contact list changes
80 [[adium notificationCenter] addObserver:self
81 selector:@selector(updateGroupList)
82 name:Contact_ListChanged
84 [self updateGroupList];
86 //Observe account changes
87 [[adium notificationCenter] addObserver:self
88 selector:@selector(updateAccountList)
89 name:Account_ListChanged
91 [self updateAccountList];
93 [tableView_accounts sizeToFit];
97 * @brief Preference view is closing
101 [accounts release]; accounts = nil;
102 [contacts release]; contacts = nil;
103 [listObject release]; listObject = nil;
104 [[adium notificationCenter] removeObserver:self];
108 * @brief Configure the pane for a list object
110 - (void)configureForListObject:(AIListObject *)inObject
112 if (listObject != inObject) {
113 //Update the table view to have or not have the "Individual Contact" column, as appropriate.
114 //It should have the column when our list object is a metacontact.
115 if ([inObject isKindOfClass:[AIMetaContact class]]) {
116 if (!contactsColumnIsInAccountsTableView) {
118 [tableView_accounts addTableColumn:tableColumn_contacts];
119 //It was added as last; move to the middle.
120 [tableView_accounts moveColumn:2 toColumn:1];
121 //Set all of the table view's columns to be the same width.
122 float columnWidth = [tableView_accounts frame].size.width / 3.0;
123 [[tableView_accounts tableColumns] setValue:[NSNumber numberWithFloat:columnWidth] forKey:@"width"];
124 [tableView_accounts tile];
125 //We don't need it retained anymore.
126 [tableColumn_contacts release];
128 contactsColumnIsInAccountsTableView = YES;
130 } else if(contactsColumnIsInAccountsTableView) {
132 //Note that the column is in the table view in the nib, so it is in the table view before we have been configured for the first time.
133 //And be sure to retain it before removing it from the view.
134 [tableColumn_contacts retain];
135 [tableView_accounts removeTableColumn:tableColumn_contacts];
136 //Set both of the table view's columns to be the same width.
137 float columnWidth = [tableView_accounts frame].size.width / 2.0;
138 [[tableView_accounts tableColumns] setValue:[NSNumber numberWithFloat:columnWidth] forKey:@"width"];
139 [tableView_accounts tile];
141 contactsColumnIsInAccountsTableView = NO;
144 //Switch to the new list object.
145 [listObject release];
146 listObject = [inObject retain];
148 //Rebuild our account list.
149 [self updateAccountList];
153 - (void)getAccounts:(NSArray **)outAccounts withContacts:(NSArray **)outContacts forListContact:(AIListContact *)listContact
155 //Build a list of all accounts (compatible with the service of the input contact) that have the input contact's UID on their contact list.
156 AIService *service = [listContact service];
157 NSString *UID = [listContact UID];
159 NSArray *compatibleAccounts = [[adium accountController] accountsCompatibleWithService:service];
160 NSMutableArray *foundAccounts = [[NSMutableArray alloc] initWithCapacity:[compatibleAccounts count]];
161 NSMutableArray *contactTimesN = [[NSMutableArray alloc] initWithCapacity:[compatibleAccounts count]];
163 id <AIContactController> contactController = [adium contactController];
164 NSEnumerator *compatibleAccountsEnum = [compatibleAccounts objectEnumerator];
166 while ((account = [compatibleAccountsEnum nextObject])) {
167 AIListContact *contactOnThisAccount;
168 if ((contactOnThisAccount = [contactController existingContactWithService:service account:account UID:UID]) &&
169 [contactOnThisAccount remoteGroupName]) {
170 [foundAccounts addObject:account];
171 [contactTimesN addObject:contactOnThisAccount];
175 if (outAccounts) *outAccounts = [foundAccounts autorelease];
176 if (outContacts) *outContacts = [contactTimesN autorelease];
180 * @brief Update our list of accounts
182 - (void)updateAccountList
184 //Get the new accounts
188 if ([listObject isKindOfClass:[AIMetaContact class]]) {
189 //Get all contacts of the metacontact.
190 //Sort them by account.
191 //Get the account of each contact.
192 //Finally, uniquify the accounts through a set.
193 NSMutableArray *workingAccounts = [NSMutableArray array];
194 NSMutableArray *workingContacts = [NSMutableArray array];
195 NSEnumerator *enumerator;
196 AIListContact *listContact;
197 enumerator = [[[(AIMetaContact *)listObject listContacts] sortedArrayUsingFunction:compareContactsByTheirAccounts
198 context:NULL] objectEnumerator];
199 while ((listContact = [enumerator nextObject])) {
200 NSArray *thisContactAccounts;
201 NSArray *thisContactContacts;
203 [self getAccounts:&thisContactAccounts withContacts:&thisContactContacts forListContact:listContact];
204 [workingAccounts addObjectsFromArray:thisContactAccounts];
205 [workingContacts addObjectsFromArray:thisContactContacts];
208 accounts = [workingAccounts retain];
209 contacts = [workingContacts retain];
211 } else if ([listObject isKindOfClass:[AIListContact class]]) {
212 [self getAccounts:&accounts withContacts:&contacts forListContact:(AIListContact *)listObject];
221 [tableView_accounts reloadData];
225 * @brief Update our list of groups
227 - (void)updateGroupList
230 NSMenu *groupMenu = [[adium contactController] menuOfAllGroupsInGroup:nil withTarget:self];
231 [[[tableView_accounts tableColumnWithIdentifier:@"group"] dataCell] setMenu:groupMenu];
234 [tableView_accounts reloadData];
238 //Table View Data Sources ----------------------------------------------------------------------------------------------
239 #pragma mark TableView Data Sources
241 * @brief Number of table view rows
243 - (int)numberOfRowsInTableView:(NSTableView *)tableView
245 return [accounts count];
249 * @brief Table view object value
251 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
255 NSString *identifier = [tableColumn identifier];
257 if ([identifier isEqualToString:@"account"]) {
258 AIAccount *account = [accounts objectAtIndex:row];
259 NSString *accountFormattedUID = [account formattedUID];
261 if ([account online]) {
262 result = accountFormattedUID;
265 //Gray the names of offline accounts
266 NSDictionary *attributes = [NSDictionary dictionaryWithObject:[NSColor grayColor] forKey:NSForegroundColorAttributeName];
267 NSAttributedString *string = [[NSAttributedString alloc] initWithString:accountFormattedUID attributes:attributes];
268 result = [string autorelease];
271 } else if ([identifier isEqualToString:@"contact"]) {
272 AIListObject *contact = [contacts objectAtIndex:row];
273 result = [contact formattedUID];
280 * @brief Table view will display a cell
282 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
284 NSString *identifier = [tableColumn identifier];
286 AIListContact *exactContact;
289 account = [accounts objectAtIndex:row];
290 accountOnline = [account online];
292 exactContact = [contacts objectAtIndex:row];
294 //Disable cells for offline accounts
295 [cell setEnabled:accountOnline];
297 //Select active group
298 if ([identifier isEqualToString:@"group"]) {
302 if ((group = [[adium contactController] remoteGroupForContact:exactContact])) {
303 [cell selectItemWithRepresentedObject:group];
305 [cell selectItemAtIndex:0];
308 [cell setTitle:AILocalizedString(@"(Unavailable)",nil)];
315 * @brief Empty. This method is the target of our menus, and needed for menu validation.
317 - (void)selectGroup:(id)sender {};
320 * @brief Table view set object value
322 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
324 NSString *identifier = [tableColumn identifier];
325 AIAccount *account = [accounts objectAtIndex:row];
326 AIListContact *exactContact;
328 if ([identifier isEqualToString:@"group"]) {
329 NSMenu *menu = [[tableColumn dataCell] menu];
330 int menuIndex = [object intValue];
332 if (menuIndex >= 0 && menuIndex < [menu numberOfItems]) {
333 AIListGroup *group = [[menu itemAtIndex:menuIndex] representedObject];
335 if ([listObject isKindOfClass:[AIMetaContact class]]) {
336 //If we're dealing with a metaContact, make sure it's the topmost one
337 exactContact = [(AIMetaContact *)listObject parentContact];
340 //Retrieve an AIListContact on this account
341 exactContact = [[adium contactController] existingContactWithService:[listObject service]
343 UID:[listObject UID]];
347 if (group != [exactContact containingObject]) {
349 if (exactContact && ([exactContact containingObject] ||
350 [exactContact isKindOfClass:[AIMetaContact class]])) {
352 [[adium contactController] moveContact:exactContact intoObject:group];
357 exactContact = [[adium contactController] contactWithService:[listObject service]
359 UID:[listObject UID]];
362 [[adium contactController] addContacts:[NSArray arrayWithObject:exactContact]
368 //User selected not listed, so we'll remove that contact
369 [[adium contactController] removeListObjects:[NSArray arrayWithObject:exactContact]];
378 static NSComparisonResult compareContactsByTheirAccounts(id firstContact, id secondContact, void *context) {
379 NSComparisonResult result = [[firstContact account] compare:[secondContact account]];
380 //If they have the same account, sort the contacts themselves within the account.
381 if(result == NSOrderedSame) result = [firstContact compare:secondContact];