Unescape the HREF attribute's text before passing it to NSURL which does not expect...
[adiumx.git] / Source / OWABSearchWindowController.m
blob999c962ee2b5aa6e8b571f45ffd37948b5f581c9
1 //
2 //  OWABSearchWindowController.m
3 //  Adium
4 //
5 //  Created by Ofri Wolfus on 19/07/05.
6 //  Copyright 2006 The Adium Team. All rights reserved.
7 //
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;
38 @end
41 /*!
42  * @class OWABSearchWindowController
43  * @brief Window controller for searching people in the Address Book database.
44  */
45 @implementation OWABSearchWindowController
47 static  ABAddressBook   *sharedAddressBook = nil;
49 /*!
50  * @brief Prompt for searching a person within the AB database.
51  *
52  * @param parentWindow Window on which to show the prompt as a sheet. Pass nil for a panel prompt.
53  */
54 + (id)promptForNewPersonSearchOnWindow:(NSWindow *)parentWindow initialService:(AIService *)inService
56         OWABSearchWindowController *newABSearchWindow;
57         
58         newABSearchWindow = [[self alloc] initWithWindowNibName:AB_SEARCH_NIB initialService:inService];
59         
60         if (parentWindow) {
61                 [NSApp beginSheet:[newABSearchWindow window]
62                    modalForWindow:parentWindow
63                         modalDelegate:newABSearchWindow
64                    didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
65                           contextInfo:nil];
66                 [newABSearchWindow _setCarryingWindow:parentWindow];
67         } else {
68                 [newABSearchWindow showWindow:nil];
69         }
70         
71         return [newABSearchWindow autorelease];
74 /*!
75  * @brief Initialize
76  */
77 - (id)initWithWindowNibName:(NSString *)windowNibName initialService:(AIService *)inService
79         self = [super initWithWindowNibName:windowNibName];
80         
81         if (self) {
82                 delegate = nil;
83                 person = nil;
84                 screenName = nil;
85                 carryingWindow = nil;
86                 contactImage = nil;
87                 service = [inService retain];
89                 if (!sharedAddressBook)
90                         sharedAddressBook = [[ABAddressBook sharedAddressBook] retain];
91         }
92         
93         return self;
96 /*!
97  * @brief Deallocate
98  */
99 - (void)dealloc
101         [self setDelegate:nil];
102         [person release];
103         [screenName release];
104         [service release];
105         [carryingWindow release];
106         [contactImage release];
107         [sharedAddressBook release]; sharedAddressBook = nil;
108         [super dealloc];
112  * @brief Setup the window before it is displayed
113  */
114 - (void)windowDidLoad
116         [[self window] center];
117         
118         //Localized strings
119         //Search window
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)];
124         //New contact window
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)];
136         
137         [imageView_contactIcon setMaxSize:NSMakeSize(256, 256)];
139         [self _configurePeoplePicker];
141         [[self window] selectKeyViewFollowingView:peoplePicker];
145  * @brief Setup our ABPeoplePickerView
146  */
147 - (void)_configurePeoplePicker
149         NSTextField             *accessoryView = [[[NSTextField alloc] init] autorelease];
150         NSEnumerator    *servicesEnumerator;
151         NSString                *property;
152         AIService               *aService;
153         
154         //Create a small explanation text
155         [accessoryView setStringValue:AILocalizedString(@"Select an entry from your address book, or add a new person.",
156                                                                                                         nil)];
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];
164         
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:)];
171         
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];
178         }
180         //Display our initial service if we were passed one
181         if (service) {
182                 property = [ESAddressBookIntegrationPlugin propertyFromService:service];
183                 if (property && [[peoplePicker properties] containsObject:property]) {
184                         [peoplePicker setDisplayedProperty:property];
185                 }
186         }
190  * @brief Hide ourself and inform our delegate
191  */
192 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
194         if (delegate && returnCode == NSOKButton)
195                 [delegate absearchWindowControllerDidSelectPerson:self];
196         
197         [sheet orderOut:nil];
201  * @brief Cancel
202  */
203 - (IBAction)cancel:(id)sender
205         if ([self windowShouldClose:nil]) {
206                 if ([[self window] isSheet]) {
207                         [NSApp endSheet:[self window] returnCode:NSCancelButton];
208                 } else {
209                         [[self window] close];
210                 }
211         }
215  * @brief Select a person
216  */
217 - (IBAction)select:(id)sender
219         NSArray *selectedValues = [peoplePicker selectedValues];
220         
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]];
228         
229         //Close our window
230         if ([self windowShouldClose:nil]) {
231                 if ([[self window] isSheet]) {
232                         [NSApp endSheet:[self window] returnCode:NSOKButton];
233                 } else {
234                         [[self window] close];
235                         if (delegate)
236                                 [delegate absearchWindowControllerDidSelectPerson:self];
237                 }
238         }
242  * @brief Close the people search sheet, and display the create new person sheet
243  */
244 - (IBAction)createNewPerson:(id)sender
246         //Close the first sheet
247         [self cancel:nil];
248         
249         //Setup our new window,
250         [self setWindow:newContactPanel];
251         [newContactPanel setDelegate:self];
252         
253         //and show it
254         if (carryingWindow) {
255                 [NSApp beginSheet:newContactPanel
256                    modalForWindow:carryingWindow
257                         modalDelegate:self
258                    didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
259                           contextInfo:nil];
260         } else {
261                 [self showWindow:nil];
262                 [[self window] center];
263         }
264         
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
272  */
273 - (IBAction)addPerson:(id)sender
275         ABPerson                *newPerson = [[[ABPerson alloc] init] autorelease];
276         NSString                *contactID = [[textField_contactID stringValue] stringByTrimmingCharactersInSet:
277                                                                         [NSCharacterSet whitespaceAndNewlineCharacterSet]];
278         
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];
284                 
285                 identifier = [value addValue:contactID withLabel:serviceIndentifier];
286                 if (identifier) {
287                         NSString *email = [[textField_email stringValue] stringByTrimmingCharactersInSet:
288                                                                 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
289                         
290                         //Set the person's IM id
291                         [newPerson setValue:value
292                                         forProperty:serviceIndentifier];
293                         
294                         //Clean our multi value
295                         [value removeValueAndLabelAtIndex:[value indexForIdentifier:identifier]];
296                         
297                         //Set the person's email address
298                         if (![email isEqualToString:@""]) {
299                                 identifier = [value addValue:[textField_email stringValue] withLabel:kABEmailProperty];
300                                 
301                                 if (identifier) {
302                                         [newPerson setValue:value
303                                                         forProperty:kABEmailProperty];
304                                 }
305                         }
306                         
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
317                         if (contactImage)
318                                 [newPerson setImageData:contactImage];
319                         
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];
324                                 
325                                 //Close our window
326                                 if ([self windowShouldClose:nil]) {
327                                         if ([[self window] isSheet]) {
328                                                 [NSApp endSheet:[self window] returnCode:NSOKButton];
329                                         } else {
330                                                 [[self window] close];
331                                                 if (delegate)
332                                                         [delegate absearchWindowControllerDidSelectPerson:self];
333                                         }
334                                 }
335                         } else {
336                                 //Cancel if we can't add our person to the AB database.
337                                 [self cancel:nil];
338                         }
339                 }
340                 
341                 //Clean up
342                 [value release];
343                 
344         } else {
345                 //We didn't get a contact id.
346                 //This is equal to pressing the cancel button.
347                 [self cancel:nil];
348         }
352  * @brief Set our delegat
353  */
354 - (void)setDelegate:(id)newDelegate
356         NSNotificationCenter    *nc = [NSNotificationCenter defaultCenter];
357         
358         if (delegate) {
359                 [nc removeObserver:delegate
360                                           name:OWABSearchWindowControllerDidSelectPersonNotification
361                                         object:self];
362         }
363         
364         if (newDelegate) {
365                 [nc addObserver:newDelegate
366                            selector:@selector(OWABSearchWindowControllerDidSelectPerson:)
367                                    name:OWABSearchWindowControllerDidSelectPersonNotification
368                                  object:self];
369         }
370         
371         [delegate release];
372         delegate = [newDelegate retain];
376  * @brief Returns our delegate
377  */
378 - (id)delegate
380         return delegate;
383 #pragma mark -
386  * @brief Returns the selected person.
387  */
388 - (ABPerson *)selectedPerson
390         return person;
394  * @brief Returns the selected person's screen name/number.
395  */
396 - (NSString *)selectedScreenName
398         return screenName;
402  * @brief Returns the selected person's name like it's displayed in AB.
403  */
404 - (NSString *)selectedName
406         NSString *result = nil;
407         NSString *firstName = [person valueForProperty:kABFirstNameProperty];
408         NSString *lastName = [person valueForProperty:kABLastNameProperty];
409         
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];
414                 else
415                         result = [lastName stringByAppendingFormat:@" %@", firstName];
416         }
417         else if (firstName)
418                 result = firstName;
419         else if (lastName)
420                 result = lastName;
421         
422         return result;
426  * @brief Returns the selected person's nickname.
427  */
428 - (NSString *)selectedAlias
430         return [person valueForProperty:kABNicknameProperty];
434  * @brief Returns the service of the selected screen name/number.
435  */
436 - (AIService *)selectedService
438         return service;
441 #pragma mark -
442 #pragma mark Private
445  * @brief Build and configure the menu of contact service types
446  */
447 - (void)buildContactTypeMenu
449         //Rebuild the menu
450         /*[popUp_contactType setMenu:[AIServiceMenu menuOfServicesWithTarget:self
451                                                                                                         activeServicesOnly:YES
452                                                                                                            longDescription:NO
453                                                                                                                                 format:nil]];*/
454         
455         [popUp_contactType removeAllItems];
456         //We show only the active services
457         NSEnumerator    *servicesEnumerator = [[[adium accountController] activeServicesIncludingCompatibleServices:YES] objectEnumerator];
458         unsigned int    i = 0;
459         
460         while ((service = [servicesEnumerator nextObject])) {
461                 if ([ESAddressBookIntegrationPlugin propertyFromService:service]) {
462                         NSMenuItem      *menuItem;
463                         
464                         [popUp_contactType addItemWithTitle:[service shortDescription]];
465                         
466                         menuItem = (NSMenuItem *)[popUp_contactType itemAtIndex:i];
467                         [menuItem setRepresentedObject:service];
468                         [menuItem setImage:[AIServiceIcons serviceIconForService:service
469                                                                                                                                 type:AIServiceIconSmall
470                                                                                                                    direction:AIIconNormal]];
471                         i++;
472                 }
473         }
474         
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
481  */
482 - (void)ensureValidContactTypeSelection
484         int                     serviceIndex = -1;
485         
486         //Force our menu to update.. it needs to be correctly validated for the code below to work
487         [[popUp_contactType menu] update];
488         
489         //Find the menu item for our current service
490         if (service) serviceIndex = [popUp_contactType indexOfItemWithRepresentedObject:service];               
491         
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];
495         }
496         
497         //If we don't have a service, pick the first availbale one
498         if (!service) {
499                 [self _setService:[[[popUp_contactType menu] firstEnabledMenuItem] representedObject]];
500         }
501         
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
509  */
510 - (void)configureForCurrentServiceType
512         NSString        *userNameLabel = [service userNameLabel];
513         
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
520  */
521 - (void)selectServiceType:(id)sender
522 {       
523         [self _setService:[[popUp_contactType selectedItem] representedObject]];
524         [self configureForCurrentServiceType];
528  * @brief Set the current service
529  */
530 - (void)_setService:(AIService *)inService
532         if (inService != service) {
533                 [service release];
534                 service = [inService retain];
535         }
539  * @brief Set the current person
540  */
541 - (void)_setPerson:(ABPerson *)inPerson
543         if (inPerson != person) {
544                 [person release];
545                 person = [inPerson retain];
546         }
550  * @brief Set the screen name/id
551  */
552 - (void)_setScreenName:(NSString *)inName
554         if (inName != screenName) {
555                 [screenName release];
556                 screenName = [inName retain];
557         }
561  * @brief Set the carrying window. This is the window that our sheet is attached to.
562  */
563 - (void)_setCarryingWindow:(NSWindow *)inWindow
565         if (carryingWindow != inWindow) {
566                 [carryingWindow release];
567                 carryingWindow = [inWindow retain];
568         }
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];
578         }
581 - (void)deleteInImageViewWithImagePicker:(AIImageViewWithImagePicker *)sender
583         [contactImage release]; contactImage = nil;
586 @end
589 #pragma mark -
590 @implementation NSObject (OWABSearchWindowControllerDelegate)
593  * @brief A delegate method that is sent when the user has selected a person/value.
594  */
595 - (void)absearchWindowControllerDidSelectPerson:(OWABSearchWindowController *)controller
597         //Do nothing by default
600 @end