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 <Adium/AIContactControllerProtocol.h>
19 #import <Adium/AIInterfaceControllerProtocol.h>
20 #import "AINewContactWindowController.h"
21 #import "AINewGroupWindowController.h"
22 #import "OWABSearchWindowController.h"
23 #import "ESAddressBookIntegrationPlugin.h"
24 #import <AIUtilities/AIMenuAdditions.h>
25 #import <AIUtilities/AIPopUpButtonAdditions.h>
26 #import <AIUtilities/AIStringAdditions.h>
27 #import <Adium/AIAccount.h>
28 #import <Adium/AIListContact.h>
29 #import <Adium/AIListGroup.h>
30 #import <Adium/AILocalizationTextField.h>
31 #import <Adium/AIService.h>
32 #import <Adium/AIServiceIcons.h>
33 #import <Adium/AIServiceMenu.h>
34 #import <AddressBook/ABPerson.h>
36 #define ADD_CONTACT_PROMPT_NIB @"AddContact"
37 #define DEFAULT_GROUP_NAME AILocalizedString(@"Contacts",nil)
39 @interface AINewContactWindowController (PRIVATE)
40 - (id)initWithWindowNibName:(NSString *)windowNibName contactName:(NSString *)inName service:(AIService *)inService account:(AIAccount *)inAccount;
41 - (void)buildGroupMenu;
42 - (void)buildContactTypeMenu;
43 - (void)configureForCurrentServiceType;
44 - (void)ensureValidContactTypeSelection;
45 - (void)updateAccountList;
46 - (void)_setServiceType:(AIService *)inService;
47 - (void)selectServiceType:(id)sender;
51 * @class AINewContactWindowController
52 * @brief Window controller for adding a new contact
54 @implementation AINewContactWindowController
57 * @brief Prompt for adding a new contact.
59 * @param parentWindow Window on which to show the prompt as a sheet. Pass nil for a panel prompt.
60 * @param inName Initial value for the contact name field
61 * @param inService <tt>AIService</tt> for determining the initial service type selection
62 * @param inAccount If non-nil, the one AIAccount which should be initially enabled for adding this contact
64 + (void)promptForNewContactOnWindow:(NSWindow *)parentWindow name:(NSString *)inName service:(AIService *)inService account:(AIAccount *)inAccount
66 AINewContactWindowController *newContactWindow;
68 newContactWindow = [[self alloc] initWithWindowNibName:ADD_CONTACT_PROMPT_NIB contactName:inName service:inService account:inAccount];
71 [parentWindow makeKeyAndOrderFront:nil];
73 [NSApp beginSheet:[newContactWindow window]
74 modalForWindow:parentWindow
75 modalDelegate:newContactWindow
76 didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
79 [newContactWindow showWindow:nil];
80 [[newContactWindow window] makeKeyAndOrderFront:nil];
88 - (id)initWithWindowNibName:(NSString *)windowNibName contactName:(NSString *)inName service:(AIService *)inService account:(AIAccount *)inAccount
90 self = [super initWithWindowNibName:windowNibName];
92 service = [inService retain];
93 initialAccount = [inAccount retain];
94 contactName = [inName retain];
106 [contactName release];
108 [initialAccount release];
110 [checkedAccounts release];
112 [[NSNotificationCenter defaultCenter] removeObserver:self];
118 * @brief Setup the window before it is displayed
120 - (void)windowDidLoad
122 [[self window] center];
125 [[self window] setTitle:AILocalizedString(@"Add Contact",nil)];
126 [textField_type setLocalizedString:AILocalizedString(@"Contact Type:","Contact type service dropdown label in Add Contact")];
127 [textField_alias setLocalizedString:AILocalizedString(@"Alias:",nil)];
128 [textField_inGroup setLocalizedString:AILocalizedString(@"In Group:",nil)];
129 [textField_addToAccounts setLocalizedString:AILocalizedString(@"On Accounts:",nil)];
131 [textField_searchInAB setAlwaysMoveRightAnchoredWindow:YES];
132 [textField_searchInAB setLocalizedString:AILocalizedString(@"Search In Address Book",nil)];
134 [button_add setLocalizedString:AILocalizedString(@"Add",nil)];
135 [button_cancel setLocalizedString:AILocalizedString(@"Cancel",nil)];
137 //Configure the rest of the window
138 [self buildGroupMenu];
139 [self buildContactTypeMenu];
140 [self configureForCurrentServiceType];
141 if (contactName) [textField_contactName setStringValue:contactName];
143 //Observe account list and status changes
144 [[adium notificationCenter] addObserver:self
145 selector:@selector(accountListChanged:)
146 name:Account_ListChanged
148 [[adium contactController] registerListObjectObserver:self];
152 * @brief Window is closing
154 - (void)windowWillClose:(id)sender
156 [super windowWillClose:sender];
157 [[adium contactController] unregisterListObjectObserver:self];
158 [[adium notificationCenter] removeObserver:self];
162 * @brief Called as the user list edit sheet closes, dismisses the sheet
164 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
166 [sheet orderOut:nil];
172 - (IBAction)cancel:(id)sender
174 [self closeWindow:nil];
178 * @brief Perform the addition of the contact
180 - (IBAction)addContact:(id)sender
182 NSString *UID = [service normalizeUID:[textField_contactName stringValue] removeIgnoredCharacters:YES];
183 NSString *alias = [textField_contactAlias stringValue];
184 NSEnumerator *enumerator;
187 NSMutableArray *contactArray = [NSMutableArray array];
190 group = ([popUp_targetGroup numberOfItems] ?
191 [[popUp_targetGroup selectedItem] representedObject] :
194 if (!group) group = [[adium contactController] groupWithUID:DEFAULT_GROUP_NAME];
196 //Add contact to our accounts
197 enumerator = [accounts objectEnumerator];
198 while ((account = [enumerator nextObject])) {
199 if ([account contactListEditable] &&
200 [[account preferenceForKey:KEY_ADD_CONTACT_TO group:PREF_GROUP_ADD_CONTACT] boolValue]) {
201 AIListContact *contact = [[adium contactController] contactWithService:service
206 if (alias && [alias length]) [contact setDisplayName:alias];
207 [contactArray addObject:contact];
209 //Remember the ABPerson's unique ID associated with this contact
211 [contact setPreference:uniqueID forKey:KEY_AB_UNIQUE_ID group:PREF_GROUP_ADDRESSBOOK];
213 //Force this contact to show up on the user's list for a little bit, even if it is offline
214 //Otherwise they have no good feedback that a contact was added at all.
215 [contact setStatusObject:[NSNumber numberWithBool:YES] forKey:@"New Object" notify:YES];
216 [contact setStatusObject:[NSNumber numberWithBool:NO] forKey:@"New Object" afterDelay:10.0];
221 //Add them to our local group
222 [[adium contactController] addContacts:contactArray toGroup:group];
224 [self closeWindow:nil];
228 * @brief Display a sheet for searching a person within the AB database.
230 - (IBAction)searchInAB:(id)sender
232 OWABSearchWindowController *abSearchWindow;
233 abSearchWindow = [[OWABSearchWindowController promptForNewPersonSearchOnWindow:[self window]
234 initialService:service] retain];
235 [abSearchWindow setDelegate:self];
239 * @brief Callback from OWABSearchWindowController
241 - (void)absearchWindowControllerDidSelectPerson:(OWABSearchWindowController *)controller
243 ABPerson *selectedPerson = [controller selectedPerson];
245 if (selectedPerson) {
246 NSString *selectedScreenName = [controller selectedScreenName];
247 NSString *selectedName = [controller selectedName];
248 AIService *selectedService = [controller selectedService];
250 if (selectedScreenName)
251 [textField_contactName setStringValue:[service normalizeUID:selectedScreenName removeIgnoredCharacters:YES]];
254 [textField_contactAlias setStringValue:selectedName];
256 if (selectedService) {
257 [popUp_contactType selectItemWithTitle:[selectedService shortDescription]];
258 [self selectServiceType:nil];
261 uniqueID = [[selectedPerson uniqueId] retain];
265 [controller release];
269 //Service Type ---------------------------------------------------------------------------------------------------------
270 #pragma mark Service Type
272 * @brief Build and configure the menu of contact service types
274 - (void)buildContactTypeMenu
277 [popUp_contactType setMenu:[AIServiceMenu menuOfServicesWithTarget:self
278 activeServicesOnly:YES
282 //Ensure our selection is still valid
283 [self ensureValidContactTypeSelection];
287 * @brief Ensures that the selected contact type is valid, selecting another if it isn't
289 - (void)ensureValidContactTypeSelection
291 int serviceIndex = -1;
293 //Force our menu to update.. it needs to be correctly validated for the code below to work
294 [[popUp_contactType menu] update];
296 //Find the menu item for our current service
297 if (service) serviceIndex = [popUp_contactType indexOfItemWithRepresentedObject:service];
299 //If our service is not available we'll have to pick another one
300 if (service && (serviceIndex == -1 || ![[popUp_contactType itemAtIndex:serviceIndex] isEnabled])) {
301 [self _setServiceType:nil];
304 //If we don't have a service, pick the first available one
306 [self _setServiceType:[[[popUp_contactType menu] firstEnabledMenuItem] representedObject]];
309 //Update our menu and window for the current service
310 [popUp_contactType selectItemWithRepresentedObject:service];
311 [self configureForCurrentServiceType];
315 * @brief Configure any service-dependent controls in our window for the current service
317 - (void)configureForCurrentServiceType
319 NSString *userNameLabel = [service contactUserNameLabel];
321 //Update the service icon
322 [imageView_service setImage:[AIServiceIcons serviceIconForService:service
323 type:AIServiceIconLarge
324 direction:AIIconNormal]];
325 [textField_contactNameLabel setLocalizedString:[(userNameLabel ? userNameLabel :
326 AILocalizedString(@"Contact ID",nil)) stringByAppendingString:AILocalizedString(@":", "Colon which will be appended after a label such as 'User Name', before an input field")]];
328 //And the list of accounts
329 [self updateAccountList];
333 * @brief User selected a new service type
335 - (void)selectServiceType:(id)sender
337 [self _setServiceType:[[popUp_contactType selectedItem] representedObject]];
338 [self configureForCurrentServiceType];
342 * @brief Set the current service type
344 - (void)_setServiceType:(AIService *)inService
346 if (inService != service) {
348 service = [inService retain];
353 * Validate a menu item
355 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
357 NSEnumerator *enumerator = [[[adium accountController] accountsCompatibleWithService:[menuItem representedObject]] objectEnumerator];
360 while ((account = [enumerator nextObject])) {
361 if ([account contactListEditable]) return YES;
368 * @brief Update our contact type menu when user accounts change
370 - (void)accountListChanged:(NSNotification *)notification
372 [self buildContactTypeMenu];
376 * @brief Update our contact type when account availability changes
378 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
380 if ([inObject isKindOfClass:[AIAccount class]]) {
381 [self ensureValidContactTypeSelection];
388 //Add to Group ---------------------------------------------------------------------------------------------------------
389 #pragma mark Add to Group
391 * @brief Build the menu of available destination groups
393 - (void)buildGroupMenu
395 AIListObject *selectedObject;
398 menu = [[adium contactController] menuOfAllGroupsInGroup:nil withTarget:self];
400 //Add a default group name to the menu if there are no groups listed
401 if ([menu numberOfItems] == 0) {
402 [menu addItemWithTitle:DEFAULT_GROUP_NAME
404 action:@selector(selectGroup:)
408 [menu addItem:[NSMenuItem separatorItem]];
409 [menu addItemWithTitle:[AILocalizedString(@"New Group",nil) stringByAppendingEllipsis]
411 action:@selector(newGroup:)
414 //Select the group of the currently selected object on the contact list
415 selectedObject = [[adium interfaceController] selectedListObject];
416 while (selectedObject && ![selectedObject isKindOfClass:[AIListGroup class]]) {
417 selectedObject = [selectedObject containingObject];
420 [popUp_targetGroup setMenu:menu];
422 //If there was no selected group, just select the first item
423 if (selectedObject) {
424 if (![popUp_targetGroup selectItemWithRepresentedObject:selectedObject]) {
425 [popUp_targetGroup selectItemAtIndex:0];
429 [popUp_targetGroup selectItemAtIndex:0];
434 * @brief Prompt the user to add a new group immediately
436 - (void)newGroup:(id)sender
438 AINewGroupWindowController *newGroupWindowController;
440 newGroupWindowController = [AINewGroupWindowController promptForNewGroupOnWindow:[self window]];
442 //Observe for the New Group window to close
443 [[adium notificationCenter] addObserver:self
444 selector:@selector(newGroupDidEnd:)
445 name:@"NewGroupWindowControllerDidEnd"
446 object:[newGroupWindowController window]];
449 - (void)newGroupDidEnd:(NSNotification *)inNotification
451 NSWindow *window = [inNotification object];
453 if ([[window windowController] isKindOfClass:[AINewGroupWindowController class]]) {
454 NSString *newGroupUID = [[window windowController] newGroupUID];
455 AIListGroup *group = [[adium contactController] existingGroupWithUID:newGroupUID];
457 //Rebuild the group menu
458 [self buildGroupMenu];
460 /* Select the new group if it exists; otherwise select the first group (so we don't still have New Group... selected).
461 * If the user cancelled, group will be nil since the group doesn't exist.
463 if (![popUp_targetGroup selectItemWithRepresentedObject:group]) {
464 [popUp_targetGroup selectItemAtIndex:0];
467 [[self window] performSelector:@selector(makeKeyAndOrderFront:)
473 [[adium notificationCenter] removeObserver:self
474 name:@"NewGroupWindowControllerDidEnd"
478 //Add to Accounts ------------------------------------------------------------------------------------------------------
479 #pragma mark Add to Accounts
481 * @brief Update the accounts list
483 - (void)updateAccountList
486 accounts = [[[adium accountController] accountsCompatibleWithService:service] retain];
488 [checkedAccounts release];
489 checkedAccounts = [[NSMutableSet alloc] init];
491 //Select accounts by default
492 if (initialAccount && [accounts containsObject:initialAccount]) {
493 [checkedAccounts addObject:initialAccount];
496 NSEnumerator *enumerator;
497 AIAccount *anAccount;
499 enumerator = [accounts objectEnumerator];
500 while ((anAccount = [enumerator nextObject])) {
501 if ([[anAccount preferenceForKey:KEY_ADD_CONTACT_TO group:PREF_GROUP_ADD_CONTACT] boolValue])
502 [checkedAccounts addObject:anAccount];
506 [tableView_accounts reloadData];
510 * @brief Rows in the accounts table view
512 - (int)numberOfRowsInTableView:(NSTableView *)tableView
514 return [accounts count];
518 * @brief Object value for columns in the accounts table view
520 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
522 NSString *identifier = [tableColumn identifier];
524 if ([identifier isEqualToString:@"check"]) {
525 return ([[accounts objectAtIndex:row] contactListEditable] ?
526 [NSNumber numberWithBool:[checkedAccounts containsObject:[accounts objectAtIndex:row]]] :
527 [NSNumber numberWithBool:NO]);
529 } else if ([identifier isEqualToString:@"account"]) {
530 return [[accounts objectAtIndex:row] explicitFormattedUID];
539 * @brief Will display cell
541 * Enable/disable account checkbox as appropriate
543 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
545 NSString *identifier = [tableColumn identifier];
547 if ([identifier isEqualToString:@"check"]) {
548 [cell setEnabled:[[accounts objectAtIndex:row] contactListEditable]];
553 * @brief Set the enabled/disabled state for an account in the account list
555 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
557 NSString *identifier = [tableColumn identifier];
559 if ([identifier isEqualToString:@"check"]) {
560 [[accounts objectAtIndex:row] setPreference:[NSNumber numberWithBool:[object boolValue]]
561 forKey:KEY_ADD_CONTACT_TO
562 group:PREF_GROUP_ADD_CONTACT];
563 if ([object boolValue]) {
564 [checkedAccounts addObject:[accounts objectAtIndex:row]];
566 [checkedAccounts removeObject:[accounts objectAtIndex:row]];
572 * @brief Empty selector called by the group popUp menu
574 - (void)selectGroup:(id)sender