French updates
[adiumx.git] / Source / AINewContactWindowController.m
blobfe5bb1a0ca1911b30282c6659b97f7424e8b9630
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 "AIAccountController.h"
18 #import "AIContactController.h"
19 #import "AINewContactWindowController.h"
20 #import "AINewGroupWindowController.h"
21 #import <AIUtilities/AIMenuAdditions.h>
22 #import <AIUtilities/AIPopUpButtonAdditions.h>
23 #import <AIUtilities/AIStringAdditions.h>
24 #import <Adium/AIAccount.h>
25 #import <Adium/AIListContact.h>
26 #import <Adium/AIListGroup.h>
27 #import <Adium/AILocalizationTextField.h>
28 #import <Adium/AIService.h>
30 #define ADD_CONTACT_PROMPT_NIB  @"AddContact"
31 #define DEFAULT_GROUP_NAME              AILocalizedString(@"Contacts",nil)
33 @interface AINewContactWindowController (PRIVATE)
34 - (void)buildContactTypeMenu;
35 - (void)buildGroupMenu;
36 - (void)_buildGroupMenu:(NSMenu *)menu forGroup:(AIListGroup *)group level:(int)level;
37 - (void)validateEnteredName;
38 - (void)updateAccountList;
39 - (void)configureNameAndService;
40 - (void)configureForCurrentServiceType;
41 - (void)setContactName:(NSString *)contact;
42 - (void)setService:(AIService *)inService;
43 - (void)selectGroup:(id)sender;
44 - (void)selectFirstValidServiceType;
45 - (void)selectServiceType:(id)sender;
46 - (void)updateContactNameLabel;
47 @end
49 /*!
50  * @class AINewContactWindowController
51  * @brief Window controller for adding a new contact
52  */
53 @implementation AINewContactWindowController
55 /*!
56  * @brief Prompt for adding a new contact.
57  *
58  * @param parentWindow Window on which to show the prompt as a sheet. Pass nil for a panel prompt. 
59  * @param inName Initial value for the contact name field
60  * @param inService <tt>AIService</tt> for determining the initial service type selection
61  */
62 + (void)promptForNewContactOnWindow:(NSWindow *)parentWindow name:(NSString *)inName service:(AIService *)inService
64         AINewContactWindowController    *newContactWindow;
65         
66         newContactWindow = [[self alloc] initWithWindowNibName:ADD_CONTACT_PROMPT_NIB];
67         [newContactWindow setContactName:inName];
68         [newContactWindow setService:inService];
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
90     self = [super initWithWindowNibName:windowNibName];
92         return self;
95 /*!
96  * @brief Deallocate
97  */
98 - (void)dealloc
100         [accounts release];
101         [contactName release];
102         [service release];
103         
104     [super dealloc];
108  * @brief Setup the window before it is displayed
109  */
110 - (void)windowDidLoad
112         [textField_type setLocalizedString:AILocalizedString(@"Contact Type:","Contact type service dropdown label in Add Contact")];
113         [textField_alias setLocalizedString:AILocalizedString(@"Alias:",nil)];
114         [textField_inGroup setLocalizedString:AILocalizedString(@"In Group:",nil)];
115         [textField_addToAccounts setLocalizedString:AILocalizedString(@"Add to Accounts:",nil)];
117         [button_add setLocalizedString:AILocalizedString(@"Add",nil)];
118         [button_cancel setLocalizedString:AILocalizedString(@"Cancel",nil)];
120         originalContactNameLabelFrame = [textField_contactNameLabel frame];
121         
122         [self buildContactTypeMenu];
123         [self buildGroupMenu];
125         [self configureNameAndService];
126         
127         [[adium notificationCenter] addObserver:self
128                                                                    selector:@selector(accountListChanged:)
129                                                                            name:Account_ListChanged
130                                                                          object:nil];
132         [[adium contactController] registerListObjectObserver:self];
134         [[self window] setTitle:AILocalizedString(@"Add Contact",nil)];
135         [[self window] center];
139  * @brief Window is closing
140  */
141 - (void)windowWillClose:(id)sender
143         [super windowWillClose:sender];
144         [[adium contactController] unregisterListObjectObserver:self];
145         [[adium notificationCenter] removeObserver:self];
149  * @brief Called as the user list edit sheet closes, dismisses the sheet
150  */
151 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
153     [sheet orderOut:nil];
157  * @brief Cancel
158  */
159 - (IBAction)cancel:(id)sender
161         if([[self window] isSheet]){
162                 [NSApp endSheet:[self window]];
163         }else{
164                 [self closeWindow:nil];
165         }
169  * @brief Perform the addition of the contact
170  */
171 - (IBAction)addContact:(id)sender
173         NSString                *UID = [service filterUID:[textField_contactName stringValue] removeIgnoredCharacters:YES];
174         NSEnumerator    *enumerator = [accounts objectEnumerator];
175         AIListGroup             *group ;
176         AIAccount               *account;
177         NSString                *alias;
178         NSMutableArray  *contactArray = [NSMutableArray array];
179         
180         alias = [textField_contactAlias stringValue];
181         if([alias length] == 0) alias = nil; 
183         group = ([popUp_targetGroup numberOfItems] ?
184                         [[popUp_targetGroup selectedItem] representedObject] : 
185                         nil);
187         if (!group) group = [[adium contactController] groupWithUID:DEFAULT_GROUP_NAME];
189         while(account = [enumerator nextObject]){
190                 if([account contactListEditable] &&
191                    [[account preferenceForKey:KEY_ADD_CONTACT_TO group:PREF_GROUP_ADD_CONTACT] boolValue]){
192                         AIListContact   *contact = [[adium contactController] contactWithService:service
193                                                                                                                                                          account:account
194                                                                                                                                                                  UID:UID];
195                         if(alias) [contact setDisplayName:alias];
196                         
197                         [contactArray addObject:contact];
198                 }
199         }
201         [[adium contactController] addContacts:contactArray
202                                                                    toGroup:group];
204         if([[self window] isSheet]){
205                 [NSApp endSheet:[self window]];
206         }else{
207                 [self closeWindow:nil];
208         }
212 //Service Type ---------------------------------------------------------------------------------------------------------
213 #pragma mark Service Type
215  * @brief Build and configure the menu of contact service types
216  */
217 - (void)buildContactTypeMenu
219         NSMenuItem      *selectedItem;
220         
221         [popUp_contactType setMenu:[[adium accountController] menuOfServicesWithTarget:self 
222                                                                                                                                 activeServicesOnly:YES
223                                                                                                                                    longDescription:NO
224                                                                                                                                                         format:nil]];
225         
226         //- (BOOL)validateMenuItem:(NSMenuItem *)menuItem below will automatically manage the enabling/disabling 
227         //when we call update.
228         [[popUp_contactType menu] update];
229         
230         //If there is no selection or the current selection is now disabled, select the first valid service type
231         if (!service ||
232                 !(selectedItem = (NSMenuItem *)[popUp_contactType selectedItem]) ||
233                 (![selectedItem isEnabled])){
234                 [self selectFirstValidServiceType];
235         }else{
236                 //Otherwise, just perform needed configuration for the current selection
237                 [self configureForCurrentServiceType];
238         }
242  * @brief Select the first valid service type
244  * 'valid' in this context means that an account on the appropriate service is online.
245  */
246 - (void)selectFirstValidServiceType
248         NSEnumerator            *enumerator;
249         
250         enumerator = [[popUp_contactType itemArray] objectEnumerator];
251         NSMenuItem                      *menuItem;
252         while(menuItem = [enumerator nextObject]) {
253                 if([menuItem isEnabled]) {
254                         [popUp_contactType selectItem:menuItem];
255                         break;
256                 }
257         }
258         
259         [self selectServiceType:nil];
263  * Validate a menu item
264  */
265 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
267         NSEnumerator    *enumerator;
268         AIAccount               *account;
269         
270         enumerator = [[[adium accountController] accountsWithServiceClassOfService:[menuItem representedObject]] objectEnumerator];
271         while(account = [enumerator nextObject]){
272                 if([account contactListEditable]){
273                         return YES;
274                 }
275         }
276         return NO;
280  * @brief Service type was selected from the menu
281  */
282 - (void)selectServiceType:(id)sender
283 {       
284         service = [[popUp_contactType selectedItem] representedObject];
286         [self configureForCurrentServiceType];
290  * @brief Configure for the current service type
291  */
292 - (void)configureForCurrentServiceType
294         [self updateContactNameLabel];
295         [self updateAccountList];
296         [self validateEnteredName];
299 //Add to Group ---------------------------------------------------------------------------------------------------------
300 #pragma mark Add to Group
302  * @brief Build the menu of available destination groups
303  */
304 - (void)buildGroupMenu
306         AIListObject    *selectedObject;
307         NSMenu                  *menu;
308         //Rebuild the menu
309         menu = [[adium contactController] menuOfAllGroupsInGroup:nil withTarget:self];
310         
311         //Add a default group name to the menu if there are no groups listed
312         if ([menu numberOfItems] == 0) {
313                 [menu addItemWithTitle:DEFAULT_GROUP_NAME
314                                                 target:self
315                                                 action:@selector(selectGroup:)
316                                  keyEquivalent:@""];
317         }
318         
319         [menu addItem:[NSMenuItem separatorItem]];
320         [menu addItemWithTitle:[AILocalizedString(@"New Group",nil) stringByAppendingEllipsis]
321                                         target:self
322                                         action:@selector(newGroup:)
323                          keyEquivalent:@""];
324         
325         //Select the group of the currently selected object on the contact list
326         selectedObject = [[adium contactController] selectedListObject];
327         while (selectedObject && ![selectedObject isKindOfClass:[AIListGroup class]]) {
328                 selectedObject = [selectedObject containingObject];
329         }
330         
331         [popUp_targetGroup setMenu:menu];
332         
333         //If there was no selected group, just select the first item
334         if (selectedObject) {
335                 if (![popUp_targetGroup selectItemWithRepresentedObject:selectedObject]) {
336                         [popUp_targetGroup selectItemAtIndex:0];                        
337                 }
338                 
339         } else {
340                 [popUp_targetGroup selectItemAtIndex:0];
341         }
345  * @brief Prompt the user to add a new group immediately
346  */
347 - (void)newGroup:(id)sender
349         AINewGroupWindowController      *newGroupWindowController;
350         
351         newGroupWindowController = [AINewGroupWindowController promptForNewGroupOnWindow:[self window]];
352         
353         //Observe for the New Group window to close
354         [[adium notificationCenter] addObserver:self
355                                                                    selector:@selector(newGroupDidEnd:) 
356                                                                            name:@"NewGroupWindowControllerDidEnd"
357                                                                          object:[newGroupWindowController window]];     
360 - (void)newGroupDidEnd:(NSNotification *)inNotification
362         NSWindow        *window = [inNotification object];
363         
364         if ([[window windowController] isKindOfClass:[AINewGroupWindowController class]]) {
365                 NSString        *newGroupUID = [[window windowController] newGroupUID];
366                 AIListGroup *group = [[adium contactController] existingGroupWithUID:newGroupUID];
367                 
368                 //Rebuild the group menu
369                 [self buildGroupMenu];
370                 
371                 /* Select the new group if it exists; otherwise select the first group (so we don't still have New Group... selected).
372                         * If the user canceled, group will be nil since the group doesn't exist.
373                         */
374                 if (![popUp_targetGroup selectItemWithRepresentedObject:group]) {
375                         [popUp_targetGroup selectItemAtIndex:0];                        
376                 }
377                 
378                 [[self window] performSelector:@selector(makeKeyAndOrderFront:)
379                                                         withObject:self
380                                                         afterDelay:0];
381         }
382         
383         //Stop observing
384         [[adium notificationCenter] removeObserver:self
385                                                                                   name:@"NewGroupWindowControllerDidEnd" 
386                                                                                 object:window];
389 //Contact Name ---------------------------------------------------------------------------------------------------------
390 #pragma mark Contact Name
392  * @brief Fill in the name field if we came from a tab
393  */
394 - (void)configureNameAndService
396         if(contactName) {
397                 [textField_contactName setStringValue:contactName];
398         }
399         
400         if(service){
401                 NSMenuItem              *item;
402                 NSEnumerator    *enumerator = [[popUp_contactType itemArray] objectEnumerator];
403                 
404                 while (item = [enumerator nextObject]){
405                         if([item representedObject] == service){
406                                 [popUp_contactType selectItem:item];
407                                 break;
408                         }
409                 }
410         }
411         
412         [self configureForCurrentServiceType];
416  * @brief Set the contact name
418  * This does not perform subsequent validation.
419  */
420 - (void)setContactName:(NSString *)contact
422     if(contactName != contact){
423            [contactName release];
424            contactName = [contact retain];
425         }
429  * Set the service
431  * This does not perform subsequent validation.
432  */
433 - (void)setService:(AIService *)inService
435     if(service != inService){
436         [service release];
437         service = [inService retain];
438     }
442  * @brief Entered name is changing; validate it.
443  */
444 - (void)controlTextDidChange:(NSNotification *)notification
446         if([notification object] == textField_contactName){
447                 [self validateEnteredName];
448         }
452  * @brief Validate the entered name, enabling the add button if it is valid
453  */
454 - (void)validateEnteredName
456         NSString        *name = [textField_contactName stringValue];
457         BOOL            enabled = YES;
458         
459         if([name length] != 0 && [name length] <= [service allowedLengthForUIDs]){
460                 BOOL            caseSensitive = [service caseSensitive];
461                 NSScanner       *scanner = [NSScanner scannerWithString:(caseSensitive ? name : [name lowercaseString])];
462                 NSString        *validSegment = nil;
463                 
464                 [scanner scanCharactersFromSet:[service allowedCharactersForUIDs] intoString:&validSegment];
465                 if(!validSegment || [validSegment length] != [name length]){
466                         enabled = NO;
467                 }
468         }else{
469                 enabled = NO;
470         }
472         //If enabled so far, make sure an account is checked
473         if (enabled){
474                 NSEnumerator    *enumerator = [accounts objectEnumerator];
475                 AIAccount               *account;
476                 
477                 BOOL anAccountIsChecked = NO;
478                 
479                 while(account = [enumerator nextObject]){
480                         if([account contactListEditable] &&
481                            [[account preferenceForKey:KEY_ADD_CONTACT_TO group:PREF_GROUP_ADD_CONTACT] boolValue]){
482                                 anAccountIsChecked = YES;
483                                 break;
484                         }
485                 }       
486                 
487                 enabled = anAccountIsChecked;
488         }
490         [button_add setEnabled:enabled];
493 //Add to Accounts ------------------------------------------------------------------------------------------------------
494 #pragma mark Add to Accounts
496  * @brief Update the accounts list
497  */
498 - (void)updateAccountList
499 {       
500         NSEnumerator    *enumerator;
501         AIAccount               *account;
502         NSNumber                *addTo;
503         
504         [accounts release];
505         accounts = [[[adium accountController] accountsWithServiceClassOfService:service] retain];
506         
507         //Select accounts by default
508         enumerator = [accounts objectEnumerator];
509         while(account = [enumerator nextObject]) {
510                 addTo = [account preferenceForKey:KEY_ADD_CONTACT_TO group:PREF_GROUP_ADD_CONTACT];
511                 if(!addTo)
512                         [account setPreference:[NSNumber numberWithBool:YES] forKey:KEY_ADD_CONTACT_TO 
513                                                          group:PREF_GROUP_ADD_CONTACT];
514         }
515         [tableView_accounts reloadData];
519  * @brief The account list changed
520  */
521 - (void)accountListChanged:(NSNotification *)notification
523         //Attempt to retain the current contact type selection
524         id representedObject = [[popUp_contactType selectedItem] representedObject];
525         [self buildContactTypeMenu];
526         
527         int index = [popUp_contactType indexOfItemWithRepresentedObject:representedObject];
528         if (index != NSNotFound){
529                 [popUp_contactType selectItemAtIndex:index];
530         }
531         
532         [self updateAccountList];
536  * @brief Update when account connectivity changes
538  * If the selected service is still valid, just reload the accounts table view to update it.
539  * If it is no longer valid (the last account on this service just signed off), select the first valid one.
540  */
541 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
543         if ([inObject isKindOfClass:[AIAccount class]] && [inModifiedKeys containsObject:@"Online"]){
544                 if([self validateMenuItem:(NSMenuItem *)[popUp_contactType selectedItem]]){
545                         //If the current selection in the contact type menu is still valid (an account is still online), reload the accounts data
546                         [tableView_accounts reloadData];
547                 }else{
548                         //If it is not, switch to the first valid contact type and update accordingly
549                         [self selectFirstValidServiceType];
550                 }
551         }
552         
553         return nil;
557  * @brief Update the contact name label
559  * The label is customized for the selected service as well as localized.
560  * Updating the label may resize the window to fit.
561  */
562 - (void)updateContactNameLabel
564         NSRect          oldFrame;
565         NSRect          newFrame;
566         
567         oldFrame = [textField_contactNameLabel frame];
569         //If the old frame is smaller than our original frame, treat the old frame as that original frame
570         //for resizing and positioning purposes
571         if(oldFrame.size.width < originalContactNameLabelFrame.size.width){
572                         oldFrame = originalContactNameLabelFrame;
573         }
574         
575         NSString        *userNameLabel = (service ? [service userNameLabel] : nil);
577         //Set to the userNameLabel, using a default value if we have no userNameLabel, then sizeToFit
578         [textField_contactNameLabel setStringValue:[(userNameLabel ? userNameLabel : AILocalizedString(@"Contact ID",nil)) stringByAppendingString:@":"]];
579         [textField_contactNameLabel sizeToFit];
580         newFrame = [textField_contactNameLabel frame];
582         //Enforce a minimum width of the original contact name label frame width
583         if(newFrame.size.width < originalContactNameLabelFrame.size.width){
584                 newFrame.size.width = originalContactNameLabelFrame.size.width;
585         }
587         //Only use integral widths to keep alignment correct;
588         //round up as an extra pixel of whitespace never hurt anybody
589         newFrame.size.width = round(newFrame.size.width + 0.5);
591         //Keep the right edge in the same place at all times
592         newFrame.origin.x = oldFrame.origin.x + oldFrame.size.width - newFrame.size.width;
594         [textField_contactNameLabel setFrame:newFrame]; 
595         [textField_contactNameLabel setNeedsDisplay:YES];
597         //Resize the window to fit the contactNameLabel if the current origin is not correct; the resut
598         if(newFrame.origin.x < 17){
599                 NSRect  windowFrame = [[self window] frame];
600                 float   difference = 17 - newFrame.origin.x;
602                 windowFrame.origin.x -= difference;
603                 windowFrame.size.width += difference;
604                 [[self window] setFrame:windowFrame display:YES animate:YES];
606         }else if(oldFrame.origin.x <= 17){
607                 NSRect  windowFrame = [[self window] frame];
608                 float   difference = oldFrame.origin.x - newFrame.origin.x;
609                 
610                 if(newFrame.origin.x + difference < originalContactNameLabelFrame.origin.x){
611                         difference = originalContactNameLabelFrame.origin.x - newFrame.origin.x;
612                 }
613                 
614                 windowFrame.origin.x -= difference;
615                 windowFrame.size.width += difference;
616                 [[self window] setFrame:windowFrame display:YES animate:YES];
618         }else{
619                 //Display to remove any artifacts from the frame changing
620                 [[self window] display];
621         }
625  * @brief Rows in the accounts table view
626  */
627 - (int)numberOfRowsInTableView:(NSTableView *)tableView
629         return([accounts count]);
633  * @brief Object value for columns in the accounts table view
634  */
635 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
637         NSString        *identifier = [tableColumn identifier];
638         
639         if([identifier isEqualToString:@"check"]){
640                 return([[accounts objectAtIndex:row] contactListEditable] ?
641                            [[accounts objectAtIndex:row] preferenceForKey:KEY_ADD_CONTACT_TO 
642                                                                                                                 group:PREF_GROUP_ADD_CONTACT] :
643                            [NSNumber numberWithBool:NO]);
644         }else if([identifier isEqualToString:@"account"]){
645                 return([[accounts objectAtIndex:row] formattedUID]);
646         }else{
647                 return(@"");
648         }
652  * @brief Will display cell
654  * Enable/disable account checkbox as appropriate
655  */
656 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
658         [cell setEnabled:[[accounts objectAtIndex:row] contactListEditable]];
662  * @brief Set the enabled/disabled state for an account in the account list
663  */
664 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
666         NSString        *identifier = [tableColumn identifier];
668         if([identifier isEqualToString:@"check"]){
669                 [[accounts objectAtIndex:row] setPreference:[NSNumber numberWithBool:[object boolValue]] 
670                                                                                          forKey:KEY_ADD_CONTACT_TO 
671                                                                                           group:PREF_GROUP_ADD_CONTACT];
672                 [self validateEnteredName];
673         }
676 /* 
677  * @brief Selection is changing
679  * Adam: I don't want the table to display its selection.
680  * Returning NO from 'shouldSelectRow' would work, but if we do that the checkbox cells stop working.
681  * The best solution I've come up with so far is to just force a deselect here :( .
682  */
683 - (void)tableViewSelectionIsChanging:(NSNotification *)notification{
684         [tableView_accounts deselectAll:nil];
687 /* 
688  * @brief Selection is changing
690  * Adam: I don't want the table to display its selection.
691  * Returning NO from 'shouldSelectRow' would work, but if we do that the checkbox cells stop working.
692  * The best solution I've come up with so far is to just force a deselect here :( .
693  */
694 - (void)tableViewSelectionDidChange:(NSNotification *)notification{
695         [tableView_accounts deselectAll:nil];
699  * @brief Empty selector called by the group popUp menu
700  */
701 - (void)selectGroup:(id)sender
705 @end