Performed [21405] and [21406], moving the contact list to Source, on adium-1.1
[adiumx.git] / Source / AIContactAccountsPane.m
blob63b1c846073277583e35b85b68346d760f729585
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
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;
34 @end
36 static NSComparisonResult compareContactsByTheirAccounts(id firstContact, id secondContact, void *context);
38 /*!
39  * @class AIContactAccountsPane
40  * @brief Accounts pane in the contact info window
41  *
42  * Provides a list of what accounts list a contact and in what group.
43  */
44 @implementation AIContactAccountsPane
46 //Preference pane properties
47 /*!
48  * @brief Category
49  */
50 - (AIContactInfoCategory)contactInfoCategory
52     return AIInfo_Accounts;
55 /*!
56  * @brief Nib name
57  */
58 - (NSString *)nibName
60     return @"ContactAccounts";
63 /*!
64  * @brief Configure the preference view
65  */
66 - (void)viewDidLoad
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
83                                                                          object:nil];
84         [self updateGroupList];
85         
86         //Observe account changes
87         [[adium notificationCenter] addObserver:self
88                                                                    selector:@selector(updateAccountList)
89                                                                            name:Account_ListChanged
90                                                                          object:nil];
91         [self updateAccountList];
92         
93         [tableView_accounts sizeToFit];
96 /*!
97  * @brief Preference view is closing
98  */
99 - (void)viewWillClose
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
109  */
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) {
117                                 //Add the column.
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;
129                         }
130                 } else if(contactsColumnIsInAccountsTableView) {
131                         //Remove the column.
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;
142                 }
144                 //Switch to the new list object.
145                 [listObject release];
146                 listObject = [inObject retain];
148                 //Rebuild our account list.
149                 [self updateAccountList];
150         }
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];
158         
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];
165         AIAccount *account;
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];
172                 }
173         }
174         
175         if (outAccounts) *outAccounts = [foundAccounts autorelease];
176         if (outContacts) *outContacts = [contactTimesN autorelease];
180  * @brief Update our list of accounts
181  */
182 - (void)updateAccountList
184         //Get the new accounts
185         [accounts release];
186         [contacts release];
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;
202                         
203                         [self getAccounts:&thisContactAccounts withContacts:&thisContactContacts forListContact:listContact];
204                         [workingAccounts addObjectsFromArray:thisContactAccounts];
205                         [workingContacts addObjectsFromArray:thisContactContacts];
206                 }
208                 accounts = [workingAccounts retain];
209                 contacts = [workingContacts retain];
211         } else if ([listObject isKindOfClass:[AIListContact class]]) {
212                 [self getAccounts:&accounts withContacts:&contacts forListContact:(AIListContact *)listObject];
213                 [accounts retain];
214                 [contacts retain];
215         } else {
216                 accounts = nil;
217                 contacts = nil;
218         }
219         
220         //Refresh our table
221         [tableView_accounts reloadData];
225  * @brief Update our list of groups
226  */
227 - (void)updateGroupList
229         //Get the new groups
230         NSMenu          *groupMenu = [[adium contactController] menuOfAllGroupsInGroup:nil withTarget:self];
231         [[[tableView_accounts tableColumnWithIdentifier:@"group"] dataCell] setMenu:groupMenu];
232         
233         //Refresh our table
234         [tableView_accounts reloadData];
238 //Table View Data Sources ----------------------------------------------------------------------------------------------
239 #pragma mark TableView Data Sources
241  * @brief Number of table view rows
242  */
243 - (int)numberOfRowsInTableView:(NSTableView *)tableView
245         return [accounts count];
249  * @brief Table view object value
250  */
251 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
253         id result = @"";
255         NSString                *identifier = [tableColumn identifier];
257         if ([identifier isEqualToString:@"account"]) {
258                 AIAccount               *account = [accounts objectAtIndex:row];
259                 NSString        *accountFormattedUID = [account formattedUID];
260                 
261                 if ([account online]) {
262                         result = accountFormattedUID;
263                         
264                 } else {
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];
269                 }
270                 
271         } else if ([identifier isEqualToString:@"contact"]) {
272                 AIListObject *contact = [contacts objectAtIndex:row];
273                 result = [contact formattedUID];
274         }
275         
276         return result;
280  * @brief Table view will display a cell
281  */
282 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
284         NSString                *identifier = [tableColumn identifier];
285         AIAccount               *account;
286         AIListContact   *exactContact;
287         BOOL                    accountOnline;
288                 
289         account =  [accounts objectAtIndex:row];
290         accountOnline = [account online];
292         exactContact = [contacts objectAtIndex:row];                            
294         //Disable cells for offline accounts
295         [cell setEnabled:accountOnline];
296         
297         //Select active group
298         if ([identifier isEqualToString:@"group"]) {
299                 if (accountOnline) {
300                         AIListGroup     *group;
301                         
302                         if ((group = [[adium contactController] remoteGroupForContact:exactContact])) {
303                                 [cell selectItemWithRepresentedObject:group];
304                         } else {
305                                 [cell selectItemAtIndex:0];                     
306                         }
307                 } else {
308                         [cell setTitle:AILocalizedString(@"(Unavailable)",nil)];
309                 }
310         }
311         
315  * @brief Empty.  This method is the target of our menus, and needed for menu validation.
316  */
317 - (void)selectGroup:(id)sender {};
320  * @brief Table view set object value
321  */
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;
327         
328         if ([identifier isEqualToString:@"group"]) {
329                 NSMenu          *menu = [[tableColumn dataCell] menu];
330                 int                     menuIndex = [object intValue];
331                 
332                 if (menuIndex >= 0 && menuIndex < [menu numberOfItems]) {
333                         AIListGroup     *group = [[menu itemAtIndex:menuIndex] representedObject];
334                         
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];
338                                 
339                         } else {
340                                 //Retrieve an AIListContact on this account
341                                 exactContact = [[adium contactController] existingContactWithService:[listObject service]
342                                                                                                                                                          account:account
343                                                                                                                                                                  UID:[listObject UID]];
344                         }
345                         
346                         if (group) {
347                                 if (group != [exactContact containingObject]) {
348                                         
349                                         if (exactContact && ([exactContact containingObject] ||
350                                                                                  [exactContact isKindOfClass:[AIMetaContact class]])) {
351                                                 //Move contact
352                                                 [[adium contactController] moveContact:exactContact intoObject:group];
353                                                 
354                                         } else {
355                                                 //Add contact
356                                                 if (!exactContact) {
357                                                         exactContact = [[adium contactController] contactWithService:[listObject service]
358                                                                                                                                                                  account:account
359                                                                                                                                                                          UID:[listObject UID]];
360                                                 }
361                                                 
362                                                 [[adium contactController] addContacts:[NSArray arrayWithObject:exactContact] 
363                                                                                                            toGroup:group];
364                                         }
365                                 }
366                         } else {
367                                 if (exactContact) {
368                                         //User selected not listed, so we'll remove that contact
369                                         [[adium contactController] removeListObjects:[NSArray arrayWithObject:exactContact]];
370                                 }
371                         }
372                 }
373         }
376 @end
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];
382         return result;