AIHyperlinks universal building
[adiumx.git] / Source / AIAccountListWindowController.m
blobb2a9474375e7fda692271fd5433ba8dd55fb46fa
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 "AIAccountController.h"
18 #import "AIAccountListWindowController.h"
19 #import "AIContactController.h"
20 #import "AIStatusController.h"
21 #import "AIEditAccountWindowController.h"
22 #import <AIUtilities/AIAutoScrollView.h>
23 #import <AIUtilities/AIImageTextCell.h>
24 #import <AIUtilities/AIVerticallyCenteredTextCell.h>
25 #import <AIUtilities/ESImageAdditions.h>
26 #import <Adium/AIAccount.h>
27 #import <Adium/AIListObject.h>
28 #import <Adium/AIServiceIcons.h>
29 #import <Adium/AIStatusIcons.h>
30 #import "KFTypeSelectTableView.h"
32 #define ACCOUNT_DRAG_TYPE                                       @"AIAccount"                            //ID for an account drag
34 #define NEW_ACCOUNT_DISPLAY_TEXT                AILocalizedString(@"<New Account>",nil)
36 @interface AIAccountListPreferences (PRIVATE)
37 - (void)configureAccountList;
38 - (void)accountListChanged:(NSNotification *)notification;
39 @end
41 /*!
42  * @class AIAccountListPreferences
43  * @brief Shows a list of accounts and provides for management of them
44  */
45 @implementation AIAccountListPreferences
47 /*!
48 * @brief Preference pane properties
49  */
50 - (PREFERENCE_CATEGORY)category{
51     return(AIPref_Accounts);
53 - (NSString *)label{
54     return(AILocalizedString(@"Accounts","Accounts preferences label"));
56 - (NSString *)nibName{
57     return(@"AccountListWindow");
60 /*!
61  * @brief Configure the view initially
62  */
63 - (void)viewDidLoad
65         //Configure the account list
66         [self configureAccountList];
67         [self updateAccountOverview];
68         
69         //Build the 'add account' menu
70         NSMenu  *serviceMenu = [[adium accountController] menuOfServicesWithTarget:self 
71                                                                                                                         activeServicesOnly:NO
72                                                                                                                            longDescription:YES
73                                                                                                                                                 format:AILocalizedString(@"%@",nil)];
74         [serviceMenu setAutoenablesItems:YES];
75         [button_newAccount setMenu:serviceMenu];
77         //Observe status icon pack changes
78         [[adium notificationCenter] addObserver:self
79                                                                    selector:@selector(statusIconsChanged:)
80                                                                            name:AIStatusIconSetDidChangeNotification
81                                                                          object:nil];
84 /*!
85  * @brief Perform actions before the view closes
86  */
87 - (void)viewWillClose
89         [[adium contactController] unregisterListObjectObserver:self];
90         [[adium notificationCenter] removeObserver:self];
93 /*!
94  * @brief Account status changed.
95  *
96  * Disable the service menu and user name field for connected accounts
97  */
98 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
100         if([inObject isKindOfClass:[AIAccount class]]){
101                 if([inModifiedKeys containsObject:@"Online"] ||
102                    [inModifiedKeys containsObject:@"Connecting"] ||
103                    [inModifiedKeys containsObject:@"Disconnecting"] ||
104                    [inModifiedKeys containsObject:@"ConnectionProgressString"] ||
105                    [inModifiedKeys containsObject:@"ConnectionProgressPercent"] ||
106                    [inModifiedKeys containsObject:@"IdleSince"] ||
107                    [inModifiedKeys containsObject:@"StatusState"]){
109                         //Refresh this account in our list
110                         int accountRow = [accountArray indexOfObject:inObject];
111                         if(accountRow >= 0 && accountRow < [accountArray count]){
112                                 [tableView_accountList setNeedsDisplayInRect:[tableView_accountList rectOfRow:accountRow]];
113                         }
114                         
115                         //Update our account overview
116                         [self updateAccountOverview];
117                 }
118         }
119     
120     return(nil);
123 //Actions --------------------------------------------------------------------------------------------------------------
124 #pragma mark Actions
126  * @brief Create a new account
128  * Called when a service type is selected from the Add menu
129  */
130 - (IBAction)selectServiceType:(id)sender
132     AIAccount   *account;
133         int                     accountRow;
134         
135         //Create the new account.  Our list will automatically update in response to the account being created
136         account = [[adium accountController] newAccountAtIndex:-1 forService:[sender representedObject]];
138         //And then, we can select and edit the new account
139         accountRow = [accountArray indexOfObject:account];
140         [tableView_accountList selectRow:accountRow byExtendingSelection:NO];
141         [tableView_accountList scrollRowToVisible:accountRow];
143         [AIEditAccountWindowController editAccount:account
144                                                                           onWindow:[[self view] window]
145                                                                   isNewAccount:YES];
149  * @brief Edit the currently selected account using <tt>AIEditAccountWindowController</tt>
150  */
151 - (IBAction)editAccount:(id)sender
153     int selectedRow = [tableView_accountList selectedRow];
154         if(selectedRow >= 0 && selectedRow < [accountArray count]){             
155                 [AIEditAccountWindowController editAccount:[accountArray objectAtIndex:selectedRow] 
156                                                                                   onWindow:[[self view] window]
157                                                                           isNewAccount:NO];
158     }
162  * @brief Delete the selected account
164  * Prompts for confirmation first
165  */
166 - (IBAction)deleteAccount:(id)sender
168     int index = [tableView_accountList selectedRow];
170     if(index != -1){            
171                 AIAccount       *targetAccount;
172                 NSString    *accountFormattedUID;
174                 targetAccount = [accountArray objectAtIndex:index];
175                 accountFormattedUID = [targetAccount formattedUID];
177                 //Confirm before deleting
178                 NSBeginAlertSheet(AILocalizedString(@"Delete Account",nil),
179                                                   AILocalizedString(@"Delete",nil),
180                                                   AILocalizedString(@"Cancel",nil),
181                                                   @"",[[self view] window], self, 
182                                                   @selector(deleteAccountSheetDidEnd:returnCode:contextInfo:), nil, targetAccount, 
183                                                   AILocalizedString(@"Delete the account %@?",nil), ([accountFormattedUID length] ? accountFormattedUID : NEW_ACCOUNT_DISPLAY_TEXT));
184         }
188  * @brief Finish account deletion
190  * Called when the sheet is closed
192  * @param returnCode NSAlertDefaultReturn indicates the account should be deleted
193  */
194 - (void)deleteAccountSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
196     AIAccount   *targetAccount = contextInfo;
197     int                 index;
198     
199     NSParameterAssert(targetAccount != nil);
200         NSParameterAssert([targetAccount isKindOfClass:[AIAccount class]]);
201     
202     if(returnCode == NSAlertDefaultReturn){
203         //Delete it
204         index = [accountArray indexOfObject:targetAccount];
205         [[adium accountController] deleteAccount:targetAccount save:YES];
206                 
207         //If it was the last row, select the new last row (by default the selection will jump to the top, which is bad)
208         if(index >= [accountArray count]){
209             index = [accountArray count]-1;
210             [tableView_accountList selectRow:index byExtendingSelection:NO];
211         }
212     }
216 //Account List ---------------------------------------------------------------------------------------------------------
217 #pragma mark Account List
219  * @brief Configure the account list table
220  */
221 - (void)configureAccountList
223     AIImageTextCell             *cell;
224         NSRect                          oldFrame, newFrame;
225         
226         //Setup our edit button, keeping its right side in the same location
227         oldFrame = [button_editAccount frame];
228         [button_editAccount setTitle:AILocalizedString(@"Edit",nil)];
229         [button_editAccount sizeToFit];
230         newFrame = [button_editAccount frame];
231         if(newFrame.size.width < oldFrame.size.width) newFrame.size.width = oldFrame.size.width;
232         newFrame.origin.x = oldFrame.origin.x + oldFrame.size.width - newFrame.size.width;
233         [button_editAccount setFrame:newFrame];
235         //Configure our table view
236         [tableView_accountList setTarget:self];
237         [tableView_accountList setDoubleAction:@selector(editAccount:)];
238         [tableView_accountList setIntercellSpacing:NSMakeSize(4,4)];
239     [scrollView_accountList setAutoHideScrollBar:YES];
241         //Enable dragging of accounts
242         [tableView_accountList registerForDraggedTypes:[NSArray arrayWithObjects:ACCOUNT_DRAG_TYPE,nil]];
243         
244     //Custom vertically-centered text cell for account names
245     cell = [[AIVerticallyCenteredTextCell alloc] init];
246     [cell setFont:[NSFont boldSystemFontOfSize:13]];
247     [[tableView_accountList tableColumnWithIdentifier:@"name"] setDataCell:cell];
248         [cell release];
250     cell = [[AIVerticallyCenteredTextCell alloc] init];
251     [cell setFont:[NSFont systemFontOfSize:13]];
252     [cell setAlignment:NSRightTextAlignment];
253     [[tableView_accountList tableColumnWithIdentifier:@"status"] setDataCell:cell];
254         [cell release];
255     
256         //Observe changes to the account list
257     [[adium notificationCenter] addObserver:self
258                                                                    selector:@selector(accountListChanged:) 
259                                                                            name:Account_ListChanged 
260                                                                          object:nil];
261         [self accountListChanged:nil];
262         
263         //Observe accounts so we can display accurate status
264     [[adium contactController] registerListObjectObserver:self];
268  * @brief Account list changed, refresh our table
269  */
270 - (void)accountListChanged:(NSNotification *)notification
272     //Update our list of accounts
273     [accountArray release];
274         accountArray = [[[adium accountController] accountArray] retain];
276         //Refresh the account table
277         [tableView_accountList reloadData];
278         [self updateControlAvailability];
279         [self updateAccountOverview];
283  * @brief Status icons changed, refresh our table
284  */
285 - (void)statusIconsChanged:(NSNotification *)notification
287         [tableView_accountList reloadData];
291  * @brief Update our account overview
293  * The overview indicates the total number of accounts and the number which are online.
294  */
295 - (void)updateAccountOverview
297         if([accountArray count] == 0){
298                 [textField_overview setStringValue:AILocalizedString(@"Click the + to add a new account","Instructions on how to add an account when none are present")];
300         }else{
301                 NSEnumerator    *enumerator = [accountArray objectEnumerator];
302                 AIAccount               *account;
303                 int                             online = 0;
304                 
305                 //Count online accounts
306                 while(account = [enumerator nextObject]){
307                         if([[account statusObjectForKey:@"Online"] boolValue]) online++;
308                 }
309                 
310                 [textField_overview setStringValue:[NSString stringWithFormat:AILocalizedString(@"%i accounts, %i online","Overview of total and online accounts"), [accountArray count], online]];
311         }
315  * @brief Update control availability based on list selection
316  */
317 - (void)updateControlAvailability
319         BOOL    selection = ([tableView_accountList selectedRow] != -1);
321         [button_editAccount setEnabled:selection];
322         [button_deleteAccount setEnabled:selection];
326 //Account List Table Delegate ------------------------------------------------------------------------------------------
327 #pragma mark Account List (Table Delegate)
329  * @brief Delete the selected row
330  */
331 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView
333     [self deleteAccount:nil];
337  * @brief Number of rows in the table
338  */
339 - (int)numberOfRowsInTableView:(NSTableView *)tableView
341         return([accountArray count]);
345  * @brief Table values
346  */
347 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
349         NSString        *identifier = [tableColumn identifier];
350         AIAccount       *account = [accountArray objectAtIndex:row];
351         
352         if([identifier isEqualToString:@"icon"]){
353                 return([[account userIcon] imageByScalingToSize:NSMakeSize(28,28)]);
355         }else if([identifier isEqualToString:@"service"]){
356                 return([[AIServiceIcons serviceIconForObject:account
357                                                                                                 type:AIServiceIconLarge
358                                                                                    direction:AIIconNormal] imageByScalingToSize:NSMakeSize(24,24)]);
360         }else if([identifier isEqualToString:@"name"]){
361                 return([[account formattedUID] length] ? [account formattedUID] : NEW_ACCOUNT_DISPLAY_TEXT);
362                 
363         }else if([identifier isEqualToString:@"status"]){
364                 NSString        *title;
365                 
366                 if([[account statusObjectForKey:@"Connecting"] boolValue]){
367                         title = AILocalizedString(@"Connecting",nil);
368                 }else if([[account statusObjectForKey:@"Disconnecting"] boolValue]){
369                         title = AILocalizedString(@"Disconnecting",nil);
370                 }else if([[account statusObjectForKey:@"Online"] boolValue]){
371                         title = AILocalizedString(@"Online",nil);
372                 }else{
373                         title = STATUS_DESCRIPTION_OFFLINE;
374                 }
376                 return(title);
377                 
378         }else if([identifier isEqualToString:@"statusicon"]){
380                 return([AIStatusIcons statusIconForListObject:account type:AIStatusIconList direction:AIIconNormal]);
381                 
382         }else if([identifier isEqualToString:@"enabled"]){
383                 return(nil);
384         }
385         
386         return(nil);
390  * @brief Configure cells before display
391  */
392 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
394         NSString        *identifier = [tableColumn identifier];
395         AIAccount       *account = [accountArray objectAtIndex:row];
396         
397         if([identifier isEqualToString:@"enabled"]){
398                 BOOL online = [[account preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue];
399                 [cell setState:(online ? NSOnState : NSOffState)];
401         }else if([identifier isEqualToString:@"status"]){
402                 [cell setEnabled:([[account statusObjectForKey:@"Connecting"] boolValue] ||
403                                                   [[account statusObjectForKey:@"Disconnecting"] boolValue] ||
404                                                   [[account statusObjectForKey:@"Online"] boolValue])];
405         }
406         
410  * @brief Handle a clicked active/inactive checkbox
412  * Checking the box both takes the account online and sets it to autoconnect. Unchecking it does the opposite.
413  */
414 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
416         if([[tableColumn identifier] isEqualToString:@"enabled"]){
417                 [[accountArray objectAtIndex:row] setPreference:object forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
418         }
422  * @brief Drag start
423  */
424 - (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
426     tempDragAccount = [accountArray objectAtIndex:[[rows objectAtIndex:0] intValue]];
427         
428     [pboard declareTypes:[NSArray arrayWithObject:ACCOUNT_DRAG_TYPE] owner:self];
429     [pboard setString:@"Account" forType:ACCOUNT_DRAG_TYPE];
430     
431     return(YES);
435  * @brief Drag validate
436  */
437 - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
439     if(op == NSTableViewDropAbove && row != -1){
440         return(NSDragOperationPrivate);
441     }else{
442         return(NSDragOperationNone);
443     }
447  * @brief Drag complete
448  */
449 - (BOOL)tableView:(NSTableView*)tv acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)op
451     NSString    *avaliableType = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:ACCOUNT_DRAG_TYPE]];
452         
453     if([avaliableType isEqualToString:@"AIAccount"]){
454         int     newIndex;
455         
456         //Select the moved account
457         newIndex = [[adium accountController] moveAccount:tempDragAccount toIndex:row];
458         [tableView_accountList selectRow:newIndex byExtendingSelection:NO];
459                 
460         return(YES);
461     }else{
462         return(NO);
463     }
467  * @brief Selection change
468  */
469 - (void)tableViewSelectionDidChange:(NSNotification *)notification
471         [self updateControlAvailability];
475  * @brief Set up KFTypeSelectTableView
477  * Only search the "name" column.
478  */
479 - (void)configureTypeSelectTableView:(KFTypeSelectTableView *)tableView
481     [tableView setSearchColumnIdentifiers:[NSSet setWithObject:@"name"]];
484 @end