2 // OWABSearchWindowController.m
5 // Created by Ofri Wolfus on 19/07/05.
6 // Copyright 2006 The Adium Team. All rights reserved.
9 #import "OWABSearchWindowController.h"
10 #import <Adium/AIAccountControllerProtocol.h>
12 #import <Adium/AIService.h>
13 #import <Adium/AIServiceMenu.h>
14 #import <Adium/AIServiceIcons.h>
15 #import "ESAddressBookIntegrationPlugin.h"
16 #import <AIUtilities/AIMenuAdditions.h>
17 #import <AIUtilities/AIPopUpButtonAdditions.h>
18 #import <AIUtilities/AIImageViewWithImagePicker.h>
19 #import <AddressBook/AddressBook.h>
20 #import <AddressBook/ABPeoplePickerView.h>
21 #import <AddressBook/ABPerson.h>
23 #define AB_SEARCH_NIB @"ABSearch"
25 @interface OWABSearchWindowController (private)
26 - (id)initWithWindowNibName:(NSString *)windowNibName initialService:(AIService *)inService;
27 - (void)_configurePeoplePicker;
28 - (void)_setCarryingWindow:(NSWindow *)inWindow;
29 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
30 - (NSString *)propertyFromService:(AIService *)service;
31 - (void)buildContactTypeMenu;
32 - (void)ensureValidContactTypeSelection;
33 - (void)configureForCurrentServiceType;
34 - (void)selectServiceType:(id)sender;
35 - (void)_setService:(AIService *)inService;
36 - (void)_setPerson:(ABPerson *)inPerson;
37 - (void)_setScreenName:(NSString *)inName;
42 * @class OWABSearchWindowController
43 * @brief Window controller for searching people in the Address Book database.
45 @implementation OWABSearchWindowController
47 static ABAddressBook *sharedAddressBook = nil;
50 * @brief Prompt for searching a person within the AB database.
52 * @param parentWindow Window on which to show the prompt as a sheet. Pass nil for a panel prompt.
54 + (id)promptForNewPersonSearchOnWindow:(NSWindow *)parentWindow initialService:(AIService *)inService
56 OWABSearchWindowController *newABSearchWindow;
58 newABSearchWindow = [[self alloc] initWithWindowNibName:AB_SEARCH_NIB initialService:inService];
61 [NSApp beginSheet:[newABSearchWindow window]
62 modalForWindow:parentWindow
63 modalDelegate:newABSearchWindow
64 didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
66 [newABSearchWindow _setCarryingWindow:parentWindow];
68 [newABSearchWindow showWindow:nil];
71 return [newABSearchWindow autorelease];
77 - (id)initWithWindowNibName:(NSString *)windowNibName initialService:(AIService *)inService
79 self = [super initWithWindowNibName:windowNibName];
87 service = [inService retain];
89 if (!sharedAddressBook)
90 sharedAddressBook = [[ABAddressBook sharedAddressBook] retain];
101 [self setDelegate:nil];
103 [screenName release];
105 [carryingWindow release];
106 [contactImage release];
107 [sharedAddressBook release]; sharedAddressBook = nil;
112 * @brief Setup the window before it is displayed
114 - (void)windowDidLoad
116 [[self window] center];
120 [[self window] setTitle:AILocalizedString(@"Search In Address Book", nil)];
121 [selectButton setLocalizedString:AILocalizedString(@"Select Buddy", nil)];
122 [cancelButton setLocalizedString:AILocalizedString(@"Cancel", nil)];
123 [newPersonButton setLocalizedString:AILocalizedString(@"New Person", nil)];
125 [newContactPanel setTitle:AILocalizedString(@"Create New Person", nil)];
126 [label_mainTitle setLocalizedString:AILocalizedString(@"Enter the contact's type and screen name/number:", nil)];
127 [label_contactType setLocalizedString:AILocalizedString(@"Contact Type:", "Contact type service dropdown label in Add Contact")];
128 [label_secondaryTitle setLocalizedString:AILocalizedString(@"Address Book Information (optional):", nil)];
129 [label_firstName setLocalizedString:AILocalizedString(@"First Name:", nil)];
130 [label_lastName setLocalizedString:AILocalizedString(@"Last Name:", nil)];
131 [label_nickname setLocalizedString:AILocalizedString(@"Nickname:", nil)];
132 [label_email setLocalizedString:AILocalizedString(@"Email:", nil)];
133 [label_contactIcon setLocalizedString:AILocalizedString(@"Contact Icon", "Contact icon label in create new AB person")];
134 [addContactButton setLocalizedString:AILocalizedString(@"Add Contact", nil)];
135 [addContactCancelButton setLocalizedString:AILocalizedString(@"Cancel", nil)];
137 [imageView_contactIcon setMaxSize:NSMakeSize(256, 256)];
139 [self _configurePeoplePicker];
141 [[self window] selectKeyViewFollowingView:peoplePicker];
145 * @brief Setup our ABPeoplePickerView
147 - (void)_configurePeoplePicker
149 NSTextField *accessoryView = [[[NSTextField alloc] init] autorelease];
150 NSEnumerator *servicesEnumerator;
154 //Create a small explanation text
155 [accessoryView setStringValue:AILocalizedString(@"Select an entry from your address book, or add a new person.",
157 [accessoryView setFont:[NSFont systemFontOfSize:10.0]];
158 [accessoryView setDrawsBackground:NO];
159 [accessoryView setEnabled:NO];
160 [accessoryView setBezeled:NO];
161 [accessoryView sizeToFit];
162 //And attach it to our people picker view
163 [peoplePicker setAccessoryView:accessoryView];
165 //Configure our people picker
166 [peoplePicker setAllowsGroupSelection:NO];
167 [peoplePicker setAllowsMultipleSelection:NO];
168 [peoplePicker setValueSelectionBehavior:ABSingleValueSelection];
169 [peoplePicker setTarget:self];
170 [peoplePicker setNameDoubleAction:@selector(select:)];
172 //We show only the active services
173 servicesEnumerator = [[[adium accountController] activeServicesIncludingCompatibleServices:YES] objectEnumerator];
174 while ((aService = [servicesEnumerator nextObject])) {
175 property = [ESAddressBookIntegrationPlugin propertyFromService:aService];
176 if (property && ![[peoplePicker properties] containsObject:property])
177 [peoplePicker addProperty:property];
180 //Display our initial service if we were passed one
182 property = [ESAddressBookIntegrationPlugin propertyFromService:service];
183 if (property && [[peoplePicker properties] containsObject:property]) {
184 [peoplePicker setDisplayedProperty:property];
190 * @brief Hide ourself and inform our delegate
192 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
194 if (delegate && returnCode == NSOKButton)
195 [delegate absearchWindowControllerDidSelectPerson:self];
197 [sheet orderOut:nil];
203 - (IBAction)cancel:(id)sender
205 if ([self windowShouldClose:nil]) {
206 if ([[self window] isSheet]) {
207 [NSApp endSheet:[self window] returnCode:NSCancelButton];
209 [[self window] close];
215 * @brief Select a person
217 - (IBAction)select:(id)sender
219 NSArray *selectedValues = [peoplePicker selectedValues];
221 //Set the selected screen name
222 if ([selectedValues count] > 0)
223 [self _setScreenName:[selectedValues objectAtIndex:0]];
224 //Set the selected service
225 [self _setService:[ESAddressBookIntegrationPlugin serviceFromProperty:[peoplePicker displayedProperty]]];
226 //Set the selected person
227 [self _setPerson:[[peoplePicker selectedRecords] objectAtIndex:0]];
230 if ([self windowShouldClose:nil]) {
231 if ([[self window] isSheet]) {
232 [NSApp endSheet:[self window] returnCode:NSOKButton];
234 [[self window] close];
236 [delegate absearchWindowControllerDidSelectPerson:self];
242 * @brief Close the people search sheet, and display the create new person sheet
244 - (IBAction)createNewPerson:(id)sender
246 //Close the first sheet
249 //Setup our new window,
250 [self setWindow:newContactPanel];
251 [newContactPanel setDelegate:self];
254 if (carryingWindow) {
255 [NSApp beginSheet:newContactPanel
256 modalForWindow:carryingWindow
258 didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
261 [self showWindow:nil];
262 [[self window] center];
265 //Configure the views of our new window
266 [self buildContactTypeMenu];
267 [self configureForCurrentServiceType];
271 * @brief Create a new person and add it to the address book database
273 - (IBAction)addPerson:(id)sender
275 ABPerson *newPerson = [[[ABPerson alloc] init] autorelease];
276 NSString *contactID = [[textField_contactID stringValue] stringByTrimmingCharactersInSet:
277 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
279 //Create the new contact
280 if (![contactID isEqualToString:@""]) {
281 ABMutableMultiValue *value = [[ABMutableMultiValue alloc] init];
282 NSString *identifier = nil;
283 NSString *serviceIndentifier = [ESAddressBookIntegrationPlugin propertyFromService:service];
285 identifier = [value addValue:contactID withLabel:serviceIndentifier];
287 NSString *email = [[textField_email stringValue] stringByTrimmingCharactersInSet:
288 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
290 //Set the person's IM id
291 [newPerson setValue:value
292 forProperty:serviceIndentifier];
294 //Clean our multi value
295 [value removeValueAndLabelAtIndex:[value indexForIdentifier:identifier]];
297 //Set the person's email address
298 if (![email isEqualToString:@""]) {
299 identifier = [value addValue:[textField_email stringValue] withLabel:kABEmailProperty];
302 [newPerson setValue:value
303 forProperty:kABEmailProperty];
307 //Set the person's first name
308 [newPerson setValue:[textField_firstName stringValue]
309 forProperty:kABFirstNameProperty];
310 //Set the person's last name
311 [newPerson setValue:[textField_lastName stringValue]
312 forProperty:kABLastNameProperty];
313 //Set the person's nickname
314 [newPerson setValue:[textField_nickname stringValue]
315 forProperty:kABNicknameProperty];
316 //Set the person's image
318 [newPerson setImageData:contactImage];
320 //Add our newly created person to the AB database
321 if ([sharedAddressBook addRecord:newPerson] && [sharedAddressBook save]) {
322 [self _setPerson:newPerson];
323 [self _setScreenName:contactID];
326 if ([self windowShouldClose:nil]) {
327 if ([[self window] isSheet]) {
328 [NSApp endSheet:[self window] returnCode:NSOKButton];
330 [[self window] close];
332 [delegate absearchWindowControllerDidSelectPerson:self];
336 //Cancel if we can't add our person to the AB database.
345 //We didn't get a contact id.
346 //This is equal to pressing the cancel button.
352 * @brief Set our delegat
354 - (void)setDelegate:(id)newDelegate
356 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
359 [nc removeObserver:delegate
360 name:OWABSearchWindowControllerDidSelectPersonNotification
365 [nc addObserver:newDelegate
366 selector:@selector(OWABSearchWindowControllerDidSelectPerson:)
367 name:OWABSearchWindowControllerDidSelectPersonNotification
372 delegate = [newDelegate retain];
376 * @brief Returns our delegate
386 * @brief Returns the selected person.
388 - (ABPerson *)selectedPerson
394 * @brief Returns the selected person's screen name/number.
396 - (NSString *)selectedScreenName
402 * @brief Returns the selected person's name like it's displayed in AB.
404 - (NSString *)selectedName
406 NSString *result = nil;
407 NSString *firstName = [person valueForProperty:kABFirstNameProperty];
408 NSString *lastName = [person valueForProperty:kABLastNameProperty];
410 //Make sure we don't get "(null)" in our result
411 if (firstName && lastName) {
412 if ([sharedAddressBook defaultNameOrdering] == kABFirstNameFirst)
413 result = [firstName stringByAppendingFormat:@" %@", lastName];
415 result = [lastName stringByAppendingFormat:@" %@", firstName];
426 * @brief Returns the selected person's nickname.
428 - (NSString *)selectedAlias
430 return [person valueForProperty:kABNicknameProperty];
434 * @brief Returns the service of the selected screen name/number.
436 - (AIService *)selectedService
445 * @brief Build and configure the menu of contact service types
447 - (void)buildContactTypeMenu
450 /*[popUp_contactType setMenu:[AIServiceMenu menuOfServicesWithTarget:self
451 activeServicesOnly:YES
455 [popUp_contactType removeAllItems];
456 //We show only the active services
457 NSEnumerator *servicesEnumerator = [[[adium accountController] activeServicesIncludingCompatibleServices:YES] objectEnumerator];
460 while ((service = [servicesEnumerator nextObject])) {
461 if ([ESAddressBookIntegrationPlugin propertyFromService:service]) {
462 NSMenuItem *menuItem;
464 [popUp_contactType addItemWithTitle:[service shortDescription]];
466 menuItem = (NSMenuItem *)[popUp_contactType itemAtIndex:i];
467 [menuItem setRepresentedObject:service];
468 [menuItem setImage:[AIServiceIcons serviceIconForService:service
469 type:AIServiceIconSmall
470 direction:AIIconNormal]];
475 //Ensure our selection is still valid
476 [self ensureValidContactTypeSelection];
480 * @breif Ensures that the selected contact type is valid, selecting another if it isn't
482 - (void)ensureValidContactTypeSelection
484 int serviceIndex = -1;
486 //Force our menu to update.. it needs to be correctly validated for the code below to work
487 [[popUp_contactType menu] update];
489 //Find the menu item for our current service
490 if (service) serviceIndex = [popUp_contactType indexOfItemWithRepresentedObject:service];
492 //If our service is not available we'll have to pick another one
493 if (service && (serviceIndex == -1 || ![[popUp_contactType itemAtIndex:serviceIndex] isEnabled])) {
494 [self _setService:nil];
497 //If we don't have a service, pick the first availbale one
499 [self _setService:[[[popUp_contactType menu] firstEnabledMenuItem] representedObject]];
502 //Update our menu and window for the current service
503 [popUp_contactType selectItemWithRepresentedObject:service];
504 [self configureForCurrentServiceType];
508 * @brief Configure any service-dependent controls in our window for the current service
510 - (void)configureForCurrentServiceType
512 NSString *userNameLabel = [service userNameLabel];
514 [label_contactID setStringValue:[(userNameLabel ? userNameLabel :
515 AILocalizedString(@"Contact ID",nil)) stringByAppendingString:AILocalizedString(@":", "Colon which will be appended after a label such as 'User Name', before an input field")]];
519 * @brief User selected a new service type
521 - (void)selectServiceType:(id)sender
523 [self _setService:[[popUp_contactType selectedItem] representedObject]];
524 [self configureForCurrentServiceType];
528 * @brief Set the current service
530 - (void)_setService:(AIService *)inService
532 if (inService != service) {
534 service = [inService retain];
539 * @brief Set the current person
541 - (void)_setPerson:(ABPerson *)inPerson
543 if (inPerson != person) {
545 person = [inPerson retain];
550 * @brief Set the screen name/id
552 - (void)_setScreenName:(NSString *)inName
554 if (inName != screenName) {
555 [screenName release];
556 screenName = [inName retain];
561 * @brief Set the carrying window. This is the window that our sheet is attached to.
563 - (void)_setCarryingWindow:(NSWindow *)inWindow
565 if (carryingWindow != inWindow) {
566 [carryingWindow release];
567 carryingWindow = [inWindow retain];
571 // AIImageViewWithImagePicker Delegate ---------------------------------------------------------------------
572 #pragma mark AIImageViewWithImagePicker Delegate
573 - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)sender didChangeToImageData:(NSData *)imageData
575 if (contactImage != imageData) {
576 [contactImage release];
577 contactImage = [imageData retain];
581 - (void)deleteInImageViewWithImagePicker:(AIImageViewWithImagePicker *)sender
583 [contactImage release]; contactImage = nil;
590 @implementation NSObject (OWABSearchWindowControllerDelegate)
593 * @brief A delegate method that is sent when the user has selected a person/value.
595 - (void)absearchWindowControllerDidSelectPerson:(OWABSearchWindowController *)controller
597 //Do nothing by default