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 AILogWithSignature(@"checkedAccounts is %@", checkedAccounts);
198 //Add contact to our accounts
199 enumerator = [accounts objectEnumerator];
200 while ((account = [enumerator nextObject])) {
201 if ([account contactListEditable] &&
202 [[account preferenceForKey:KEY_ADD_CONTACT_TO group:PREF_GROUP_ADD_CONTACT] boolValue]) {
203 AILogWithSignature(@"Accont %@ was checked per its preference; we'll add %@ to it", account, UID);
204 AIListContact *contact = [[adium contactController] contactWithService:service
209 if (alias && [alias length]) [contact setDisplayName:alias];
210 [contactArray addObject:contact];
212 //Remember the ABPerson's unique ID associated with this contact
214 [contact setPreference:uniqueID forKey:KEY_AB_UNIQUE_ID group:PREF_GROUP_ADDRESSBOOK];
216 //Force this contact to show up on the user's list for a little bit, even if it is offline
217 //Otherwise they have no good feedback that a contact was added at all.
218 [contact setStatusObject:[NSNumber numberWithBool:YES] forKey:@"New Object" notify:YES];
219 [contact setStatusObject:[NSNumber numberWithBool:NO] forKey:@"New Object" afterDelay:10.0];
224 //Add them to our local group
225 AILogWithSignature(@"Adding %@ to %@", contactArray, group);
226 [[adium contactController] addContacts:contactArray toGroup:group];
228 [self closeWindow:nil];
232 * @brief Display a sheet for searching a person within the AB database.
234 - (IBAction)searchInAB:(id)sender
236 OWABSearchWindowController *abSearchWindow;
237 abSearchWindow = [[OWABSearchWindowController promptForNewPersonSearchOnWindow:[self window]
238 initialService:service] retain];
239 [abSearchWindow setDelegate:self];
243 * @brief Callback from OWABSearchWindowController
245 - (void)absearchWindowControllerDidSelectPerson:(OWABSearchWindowController *)controller
247 ABPerson *selectedPerson = [controller selectedPerson];
249 if (selectedPerson) {
250 NSString *selectedScreenName = [controller selectedScreenName];
251 NSString *selectedName = [controller selectedName];
252 AIService *selectedService = [controller selectedService];
254 if (selectedScreenName)
255 [textField_contactName setStringValue:[service normalizeUID:selectedScreenName removeIgnoredCharacters:YES]];
258 [textField_contactAlias setStringValue:selectedName];
260 if (selectedService) {
261 [popUp_contactType selectItemWithTitle:[selectedService shortDescription]];
262 [self selectServiceType:nil];
265 uniqueID = [[selectedPerson uniqueId] retain];
269 [controller release];
273 //Service Type ---------------------------------------------------------------------------------------------------------
274 #pragma mark Service Type
276 * @brief Build and configure the menu of contact service types
278 - (void)buildContactTypeMenu
281 [popUp_contactType setMenu:[AIServiceMenu menuOfServicesWithTarget:self
282 activeServicesOnly:YES
286 //Ensure our selection is still valid
287 [self ensureValidContactTypeSelection];
291 * @brief Ensures that the selected contact type is valid, selecting another if it isn't
293 - (void)ensureValidContactTypeSelection
295 int serviceIndex = -1;
297 //Force our menu to update.. it needs to be correctly validated for the code below to work
298 [[popUp_contactType menu] update];
300 //Find the menu item for our current service
301 if (service) serviceIndex = [popUp_contactType indexOfItemWithRepresentedObject:service];
303 //If our service is not available we'll have to pick another one
304 if (service && (serviceIndex == -1 || ![[popUp_contactType itemAtIndex:serviceIndex] isEnabled])) {
305 [self _setServiceType:nil];
308 //If we don't have a service, pick the first available one
310 [self _setServiceType:[[[popUp_contactType menu] firstEnabledMenuItem] representedObject]];
313 //Update our menu and window for the current service
314 [popUp_contactType selectItemWithRepresentedObject:service];
315 [self configureForCurrentServiceType];
319 * @brief Configure any service-dependent controls in our window for the current service
321 - (void)configureForCurrentServiceType
323 NSString *userNameLabel = [service contactUserNameLabel];
325 //Update the service icon
326 [imageView_service setImage:[AIServiceIcons serviceIconForService:service
327 type:AIServiceIconLarge
328 direction:AIIconNormal]];
329 [textField_contactNameLabel setLocalizedString:[(userNameLabel ? userNameLabel :
330 AILocalizedString(@"Contact ID",nil)) stringByAppendingString:AILocalizedString(@":", "Colon which will be appended after a label such as 'User Name', before an input field")]];
332 //And the list of accounts
333 [self updateAccountList];
337 * @brief User selected a new service type
339 - (void)selectServiceType:(id)sender
341 [self _setServiceType:[[popUp_contactType selectedItem] representedObject]];
342 [self configureForCurrentServiceType];
346 * @brief Set the current service type
348 - (void)_setServiceType:(AIService *)inService
350 if (inService != service) {
352 service = [inService retain];
357 * Validate a menu item
359 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
361 NSEnumerator *enumerator = [[[adium accountController] accountsCompatibleWithService:[menuItem representedObject]] objectEnumerator];
364 while ((account = [enumerator nextObject])) {
365 if ([account contactListEditable]) return YES;
372 * @brief Update our contact type menu when user accounts change
374 - (void)accountListChanged:(NSNotification *)notification
376 [self buildContactTypeMenu];
380 * @brief Update our contact type when account availability changes
382 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
384 if ([inObject isKindOfClass:[AIAccount class]]) {
385 [self ensureValidContactTypeSelection];
392 //Add to Group ---------------------------------------------------------------------------------------------------------
393 #pragma mark Add to Group
395 * @brief Build the menu of available destination groups
397 - (void)buildGroupMenu
399 AIListObject *selectedObject;
402 menu = [[adium contactController] menuOfAllGroupsInGroup:nil withTarget:self];
404 //Add a default group name to the menu if there are no groups listed
405 if ([menu numberOfItems] == 0) {
406 [menu addItemWithTitle:DEFAULT_GROUP_NAME
408 action:@selector(selectGroup:)
412 [menu addItem:[NSMenuItem separatorItem]];
413 [menu addItemWithTitle:[AILocalizedString(@"New Group",nil) stringByAppendingEllipsis]
415 action:@selector(newGroup:)
418 //Select the group of the currently selected object on the contact list
419 selectedObject = [[adium interfaceController] selectedListObject];
420 while (selectedObject && ![selectedObject isKindOfClass:[AIListGroup class]]) {
421 selectedObject = [selectedObject containingObject];
424 [popUp_targetGroup setMenu:menu];
426 //If there was no selected group, just select the first item
427 if (selectedObject) {
428 if (![popUp_targetGroup selectItemWithRepresentedObject:selectedObject]) {
429 [popUp_targetGroup selectItemAtIndex:0];
433 [popUp_targetGroup selectItemAtIndex:0];
438 * @brief Prompt the user to add a new group immediately
440 - (void)newGroup:(id)sender
442 AINewGroupWindowController *newGroupWindowController;
444 newGroupWindowController = [AINewGroupWindowController promptForNewGroupOnWindow:[self window]];
446 //Observe for the New Group window to close
447 [[adium notificationCenter] addObserver:self
448 selector:@selector(newGroupDidEnd:)
449 name:@"NewGroupWindowControllerDidEnd"
450 object:[newGroupWindowController window]];
453 - (void)newGroupDidEnd:(NSNotification *)inNotification
455 NSWindow *window = [inNotification object];
457 if ([[window windowController] isKindOfClass:[AINewGroupWindowController class]]) {
458 NSString *newGroupUID = [[window windowController] newGroupUID];
459 AIListGroup *group = [[adium contactController] existingGroupWithUID:newGroupUID];
461 //Rebuild the group menu
462 [self buildGroupMenu];
464 /* Select the new group if it exists; otherwise select the first group (so we don't still have New Group... selected).
465 * If the user cancelled, group will be nil since the group doesn't exist.
467 if (![popUp_targetGroup selectItemWithRepresentedObject:group]) {
468 [popUp_targetGroup selectItemAtIndex:0];
471 [[self window] performSelector:@selector(makeKeyAndOrderFront:)
477 [[adium notificationCenter] removeObserver:self
478 name:@"NewGroupWindowControllerDidEnd"
482 //Add to Accounts ------------------------------------------------------------------------------------------------------
483 #pragma mark Add to Accounts
485 * @brief Update the accounts list
487 - (void)updateAccountList
490 accounts = [[[adium accountController] accountsCompatibleWithService:service] retain];
492 [checkedAccounts release];
493 checkedAccounts = [[NSMutableSet alloc] init];
495 //Select accounts by default
496 if (initialAccount && [accounts containsObject:initialAccount]) {
497 [checkedAccounts addObject:initialAccount];
500 NSEnumerator *enumerator;
501 AIAccount *anAccount;
503 enumerator = [accounts objectEnumerator];
504 while ((anAccount = [enumerator nextObject])) {
505 if ([[anAccount preferenceForKey:KEY_ADD_CONTACT_TO group:PREF_GROUP_ADD_CONTACT] boolValue])
506 [checkedAccounts addObject:anAccount];
510 [tableView_accounts reloadData];
514 * @brief Rows in the accounts table view
516 - (int)numberOfRowsInTableView:(NSTableView *)tableView
518 return [accounts count];
522 * @brief Object value for columns in the accounts table view
524 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
526 NSString *identifier = [tableColumn identifier];
528 if ([identifier isEqualToString:@"check"]) {
529 return ([[accounts objectAtIndex:row] contactListEditable] ?
530 [NSNumber numberWithBool:[checkedAccounts containsObject:[accounts objectAtIndex:row]]] :
531 [NSNumber numberWithBool:NO]);
533 } else if ([identifier isEqualToString:@"account"]) {
534 return [[accounts objectAtIndex:row] explicitFormattedUID];
543 * @brief Will display cell
545 * Enable/disable account checkbox as appropriate
547 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
549 NSString *identifier = [tableColumn identifier];
551 if ([identifier isEqualToString:@"check"]) {
552 [cell setEnabled:[[accounts objectAtIndex:row] contactListEditable]];
557 * @brief Set the enabled/disabled state for an account in the account list
559 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
561 NSString *identifier = [tableColumn identifier];
563 if ([identifier isEqualToString:@"check"]) {
564 [[accounts objectAtIndex:row] setPreference:[NSNumber numberWithBool:[object boolValue]]
565 forKey:KEY_ADD_CONTACT_TO
566 group:PREF_GROUP_ADD_CONTACT];
567 if ([object boolValue]) {
568 [checkedAccounts addObject:[accounts objectAtIndex:row]];
570 [checkedAccounts removeObject:[accounts objectAtIndex:row]];
576 * @brief Empty selector called by the group popUp menu
578 - (void)selectGroup:(id)sender