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 <Adium/AIAccountControllerProtocol.h>
18 #import "AIAccountListPreferences.h"
19 #import <Adium/AIContactControllerProtocol.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/AITableViewAdditions.h>
26 #import <AIUtilities/AIImageAdditions.h>
27 #import <AIUtilities/AIMutableStringAdditions.h>
28 #import <AIUtilities/AIStringAdditions.h>
29 #import <AIUtilities/AIDateFormatterAdditions.h>
30 #import <AIUtilities/AIMenuAdditions.h>
31 #import <Adium/AIAccount.h>
32 #import <Adium/AIAccountMenu.h>
33 #import <Adium/AIListObject.h>
34 #import <Adium/AIService.h>
35 #import <Adium/AIStatusMenu.h>
36 #import <Adium/AIServiceIcons.h>
37 #import <Adium/AIServiceMenu.h>
38 #import <Adium/AIStatusIcons.h>
39 #import <AIUtilities/AIAttributedStringAdditions.h>
40 #import "KFTypeSelectTableView.h"
42 #define MINIMUM_ROW_HEIGHT 34
43 #define MINIMUM_CELL_SPACING 4
45 #define ACCOUNT_DRAG_TYPE @"AIAccount" //ID for an account drag
47 #define NEW_ACCOUNT_DISPLAY_TEXT AILocalizedString(@"<New Account>", "Placeholder displayed as the name of a new account")
49 @interface AIAccountListPreferences (PRIVATE)
50 - (void)configureAccountList;
51 - (void)accountListChanged:(NSNotification *)notification;
53 - (void)calculateHeightForRow:(int)row;
54 - (void)calculateAllHeights;
56 - (void)updateReconnectTime:(NSTimer *)timer;
60 * @class AIAccountListPreferences
61 * @brief Shows a list of accounts and provides for management of them
63 @implementation AIAccountListPreferences
66 * @brief Preference pane properties
68 - (NSString *)paneIdentifier
72 - (NSString *)paneName{
73 return AILocalizedString(@"Accounts","Accounts preferences label");
75 - (NSString *)nibName{
76 return @"AccountListPreferences";
80 return [NSImage imageNamed:@"pref-accounts" forClass:[self class]];
84 * @brief Configure the view initially
88 //Configure the account list
89 [self configureAccountList];
90 [self updateAccountOverview];
92 //Build the 'add account' menu of each available service
93 NSMenu *serviceMenu = [AIServiceMenu menuOfServicesWithTarget:self
96 format:AILocalizedString(@"%@",nil)];
97 [serviceMenu setAutoenablesItems:YES];
99 //Indent each item in the service menu one level
100 NSEnumerator *enumerator = [[serviceMenu itemArray] objectEnumerator];
101 NSMenuItem *menuItem;
102 while ((menuItem = [enumerator nextObject])) {
103 [menuItem setIndentationLevel:[menuItem indentationLevel]+1];
106 //Add a label to the top of the menu to clarify why we're showing this list of services
107 [serviceMenu insertItemWithTitle:AILocalizedString(@"Add an account for:",nil)
113 [button_newAccount setMenu:serviceMenu];
115 //Set ourselves up for Account Menus
116 accountMenu_options = [[AIAccountMenu accountMenuWithDelegate:self
117 submenuType:AIAccountOptionsSubmenu
118 showTitleVerbs:NO] retain];
120 accountMenu_status = [[AIAccountMenu accountMenuWithDelegate:self
121 submenuType:AIAccountStatusSubmenu
122 showTitleVerbs:NO] retain];
124 //Observe status icon pack changes
125 [[adium notificationCenter] addObserver:self
126 selector:@selector(iconPackDidChange:)
127 name:AIStatusIconSetDidChangeNotification
130 //Observe service icon pack changes
131 [[adium notificationCenter] addObserver:self
132 selector:@selector(iconPackDidChange:)
133 name:AIServiceIconSetDidChangeNotification
136 // Start updating the reconnect time if an account is already reconnecting.
137 [self updateReconnectTime:nil];
141 * @brief Perform actions before the view closes
143 - (void)viewWillClose
145 [[adium contactController] unregisterListObjectObserver:self];
146 [[adium notificationCenter] removeObserver:self];
148 [accountArray release]; accountArray = nil;
149 [requiredHeightDict release]; requiredHeightDict = nil;
150 [accountMenu_options release]; accountMenu_options = nil;
151 [accountMenu_status release]; accountMenu_status = nil;
153 // Cancel our auto-refreshing reconnect countdown.
154 [reconnectTimeUpdater invalidate];
155 [reconnectTimeUpdater release]; reconnectTimeUpdater = nil;
160 [accountArray release];
165 * @brief Account status changed.
167 * Disable the service menu and user name field for connected accounts
169 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
171 if ([inObject isKindOfClass:[AIAccount class]]) {
172 if ([inModifiedKeys containsObject:@"Online"] ||
173 [inModifiedKeys containsObject:@"Enabled"] ||
174 [inModifiedKeys containsObject:@"Connecting"] ||
175 [inModifiedKeys containsObject:@"Waiting to Reconnect"] ||
176 [inModifiedKeys containsObject:@"Disconnecting"] ||
177 [inModifiedKeys containsObject:@"ConnectionProgressString"] ||
178 [inModifiedKeys containsObject:@"ConnectionProgressPercent"] ||
179 [inModifiedKeys containsObject:@"Waiting for Network"] ||
180 [inModifiedKeys containsObject:@"IdleSince"] ||
181 [inModifiedKeys containsObject:@"StatusState"]) {
183 //Refresh this account in our list
184 int accountRow = [accountArray indexOfObject:inObject];
185 if (accountRow >= 0 && accountRow < [accountArray count]) {
186 [tableView_accountList setNeedsDisplayInRect:[tableView_accountList rectOfRow:accountRow]];
187 // Update the height of the row.
188 [self calculateHeightForRow:accountRow];
189 [tableView_accountList noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndex:accountRow]];
191 // If necessary, update our reconnection display time.
192 if (!reconnectTimeUpdater) {
193 [self updateReconnectTime:nil];
197 //Update our account overview
198 [self updateAccountOverview];
205 //Actions --------------------------------------------------------------------------------------------------------------
208 * @brief Create a new account
210 * Called when a service type is selected from the Add menu
212 - (IBAction)selectServiceType:(id)sender
214 AIService *service = [sender representedObject];
215 AIAccount *account = [[adium accountController] createAccountWithService:service
216 UID:[service defaultUserName]];
218 [AIEditAccountWindowController editAccount:account
219 onWindow:[[self view] window]
220 notifyingTarget:self];
223 - (void)editAccount:(AIAccount *)inAccount
225 [AIEditAccountWindowController editAccount:inAccount
226 onWindow:[[self view] window]
227 notifyingTarget:self];
231 * @brief Edit the currently selected account using <tt>AIEditAccountWindowController</tt>
233 - (IBAction)editSelectedAccount:(id)sender
235 int selectedRow = [tableView_accountList selectedRow];
236 if ([tableView_accountList numberOfSelectedRows] == 1 && selectedRow >= 0 && selectedRow < [accountArray count]) {
237 [self editAccount:[accountArray objectAtIndex:selectedRow]];
242 * @brief Handle a double click within our table
244 * Ignore double clicks on the enable/disable checkbox
246 - (void)doubleClickInTableView:(id)sender
248 if (!(NSPointInRect([tableView_accountList convertPoint:[[NSApp currentEvent] locationInWindow] fromView:nil],
249 [tableView_accountList rectOfColumn:[tableView_accountList columnWithIdentifier:@"enabled"]]))) {
250 [self editSelectedAccount:sender];
255 * @brief Editing of an account completed
257 - (void)editAccountSheetDidEndForAccount:(AIAccount *)inAccount withSuccess:(BOOL)successful
259 BOOL existingAccount = ([[[adium accountController] accounts] containsObject:inAccount]);
261 if (!existingAccount && successful) {
262 //New accounts need to be added to our account list once they're configured
263 [[adium accountController] addAccount:inAccount];
265 //Scroll the new account visible so that the user can see we added it
266 [tableView_accountList scrollRowToVisible:[accountArray indexOfObject:inAccount]];
268 //Put new accounts online by default
269 [inAccount setPreference:[NSNumber numberWithBool:YES] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
270 } else if (existingAccount && successful && [inAccount enabled]) {
271 //If the user edited an account that is "reconnecting" or "connecting", disconnect it and try to reconnect.
272 if ([[inAccount statusObjectForKey:@"Connecting"] boolValue] ||
273 [inAccount statusObjectForKey:@"Waiting to Reconnect"]) {
274 // Stop connecting or stop waiting to reconnect.
275 [inAccount setShouldBeOnline:NO];
277 [inAccount setShouldBeOnline:YES];
283 * @brief Delete the selected account
285 * Prompts for confirmation first
287 - (IBAction)deleteAccount:(id)sender
289 int index = [tableView_accountList selectedRow];
291 if ([tableView_accountList numberOfSelectedRows] == 1 && index >= 0 && index < [accountArray count])
292 [[[adium accountController] deleteAccount:[accountArray objectAtIndex:index]] beginSheetModalForWindow:[[self view] window]];
296 * @brief Toggles an account online or offline.
298 - (void)toggleShouldBeOnline:(id)sender
300 AIAccount *account = [sender representedObject];
301 if (![account enabled])
302 [account setEnabled:YES];
304 [account toggleOnline];
307 #pragma mark AIAccountMenu Delegates
310 * @brief AIAccountMenu delieate method
312 - (void)accountMenu:(AIAccountMenu *)inAccountMenu didRebuildMenuItems:(NSArray *)menuItems {
317 * @brief AIAccountMenu delegate method -- this allows disabled items to have menus.
319 - (BOOL)accountMenu:(AIAccountMenu *)inAccountMenu shouldIncludeAccount:(AIAccount *)inAccount
324 //Account List ---------------------------------------------------------------------------------------------------------
325 #pragma mark Account List
327 * @brief Configure the account list table
329 - (void)configureAccountList
331 AIImageTextCell *cell;
332 NSRect oldFrame, newFrame;
334 //Setup our edit button, keeping its right side in the same location
335 oldFrame = [button_editAccount frame];
336 [button_editAccount setTitle:AILocalizedStringFromTable(@"Edit", @"Buttons", "Verb 'edit' on a button")];
337 [button_editAccount sizeToFit];
338 newFrame = [button_editAccount frame];
339 if (newFrame.size.width < oldFrame.size.width) newFrame.size.width = oldFrame.size.width;
340 newFrame.origin.x = oldFrame.origin.x + oldFrame.size.width - newFrame.size.width;
341 [button_editAccount setFrame:newFrame];
343 //Configure our table view
344 [tableView_accountList setTarget:self];
345 [tableView_accountList setDoubleAction:@selector(doubleClickInTableView:)];
346 [tableView_accountList setIntercellSpacing:NSMakeSize(MINIMUM_CELL_SPACING, MINIMUM_CELL_SPACING)];
348 //Enable dragging of accounts
349 [tableView_accountList registerForDraggedTypes:[NSArray arrayWithObjects:ACCOUNT_DRAG_TYPE,nil]];
351 //Custom vertically-centered text cell for account names
352 cell = [[AIImageTextCell alloc] init];
353 [cell setFont:[NSFont boldSystemFontOfSize:13]];
354 [cell setDrawsImageAfterMainString:YES];
355 [[tableView_accountList tableColumnWithIdentifier:@"name"] setDataCell:cell];
356 [cell setLineBreakMode:NSLineBreakByWordWrapping];
359 cell = [[AIImageTextCell alloc] init];
360 [cell setFont:[NSFont systemFontOfSize:13]];
361 [cell setAlignment:NSRightTextAlignment];
362 [cell setLineBreakMode:NSLineBreakByWordWrapping];
363 [[tableView_accountList tableColumnWithIdentifier:@"status"] setDataCell:cell];
366 [tableView_accountList sizeToFit];
368 //Observe changes to the account list
369 [[adium notificationCenter] addObserver:self
370 selector:@selector(accountListChanged:)
371 name:Account_ListChanged
373 [self accountListChanged:nil];
375 //Observe accounts so we can display accurate status
376 [[adium contactController] registerListObjectObserver:self];
380 * @brief Account list changed, refresh our table
382 - (void)accountListChanged:(NSNotification *)notification
384 //Update our list of accounts
385 [accountArray release];
386 accountArray = [[[adium accountController] accounts] retain];
388 //Refresh the account table
389 [tableView_accountList reloadData];
390 [self updateControlAvailability];
391 [self updateAccountOverview];
392 [self calculateAllHeights];
396 * @brief Returns the status menu associated with several rows
398 - (NSMenu *)menuForRowIndexes:(NSIndexSet *)indexes
400 NSMenu *statusMenu = nil, *optionsMenu = [[[NSMenu alloc] init] autorelease];
401 NSMenuItem *statusMenuItem = nil;
402 NSArray *accounts = [accountArray objectsAtIndexes:indexes];
404 NSEnumerator *enumerator = [accounts objectEnumerator];
405 BOOL atLeastOneDisabledAccount = NO, atLeastOneOfflineAccount = NO;
407 // Check the accounts' enabled/disabled and online/offline status.
408 while ((account = [enumerator nextObject])) {
409 if (![account enabled])
410 atLeastOneDisabledAccount = YES;
412 if (![account online] && ![[account statusObjectForKey:@"Connecting"] boolValue])
413 atLeastOneOfflineAccount = YES;
415 if (atLeastOneOfflineAccount && atLeastOneDisabledAccount)
419 statusMenuItem = [optionsMenu addItemWithTitle:AILocalizedString(@"Set Status", "Used in the context menu for the accounts list for the sub menu to set status in.")
424 statusMenu = [AIStatusMenu staticStatusStatesMenuNotifyingTarget:self
425 selector:@selector(updateAccountsForStatus:)];
426 [statusMenuItem setSubmenu:statusMenu];
428 //If any accounts are offline, present the option to connect them all.
429 if (atLeastOneOfflineAccount) {
430 [optionsMenu addItemWithTitle:AILocalizedString(@"Connect",nil)
432 action:@selector(toggleOnlineForAccounts:)
434 representedObject:[NSDictionary dictionaryWithObjectsAndKeys:accounts,@"Accounts",
435 [NSNumber numberWithBool:YES],@"Connect",nil]];
437 [optionsMenu addItemWithTitle:AILocalizedString(@"Disconnect",nil)
439 action:@selector(toggleOnlineForAccounts:)
441 representedObject:[NSDictionary dictionaryWithObjectsAndKeys:accounts,@"Accounts",
442 [NSNumber numberWithBool:NO],@"Connect",nil]];
444 [optionsMenu addItem:[NSMenuItem separatorItem]];
446 // If any accounts are disable,d show the option to enable them.
447 if (atLeastOneDisabledAccount) {
448 [optionsMenu addItemWithTitle:AILocalizedString(@"Enable",nil)
450 action:@selector(toggleEnabledForAccounts:)
452 representedObject:[NSDictionary dictionaryWithObjectsAndKeys:accounts,@"Accounts",
453 [NSNumber numberWithBool:YES],@"Enable",nil]];
456 [optionsMenu addItemWithTitle:AILocalizedString(@"Disable",nil)
458 action:@selector(toggleEnabledForAccounts:)
460 representedObject:[NSDictionary dictionaryWithObjectsAndKeys:accounts,@"Accounts",
461 [NSNumber numberWithBool:NO],@"Enable",nil]];
467 * @brief Callback for the Connect/Disconnect menu item in a multiple account selection
469 - (void)toggleOnlineForAccounts:(id)sender
471 NSDictionary *dict = [sender representedObject];
472 NSEnumerator *enumerator = [[dict objectForKey:@"Accounts"] objectEnumerator];
473 BOOL connect = [[dict objectForKey:@"Connect"] boolValue];
476 while ((account = [enumerator nextObject])) {
477 if (![account enabled] && connect)
478 [account setEnabled:YES];
479 [account setShouldBeOnline:connect];
484 * @brief Callback for the Enable/Disable menu item in a multiple account selection
486 - (void)toggleEnabledForAccounts:(id)sender
488 NSDictionary *dict = [sender representedObject];
489 NSEnumerator *enumerator = [[dict objectForKey:@"Accounts"] objectEnumerator];
490 BOOL enable = [[dict objectForKey:@"Enable"] boolValue];
493 while ((account = [enumerator nextObject])) {
494 [account setEnabled:enable];
499 * @brief Callback for the Set Status menu item in a multiple-account selection
501 - (void)updateAccountsForStatus:(id)sender
503 NSEnumerator *enumerator = [[accountArray objectsAtIndexes:[tableView_accountList selectedRowIndexes]] objectEnumerator];
504 AIStatus *status = [[sender representedObject] objectForKey:@"AIStatus"];
507 while ((account = [enumerator nextObject])) {
508 [account setStatusState:status];
510 //Enable the account if it isn't currently enabled and this isn't an offline status
511 if (![account enabled] && [status statusType] != AIOfflineStatusType) {
512 [account setEnabled:YES];
518 * @brief Callback for the Copy Error Message menu item for an account
520 - (void)copyStatusMessage:(id)sender
522 NSPasteboard *generalPasteboard = [NSPasteboard generalPasteboard];
524 [generalPasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType]
526 [generalPasteboard setString:[self statusMessageForAccount:[sender representedObject]]
527 forType:NSStringPboardType];
531 * @brief Returns the status menu associated with a particular row
533 - (NSMenu *)menuForRow:(int)row
535 if (row >= 0 && row < [accountArray count]) {
536 AIAccount *account = [accountArray objectAtIndex:row];
537 NSMenu *optionsMenu = [[[NSMenu alloc] init] autorelease];
538 NSMenu *accountOptionsMenu = [[accountMenu_options menuItemForAccount:account] submenu];
540 NSMenuItem *statusMenuItem = [optionsMenu addItemWithTitle:AILocalizedString(@"Set Status", "Used in the context menu for the accounts list for the sub menu to set status in.")
545 //We can't put the submenu into our menu directly or otherwise modify the accountMenu_status, as we may want to use it again
546 [statusMenuItem setSubmenu:[[[[accountMenu_status menuItemForAccount:account] submenu] copy] autorelease]];
548 if (![account online] && ![[account statusObjectForKey:@"Connecting"] boolValue] && [self statusMessageForAccount:account]) {
549 [optionsMenu addItemWithTitle:AILocalizedString(@"Copy Error Message","Menu Item for the context menu of an account in the accounts list")
551 action:@selector(copyStatusMessage:)
553 representedObject:account];
556 if ([[statusMenuItem submenu] numberOfItems] >= 2) {
557 //Remove the 'Disable' item
558 [[statusMenuItem submenu] removeItemAtIndex:([[statusMenuItem submenu] numberOfItems] - 1)];
560 //And remove the separator above it
561 [[statusMenuItem submenu] removeItemAtIndex:([[statusMenuItem submenu] numberOfItems] - 1)];
564 //Connect or disconnect the account. Enabling a disabled account will connect it, so this is only valid for non-disabled accounts.
565 //Only online & connecting can be "Disconnected"; those offline or waiting to reconnect can be "Connected"
566 [optionsMenu addItemWithTitle:(([account online] || [[account statusObjectForKey:@"Connecting"] boolValue]) ?
567 AILocalizedString(@"Disconnect",nil) :
568 AILocalizedString(@"Connect",nil))
570 action:@selector(toggleShouldBeOnline:)
572 representedObject:account];
574 //Add a separator if we have any items shown so far
575 [optionsMenu addItem:[NSMenuItem separatorItem]];
577 //Add account options
578 NSMenuItem *menuItem;
579 NSEnumerator *enumerator = [[accountOptionsMenu itemArray] objectEnumerator];
580 while ((menuItem = [enumerator nextObject])) {
581 //Use copies of the menu items rather than moving the actual items, as we may want to use them again
582 [optionsMenu addItem:[[menuItem copy] autorelease]];
592 * @brief Updates reconnecting time where necessary.
594 - (void)updateReconnectTime:(NSTimer *)timer
597 BOOL moreUpdatesNeeded = NO;
599 for (accountRow = 0; accountRow < [accountArray count]; accountRow++) {
600 if ([[accountArray objectAtIndex:accountRow] statusObjectForKey:@"Waiting to Reconnect"] != nil) {
601 [tableView_accountList setNeedsDisplayInRect:[tableView_accountList rectOfRow:accountRow]];
602 moreUpdatesNeeded = YES;
606 if (moreUpdatesNeeded && reconnectTimeUpdater == nil) {
607 reconnectTimeUpdater = [[NSTimer scheduledTimerWithTimeInterval:1.0
609 selector:@selector(updateReconnectTime:)
611 repeats:YES] retain];
612 } else if (!moreUpdatesNeeded && reconnectTimeUpdater != nil) {
613 [reconnectTimeUpdater invalidate];
614 [reconnectTimeUpdater release]; reconnectTimeUpdater = nil;
619 * @brief Status icons changed, refresh our table
621 - (void)iconPackDidChange:(NSNotification *)notification
623 [tableView_accountList reloadData];
627 * @brief Update our account overview
629 * The overview indicates the total number of accounts and the number which are online.
631 - (void)updateAccountOverview
633 NSString *accountOverview;
634 int accountArrayCount = [accountArray count];
636 if (accountArrayCount == 0) {
637 accountOverview = AILocalizedString(@"Click the + to add a new account","Instructions on how to add an account when none are present");
640 NSEnumerator *enumerator = [accountArray objectEnumerator];
642 int online = 0, enabled = 0;
644 //Count online accounts
645 while ((account = [enumerator nextObject])) {
646 if ([account online]) online++;
647 if ([account enabled]) enabled++;
651 if ((accountArrayCount == enabled) ||
652 (online == enabled)){
653 accountOverview = [NSString stringWithFormat:AILocalizedString(@"%i accounts, %i online","Overview of total and online accounts"),
657 accountOverview = [NSString stringWithFormat:AILocalizedString(@"%i accounts, %i enabled, %i online","Overview of total, enabled, and online accounts"),
663 accountOverview = AILocalizedString(@"Check a box to enable an account","Instructions for enabling an account");
667 [textField_overview setStringValue:accountOverview];
671 * @brief Update control availability based on list selection
673 - (void)updateControlAvailability
675 BOOL selection = ([tableView_accountList numberOfSelectedRows] == 1 && [tableView_accountList selectedRow] != -1);
677 [button_editAccount setEnabled:selection];
678 [button_deleteAccount setEnabled:selection];
682 * @brief Returns the status string associated with the account
684 * Returns a connection status if connecting, or an error if disconnected with an error
686 - (NSString *)statusMessageForAccount:(AIAccount *)account
688 NSString *statusMessage = nil;
690 if ([account statusObjectForKey:@"ConnectionProgressString"] && [[account statusObjectForKey:@"Connecting"] boolValue]) {
691 // Connection status if we're currently connecting, with the percent at the end
692 statusMessage = [[account statusObjectForKey:@"ConnectionProgressString"] stringByAppendingFormat:@" (%2.f%%)", [[account statusObjectForKey:@"ConnectionProgressPercent"] floatValue]*100.0];
693 } else if ([account lastDisconnectionError] && ![[account statusObjectForKey:@"Online"] boolValue] && ![[account statusObjectForKey:@"Connecting"] boolValue]) {
694 // If there's an error and we're not online and not connecting
695 NSMutableString *returnedMessage = [[[account lastDisconnectionError] mutableCopy] autorelease];
697 // Replace the LibPurple error prefixes
698 [returnedMessage replaceOccurrencesOfString:@"Could not establish a connection with the server:\n"
700 options:NSLiteralSearch
701 range:NSMakeRange(0, [returnedMessage length])];
702 [returnedMessage replaceOccurrencesOfString:@"Connection error from Notification server:\n"
704 options:NSLiteralSearch
705 range:NSMakeRange(0, [returnedMessage length])];
706 [returnedMessage replaceOccurrencesOfString:@"Could not connect to authentication server:\n"
708 options:NSLiteralSearch
709 range:NSMakeRange(0, [returnedMessage length])];
711 // Remove newlines from the error message, replace them with spaces
712 [returnedMessage replaceOccurrencesOfString:@"\n"
714 options:NSLiteralSearch
715 range:NSMakeRange(0, [returnedMessage length])];
717 statusMessage = [NSString stringWithFormat:@"%@: %@", AILocalizedString(@"Error", "Prefix to error messages in the Account List."), returnedMessage];
720 return statusMessage;
724 * @brief Calculates the height of a given row and stores it
726 - (void)calculateHeightForRow:(int)row
728 // Make sure this is a valid row.
729 if (row < 0 || row >= [accountArray count]) {
733 AIAccount *account = [accountArray objectAtIndex:row];
734 float necessaryHeight = MINIMUM_ROW_HEIGHT;
736 // If there's a status message, let's try size to fit it.
737 if ([self statusMessageForAccount:account]) {
738 NSTableColumn *tableColumn = [tableView_accountList tableColumnWithIdentifier:@"name"];
740 [self tableView:tableView_accountList willDisplayCell:[tableColumn dataCell] forTableColumn:tableColumn row:row];
742 // Main string (account name)
743 NSDictionary *mainStringAttributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont boldSystemFontOfSize:13], NSFontAttributeName, nil];
744 NSAttributedString *mainTitle = [[NSAttributedString alloc] initWithString:([[account formattedUID] length] ? [account formattedUID] : NEW_ACCOUNT_DISPLAY_TEXT)
745 attributes:mainStringAttributes];
747 // Substring (the status message)
748 NSDictionary *subStringAttributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:10], NSFontAttributeName, nil];
749 NSAttributedString *subStringTitle = [[NSAttributedString alloc] initWithString:[self statusMessageForAccount:account]
750 attributes:subStringAttributes];
752 // Both heights combined, with spacing in-between
753 float combinedHeight = [mainTitle heightWithWidth:[tableColumn width]] + [subStringTitle heightWithWidth:[tableColumn width]] + MINIMUM_CELL_SPACING;
755 // Make sure we're not down-sizing
756 if (combinedHeight > necessaryHeight) {
757 necessaryHeight = combinedHeight;
760 [subStringTitle release];
764 // Cache the height value
765 [requiredHeightDict setObject:[NSNumber numberWithFloat:necessaryHeight]
766 forKey:[NSNumber numberWithInt:row]];
770 * @brief Calculates the height of all rows
772 - (void)calculateAllHeights
776 [requiredHeightDict release]; requiredHeightDict = [[NSMutableDictionary alloc] init];
778 for (accountNumber = 0; accountNumber < [accountArray count]; accountNumber++) {
779 [self calculateHeightForRow:accountNumber];
784 //Account List Table Delegate ------------------------------------------------------------------------------------------
785 #pragma mark Account List (Table Delegate)
787 * @brief Delete the selected row
789 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView
791 [self deleteAccount:nil];
795 * @brief Number of rows in the table
797 - (int)numberOfRowsInTableView:(NSTableView *)tableView
799 return [accountArray count];
803 * @brief Table values
805 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
807 if (row < 0 || row >= [accountArray count]) {
811 NSString *identifier = [tableColumn identifier];
812 AIAccount *account = [accountArray objectAtIndex:row];
814 if ([identifier isEqualToString:@"service"]) {
815 return [[AIServiceIcons serviceIconForObject:account
816 type:AIServiceIconLarge
817 direction:AIIconNormal] imageByScalingToSize:NSMakeSize(MINIMUM_ROW_HEIGHT-2, MINIMUM_ROW_HEIGHT-2)
818 fraction:([account enabled] ?
822 } else if ([identifier isEqualToString:@"name"]) {
823 return [[account formattedUID] length] ? [account formattedUID] : NEW_ACCOUNT_DISPLAY_TEXT;
825 } else if ([identifier isEqualToString:@"status"]) {
828 if ([account enabled]) {
829 if ([[account statusObjectForKey:@"Connecting"] boolValue]) {
830 title = AILocalizedString(@"Connecting",nil);
831 } else if ([[account statusObjectForKey:@"Disconnecting"] boolValue]) {
832 title = AILocalizedString(@"Disconnecting",nil);
833 } else if ([[account statusObjectForKey:@"Online"] boolValue]) {
834 title = AILocalizedString(@"Online",nil);
835 } else if ([account statusObjectForKey:@"Waiting to Reconnect"]) {
836 title = AILocalizedString(@"Reconnecting", @"Used when the account will perform an automatic reconnection after a certain period of time.");
837 } else if ([[account statusObjectForKey:@"Waiting for Network"] boolValue]) {
838 title = AILocalizedString(@"Network Offline", @"Used when the account will connect once the network returns.");
840 title = [[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_OFFLINE];
844 title = AILocalizedString(@"Disabled",nil);
849 } else if ([identifier isEqualToString:@"statusicon"]) {
851 return [AIStatusIcons statusIconForListObject:account type:AIStatusIconList direction:AIIconNormal];
853 } else if ([identifier isEqualToString:@"enabled"]) {
861 * @brief Configure the height of each account for error messages if necessary
863 - (float)tableView:(NSTableView *)tableView heightOfRow:(int)row
865 // We should probably have this value cached.
866 float necessaryHeight = MINIMUM_ROW_HEIGHT;
868 NSNumber *cachedHeight = [requiredHeightDict objectForKey:[NSNumber numberWithInt:row]];
870 necessaryHeight = [cachedHeight floatValue];
873 return necessaryHeight;
877 * @brief Configure cells before display
879 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
881 // Make sure this row actually exists
882 if (row < 0 || row >= [accountArray count]) {
886 NSString *identifier = [tableColumn identifier];
887 AIAccount *account = [accountArray objectAtIndex:row];
889 if ([identifier isEqualToString:@"enabled"]) {
890 [cell setState:([account enabled] ? NSOnState : NSOffState)];
892 } else if ([identifier isEqualToString:@"name"]) {
893 if ([account encrypted]) {
894 [cell setImage:[NSImage imageForSSL]];
899 [cell setImageTextPadding:MINIMUM_CELL_SPACING/2.0];
901 [cell setEnabled:[account enabled]];
903 // Update the subString with our current status message (if it exists);
904 [cell setSubString:[self statusMessageForAccount:account]];
906 } else if ([identifier isEqualToString:@"status"]) {
907 if ([account enabled] && ![[account statusObjectForKey:@"Connecting"] boolValue] && [account statusObjectForKey:@"Waiting to Reconnect"]) {
908 NSString *format = [NSDateFormatter stringForTimeInterval:[[account statusObjectForKey:@"Waiting to Reconnect"] timeIntervalSinceNow]
913 [cell setSubString:[NSString stringWithFormat:AILocalizedString(@"...in %@", @"The amount of time until a reconnect occurs. %@ is the formatted time remaining."), format]];
915 [cell setSubString:nil];
918 [cell setEnabled:([[account statusObjectForKey:@"Connecting"] boolValue] ||
919 [account statusObjectForKey:@"Waiting to Reconnect"] ||
920 [[account statusObjectForKey:@"Disconnecting"] boolValue] ||
921 [[account statusObjectForKey:@"Online"] boolValue])];
927 * @brief Handle a clicked active/inactive checkbox
929 * Checking the box both takes the account online and sets it to autoconnect. Unchecking it does the opposite.
931 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
933 if (row >= 0 && row < [accountArray count] && [[tableColumn identifier] isEqualToString:@"enabled"]) {
934 [[accountArray objectAtIndex:row] setEnabled:[(NSNumber *)object boolValue]];
941 - (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet*)rows toPasteboard:(NSPasteboard*)pboard
943 tempDragAccounts = [accountArray objectsAtIndexes:rows];
945 [pboard declareTypes:[NSArray arrayWithObject:ACCOUNT_DRAG_TYPE] owner:self];
946 [pboard setString:@"Account" forType:ACCOUNT_DRAG_TYPE];
952 * @brief Drag validate
954 - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
956 if (op == NSTableViewDropAbove && row != -1) {
957 return NSDragOperationPrivate;
959 return NSDragOperationNone;
964 * @brief Drag complete
966 - (BOOL)tableView:(NSTableView*)tv acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)op
968 NSString *avaliableType = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:ACCOUNT_DRAG_TYPE]];
970 if ([avaliableType isEqualToString:@"AIAccount"]) {
971 NSEnumerator *enumerator;
974 //Indexes are shifting as we're doing this, so we have to iterate in the right order
975 //If we're moving accounts to an earlier point in the list, we've got to insert backwards
976 if ([accountArray indexOfObject:[tempDragAccounts objectAtIndex:0]] >= row)
977 enumerator = [tempDragAccounts reverseObjectEnumerator];
978 else //If we're inserting into a later part of the list, we've got to insert forwards
979 enumerator = [tempDragAccounts objectEnumerator];
981 [tableView_accountList deselectAll:nil];
983 while ((account = [enumerator nextObject])) {
984 [[adium accountController] moveAccount:account toIndex:row];
987 //Re-select our now-moved accounts
988 [tableView_accountList selectRowIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange([accountArray indexOfObject:[tempDragAccounts objectAtIndex:0]], [tempDragAccounts count])]
989 byExtendingSelection:NO];
998 * @brief Selection change
1000 - (void)tableViewSelectionDidChange:(NSNotification *)notification
1002 [self updateControlAvailability];
1005 - (NSMenu *)tableView:(NSTableView *)inTableView menuForEvent:(NSEvent *)theEvent
1007 NSIndexSet *selectedIndexes = [inTableView selectedRowIndexes];
1008 int mouseRow = [inTableView rowAtPoint:[inTableView convertPoint:[theEvent locationInWindow] toView:nil]];
1010 //Multiple rows selected where the right-clicked row is in the selection
1011 if ([selectedIndexes count] > 1 && [selectedIndexes containsIndex:mouseRow]) {
1012 //Display a multi-selection menu
1013 return [self menuForRowIndexes:selectedIndexes];
1015 // Otherwise, select our new row and provide a menu for it.
1016 [inTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:mouseRow] byExtendingSelection:NO];
1018 // Return our delegate's menu for this row.
1019 return [self menuForRow:mouseRow];
1024 * @brief Set up KFTypeSelectTableView
1026 * Only search the "name" column.
1028 - (void)configureTypeSelectTableView:(KFTypeSelectTableView *)tableView
1030 [tableView setSearchColumnIdentifiers:[NSSet setWithObject:@"name"]];