Documentation of `SecCertificateGetData()` says that we should malloc space for its...
[adiumx.git] / Source / AINewContactWindowController.m
blobc07f3052bb615efbe1eb0a8d67720d6764f034a2
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
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;
48 @end
50 /*!
51  * @class AINewContactWindowController
52  * @brief Window controller for adding a new contact
53  */
54 @implementation AINewContactWindowController
56 /*!
57  * @brief Prompt for adding a new contact.
58  *
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
63  */
64 + (void)promptForNewContactOnWindow:(NSWindow *)parentWindow name:(NSString *)inName service:(AIService *)inService account:(AIAccount *)inAccount
66         AINewContactWindowController    *newContactWindow;
67         
68         newContactWindow = [[self alloc] initWithWindowNibName:ADD_CONTACT_PROMPT_NIB contactName:inName service:inService account:inAccount];
69         
70         if (parentWindow) {
71                 [parentWindow makeKeyAndOrderFront:nil];
72                 
73                 [NSApp beginSheet:[newContactWindow window]
74                    modalForWindow:parentWindow
75                         modalDelegate:newContactWindow
76                    didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
77                           contextInfo:nil];
78         } else {
79                 [newContactWindow showWindow:nil];
80                 [[newContactWindow window] makeKeyAndOrderFront:nil];
81         }
82         
85 /*!
86  * @brief Initialize
87  */
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];
95         uniqueID = nil;
96         
97         return self;
101  * @brief Deallocate
102  */
103 - (void)dealloc
105         [accounts release];
106         [contactName release];
107         [service release];
108         [initialAccount release];
109         [uniqueID release];
110         [checkedAccounts release];
112         [[NSNotificationCenter defaultCenter] removeObserver:self];
114     [super dealloc];
118  * @brief Setup the window before it is displayed
119  */
120 - (void)windowDidLoad
122         [[self window] center];
124         //Localized Strings
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)];
130         
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];    
142         
143         //Observe account list and status changes
144         [[adium notificationCenter] addObserver:self
145                                                                    selector:@selector(accountListChanged:)
146                                                                            name:Account_ListChanged
147                                                                          object:nil];
148         [[adium contactController] registerListObjectObserver:self];
152  * @brief Window is closing
153  */
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
163  */
164 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
166     [sheet orderOut:nil];
170  * @brief Cancel
171  */
172 - (IBAction)cancel:(id)sender
174         [self closeWindow:nil];
178  * @brief Perform the addition of the contact
179  */
180 - (IBAction)addContact:(id)sender
182         NSString                *UID = [service normalizeUID:[textField_contactName stringValue] removeIgnoredCharacters:YES];
183         NSString                *alias = [textField_contactAlias stringValue];
184         NSEnumerator    *enumerator;
185         AIListGroup             *group;
186         AIAccount               *account;
187         NSMutableArray  *contactArray = [NSMutableArray array];
188         
189         //Group
190         group = ([popUp_targetGroup numberOfItems] ?
191                         [[popUp_targetGroup selectedItem] representedObject] : 
192                         nil);
193         
194         if (!group) group = [[adium contactController] groupWithUID:DEFAULT_GROUP_NAME];
195         
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
202                                                                                                                                                          account:account
203                                                                                                                                                                  UID:UID];
204                         
205                         if (contact) {
206                                 if (alias && [alias length]) [contact setDisplayName:alias];
207                                 [contactArray addObject:contact];
208                                 
209                                 //Remember the ABPerson's unique ID associated with this contact
210                                 if (uniqueID)
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];
217                         }
218                 }
219         }
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.
229  */
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
240  */
241 - (void)absearchWindowControllerDidSelectPerson:(OWABSearchWindowController *)controller
243         ABPerson *selectedPerson = [controller selectedPerson];
244         
245         if (selectedPerson) {
246                 NSString *selectedScreenName = [controller selectedScreenName];
247                 NSString *selectedName = [controller selectedName];
248                 AIService *selectedService = [controller selectedService];
249                 
250                 if (selectedScreenName)
251                         [textField_contactName setStringValue:[service normalizeUID:selectedScreenName removeIgnoredCharacters:YES]];
252                 
253                 if (selectedName)
254                         [textField_contactAlias setStringValue:selectedName];
255                 
256                 if (selectedService) {
257                         [popUp_contactType selectItemWithTitle:[selectedService shortDescription]];
258                         [self selectServiceType:nil];
259                 }
260                 
261                 uniqueID = [[selectedPerson uniqueId] retain];
262         }
263         
264         //Clean up
265         [controller release];
269 //Service Type ---------------------------------------------------------------------------------------------------------
270 #pragma mark Service Type
272  * @brief Build and configure the menu of contact service types
273  */
274 - (void)buildContactTypeMenu
276         //Rebuild the menu
277         [popUp_contactType setMenu:[AIServiceMenu menuOfServicesWithTarget:self
278                                                                                                         activeServicesOnly:YES
279                                                                                                            longDescription:NO
280                                                                                                                                 format:nil]];
281         
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
288  */
289 - (void)ensureValidContactTypeSelection
291         int                     serviceIndex = -1;
292         
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];
302         }
304         //If we don't have a service, pick the first available one
305         if (!service) {
306                 [self _setServiceType:[[[popUp_contactType menu] firstEnabledMenuItem] representedObject]];
307         }
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
316  */
317 - (void)configureForCurrentServiceType
319         NSString        *userNameLabel = [service contactUserNameLabel];
320         
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
334  */
335 - (void)selectServiceType:(id)sender
336 {       
337         [self _setServiceType:[[popUp_contactType selectedItem] representedObject]];
338         [self configureForCurrentServiceType];
342  * @brief Set the current service type
343  */
344 - (void)_setServiceType:(AIService *)inService
346         if (inService != service) {
347                 [service release];
348                 service = [inService retain];
349         }
353  * Validate a menu item
354  */
355 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
357         NSEnumerator    *enumerator = [[[adium accountController] accountsCompatibleWithService:[menuItem representedObject]] objectEnumerator];
358         AIAccount               *account;
359         
360         while ((account = [enumerator nextObject])) {
361                 if ([account contactListEditable]) return YES;
362         }
363         
364         return NO;
368  * @brief Update our contact type menu when user accounts change
369  */
370 - (void)accountListChanged:(NSNotification *)notification
372         [self buildContactTypeMenu];
376  * @brief Update our contact type when account availability changes
377  */
378 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
380         if ([inObject isKindOfClass:[AIAccount class]]) {
381                 [self ensureValidContactTypeSelection];
382         }
384         return nil;
388 //Add to Group ---------------------------------------------------------------------------------------------------------
389 #pragma mark Add to Group
391  * @brief Build the menu of available destination groups
392  */
393 - (void)buildGroupMenu
395         AIListObject    *selectedObject;
396         NSMenu                  *menu;
397         //Rebuild the menu
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
403                                                 target:self
404                                                 action:@selector(selectGroup:)
405                                  keyEquivalent:@""];
406         }
407         
408         [menu addItem:[NSMenuItem separatorItem]];
409         [menu addItemWithTitle:[AILocalizedString(@"New Group",nil) stringByAppendingEllipsis]
410                                         target:self
411                                         action:@selector(newGroup:)
412                          keyEquivalent:@""];
413         
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];
418         }
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];                        
426                 }
428         } else {
429                 [popUp_targetGroup selectItemAtIndex:0];
430         }
434  * @brief Prompt the user to add a new group immediately
435  */
436 - (void)newGroup:(id)sender
438         AINewGroupWindowController      *newGroupWindowController;
439         
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];
459                 
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.
462                  */
463                 if (![popUp_targetGroup selectItemWithRepresentedObject:group]) {
464                         [popUp_targetGroup selectItemAtIndex:0];                        
465                 }
466                 
467                 [[self window] performSelector:@selector(makeKeyAndOrderFront:)
468                                                         withObject:self
469                                                         afterDelay:0];
470         }
472         //Stop observing
473         [[adium notificationCenter] removeObserver:self
474                                                                                   name:@"NewGroupWindowControllerDidEnd" 
475                                                                                 object:window];
478 //Add to Accounts ------------------------------------------------------------------------------------------------------
479 #pragma mark Add to Accounts
481  * @brief Update the accounts list
482  */
483 - (void)updateAccountList
484 {       
485         [accounts release];
486         accounts = [[[adium accountController] accountsCompatibleWithService:service] retain];
487         
488         [checkedAccounts release];
489         checkedAccounts = [[NSMutableSet alloc] init];
491         //Select accounts by default
492         if (initialAccount && [accounts containsObject:initialAccount]) {
493                 [checkedAccounts addObject:initialAccount];
495         } else {
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];
503                 }
504         }
506         [tableView_accounts reloadData];
510  * @brief Rows in the accounts table view
511  */
512 - (int)numberOfRowsInTableView:(NSTableView *)tableView
514         return [accounts count];
518  * @brief Object value for columns in the accounts table view
519  */
520 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
522         NSString        *identifier = [tableColumn identifier];
523         
524         if ([identifier isEqualToString:@"check"]) {
525                 return ([[accounts objectAtIndex:row] contactListEditable] ?
526                                 [NSNumber numberWithBool:[checkedAccounts containsObject:[accounts objectAtIndex:row]]] :
527                                 [NSNumber numberWithBool:NO]);
528         
529         } else if ([identifier isEqualToString:@"account"]) {
530                 return [[accounts objectAtIndex:row] explicitFormattedUID];
531                 
532         } else {
533                 return @"";
535         }
539  * @brief Will display cell
541  * Enable/disable account checkbox as appropriate
542  */
543 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
545         NSString        *identifier = [tableColumn identifier];
546         
547         if ([identifier isEqualToString:@"check"]) {
548                 [cell setEnabled:[[accounts objectAtIndex:row] contactListEditable]];
549         }
553  * @brief Set the enabled/disabled state for an account in the account list
554  */
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]];
565                 } else {
566                         [checkedAccounts removeObject:[accounts objectAtIndex:row]];                    
567                 }
568         }
572  * @brief Empty selector called by the group popUp menu
573  */
574 - (void)selectGroup:(id)sender
579 @end