Put NSAutoreleasePool usage around other distributed notification observer methods
[adiumx.git] / Source / AIStandardListWindowController.m
blobd97ee2f0145c579129abcdd7adcf7f55780cc951
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 <AIUtilities/AIArrayAdditions.h>
18 #import <AIUtilities/AIAttributedStringAdditions.h>
19 #import <AIUtilities/AIImageAdditions.h>
20 #import <AIUtilities/AIMenuAdditions.h>
21 #import <AIUtilities/AIStringAdditions.h>
22 #import <AIUtilities/AIToolbarUtilities.h>
24 #import <Adium/AIListGroup.h>
25 #import <Adium/AIAccountControllerProtocol.h>
26 #import <Adium/AIContactControllerProtocol.h>
27 #import <Adium/AIPreferenceControllerProtocol.h>
28 #import <Adium/AIToolbarControllerProtocol.h>
29 #import <Adium/AIAccount.h>
30 #import <Adium/AIListObject.h>
31 #import <Adium/AIMetaContact.h>
32 #import <Adium/AIStatusMenu.h>
33 #import <Adium/AIListSmartGroup.h>
35 #import "AIStatusController.h"
36 #import "AIStandardListWindowController.h"
38 #import "AIHoveringPopUpButton.h"
39 #import "AIContactListImagePicker.h"
40 #import "AIContactListNameButton.h"
42 #define TOOLBAR_CONTACT_LIST                            @"ContactList:1.0"                              //Toolbar identifier
44 @interface AIStandardListWindowController (PRIVATE)
45 - (void)_configureToolbar;
46 - (void)updateStatusMenuSelection:(NSNotification *)notification;
47 - (void)updateImagePicker;
48 - (void)updateNameView;
49 - (void)repositionImagePickerToPosition:(ContactListImagePickerPosition)desiredImagePickerPosition;
50 @end
52 @implementation AIStandardListWindowController
54 /*!
55  * @brief Deallocate
56  */
57 - (void)dealloc
59         [[adium preferenceController] unregisterPreferenceObserver:self];
60         [[adium notificationCenter] removeObserver:self];
62         [super dealloc];
65 /*!
66  * @brief Nib name
67  */
68 + (NSString *)nibName
70     return @"ContactListWindow";
73 /*!
74  * @brief Window loaded
75  */
76 - (void)windowDidLoad
78         //Our nib starts with the image picker on the left side
79         imagePickerPosition = ContactListImagePickerOnLeft;
81         [super windowDidLoad];
83         [nameView setFont:[NSFont fontWithName:@"Lucida Grande" size:12]];
85         //Configure the state menu
86         statusMenu = [[AIStatusMenu statusMenuWithDelegate:self] retain];
88         //Update the selections in our state menu when the active state changes
89         [[adium notificationCenter] addObserver:self
90                                                                    selector:@selector(updateStatusMenuSelection:)
91                                                                            name:AIStatusActiveStateChangedNotification
92                                                                          object:nil];
93         //Update our state menus when the status icon set changes
94         [[adium notificationCenter] addObserver:self
95                                                                    selector:@selector(updateStatusMenuSelection:)
96                                                                            name:AIStatusIconSetDidChangeNotification
97                                                                          object:nil];
98         [[adium notificationCenter] addObserver:self
99                                                                    selector:@selector(updateStatusMenuSelection:)
100                                                                            name:@"AIStatusFilteredStatusMessageChanged"
101                                                                          object:nil];
102         [self updateStatusMenuSelection:nil];
103         
104         [[adium notificationCenter] addObserver:self
105                                                                    selector:@selector(listObjectAttributesChanged:)
106                                                                            name:ListObject_AttributesChanged
107                                                                          object:nil];
108         
109         [[adium preferenceController] registerPreferenceObserver:self forGroup:GROUP_ACCOUNT_STATUS];
110         
111         //Set our minimum size here rather than in the nib to avoid conflicts with autosizing
112         [[self window] setMinSize:NSMakeSize(135, 60)];
113         
114         [searchField setDelegate:self];
116         [self _configureToolbar];
120  * @brief Window closing
121  */
122 - (void)windowWillClose:(id)sender
124         [[adium notificationCenter] removeObserver:self];
125         [searchField setDelegate:nil];
126         [statusMenu release];
127         
128         [super windowWillClose:sender];
131 - (void)positionImagePickerIfNeeded
133         LIST_POSITION                                   layoutUserIconPosition = [[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_USER_ICON_POSITION
134                                                                                                                                                                                  group:PREF_GROUP_LIST_LAYOUT] intValue];
135         ContactListImagePickerPosition  desiredImagePickerPosition;
136         
137         //Determine where we want the image picker now
138         switch (layoutUserIconPosition) {
139                 case LIST_POSITION_RIGHT:
140                 case LIST_POSITION_FAR_RIGHT:
141                 case LIST_POSITION_BADGE_RIGHT:
142                         desiredImagePickerPosition = ContactListImagePickerOnRight;
143                         break;
144                 case LIST_POSITION_NA:
145                 case LIST_POSITION_FAR_LEFT:
146                 case LIST_POSITION_LEFT:
147                 case LIST_POSITION_BADGE_LEFT:
148                 default:
149                         desiredImagePickerPosition = ContactListImagePickerOnLeft;
150                         break;                          
151         }
152         
153         
154         AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil ownIconAccounts:nil];
155         BOOL imagePickerIsVisible;
156         
157         if (activeAccount) {
158                 imagePickerIsVisible = ([activeAccount userIcon] != nil);
159         } else {
160                 imagePickerIsVisible = [[[adium preferenceController] preferenceForKey:KEY_USE_USER_ICON group:GROUP_ACCOUNT_STATUS] boolValue];
161         }
162         
163         if (!imagePickerIsVisible) {
164                 desiredImagePickerPosition = ((desiredImagePickerPosition == ContactListImagePickerOnLeft) ?
165                                                                           ContactListImagePickerHiddenOnLeft :
166                                                                           ContactListImagePickerHiddenOnRight);
167         }
168         
169         //Only proceed if this new position is different from the old one
170         if (desiredImagePickerPosition != imagePickerPosition) {
171                 [self repositionImagePickerToPosition:desiredImagePickerPosition];
172         }
175 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
176                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
178         if ([group isEqualToString:GROUP_ACCOUNT_STATUS]) {
179                 if ([key isEqualToString:KEY_USER_ICON] ||
180                         [key isEqualToString:KEY_DEFAULT_USER_ICON] || 
181                         [key isEqualToString:KEY_USE_USER_ICON] ||
182                         [key isEqualToString:@"Active Icon Selection Account"] ||
183                         firstTime) {
184                         [self updateImagePicker];
185                         [self positionImagePickerIfNeeded];
186                 }
187                 
188                 if ([key isEqualToString:@"Active Display Name Account"] ||
189                         firstTime) {
190                         [self updateNameView];
191                 }
192         }
194         /*
195          * We move our image picker to mirror the contact list's own layout
196          */
197         if ([group isEqualToString:PREF_GROUP_LIST_LAYOUT]) {
198                 [self positionImagePickerIfNeeded];
199         }
200         
201         [super preferencesChangedForGroup:group
202                                                                   key:key
203                                                            object:object
204                                            preferenceDict:prefDict
205                                                         firstTime:firstTime];
208 - (void)listObjectAttributesChanged:(NSNotification *)inNotification
210     AIListObject        *object = [inNotification object];
211         
212         if ([object isKindOfClass:[AIAccount class]] &&
213                 [[[inNotification userInfo] objectForKey:@"Keys"] containsObject:@"Display Name"]) {
214                 [self updateNameView];
215         }
219  * @brief Reposition the image picker to a desireed position
221  * This shifts the status picker view and the name view in the opposite direction, maintaining the same relative spacing relationships
222  */
223 - (void)repositionImagePickerToPosition:(ContactListImagePickerPosition)desiredImagePickerPosition
225         NSRect nameAndStatusMenuFrame = [view_nameAndStatusMenu frame];
226         NSRect newNameAndStatusMenuFrame = nameAndStatusMenuFrame;
227         
228         NSRect imagePickerFrame = [imagePicker frame];
229         NSRect newImagePickerFrame = imagePickerFrame;
230         
231         switch (desiredImagePickerPosition)
232         {
233                 case ContactListImagePickerOnLeft:
234                 case ContactListImagePickerHiddenOnLeft:
235                 {
236                         if ((imagePickerPosition == ContactListImagePickerOnRight) ||
237                                 (imagePickerPosition == ContactListImagePickerHiddenOnRight)) {
238                                 //Image picker is on the right but we want it on the left
239                                 newImagePickerFrame.origin.x = NSMinX(nameAndStatusMenuFrame);  
240                         }
242                         if (desiredImagePickerPosition == ContactListImagePickerOnLeft) {
243                                 if ((imagePickerPosition == ContactListImagePickerHiddenOnLeft) ||
244                                         (imagePickerPosition == ContactListImagePickerHiddenOnRight)) {
245                                         //Image picker was hidden but now is visible; shrink the name/status menu
246                                         newNameAndStatusMenuFrame.size.width -= NSWidth(newImagePickerFrame);
247                                         [imagePicker setHidden:NO];
248                                 }
250                                 newNameAndStatusMenuFrame.origin.x = NSMaxX(newImagePickerFrame);
252                         } else /* if (desiredImagePickerPosition == ContactListImagePickerHiddenOnLeft) */ {
253                                 if ((imagePickerPosition == ContactListImagePickerOnLeft) ||
254                                         (imagePickerPosition == ContactListImagePickerOnRight)) {
255                                         //Image picker was visible but now is hidden; expand the name/status menu
256                                         newNameAndStatusMenuFrame.size.width += NSWidth(newImagePickerFrame);
257                                         [imagePicker setHidden:YES];
258                                 }
260                                 newNameAndStatusMenuFrame.origin.x = NSMinX(newImagePickerFrame);
261                         }
262                         
263                         [imagePicker setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)];
265                         break;
266                 }
267                 case ContactListImagePickerOnRight:
268                 case ContactListImagePickerHiddenOnRight:
269                 {
270                         if (desiredImagePickerPosition == ContactListImagePickerOnRight) {
271                                 if ((imagePickerPosition == ContactListImagePickerHiddenOnLeft) ||
272                                         (imagePickerPosition == ContactListImagePickerHiddenOnRight)) {
273                                         //Image picker was hidden but not is visible; shrink the name/status menu
274                                         newNameAndStatusMenuFrame.size.width -= NSWidth(newImagePickerFrame);
275                                         [imagePicker setHidden:NO];     
276                                 }
277         
278                         } else /* if (desiredImagePickerPosition == ContactListImagePickerHiddenOnLeft) */ {
279                                 if ((imagePickerPosition == ContactListImagePickerOnLeft) ||
280                                         (imagePickerPosition == ContactListImagePickerOnRight)) {
281                                         //Image picker was visible but now is hidden; expand the name/status menu
282                                         newNameAndStatusMenuFrame.size.width += NSWidth(newImagePickerFrame);
283                                         [imagePicker setHidden:YES];
284                                 }
285                         }
286                         
287                         if ((imagePickerPosition == ContactListImagePickerOnLeft) ||
288                                 (imagePickerPosition == ContactListImagePickerHiddenOnLeft)) {
289                                 /* Image picker is on the left but we want it on the right. Positioning is frame relative, not name-and-status-menu relative,
290                                  * so we can position it the same regardless of hidden status. */
291                                 newImagePickerFrame.origin.x = (NSWidth([[imagePicker superview] frame]) - NSMaxX(imagePickerFrame));
292                                 newNameAndStatusMenuFrame.origin.x = NSMinX(imagePickerFrame);
293                         }
295                         [imagePicker setAutoresizingMask:(NSViewMinXMargin | NSViewMinYMargin)];
296                         break;
297                 }
298         }
300         [view_nameAndStatusMenu setFrame:newNameAndStatusMenuFrame];
301         [[nameView superview] setNeedsDisplayInRect:nameAndStatusMenuFrame];
302         [view_nameAndStatusMenu setNeedsDisplay:YES];
303         
304         [imagePicker setFrame:newImagePickerFrame];
305         [[imagePicker superview] setNeedsDisplayInRect:imagePickerFrame];
306         [imagePicker setNeedsDisplay:YES];
307         
308         imagePickerPosition = desiredImagePickerPosition;       
312 #pragma mark User icon changing
315  * @brief Determine the account which will be modified by a change to the image picker
317  * @result The 'active' account for image purposes, or nil if the global icon is active
318  */
319 + (AIAccount *)activeAccountForIconsGettingOnlineAccounts:(NSMutableSet *)onlineAccounts ownIconAccounts:(NSMutableSet *)ownIconAccounts
321         NSObject<AIAdium>       *sharedAdium = [AIObject sharedAdiumInstance];
322         AIAccount                       *account;
323         AIAccount                       *activeAccount = nil;
324         NSEnumerator            *enumerator;
325         BOOL                            atLeastOneOwnIconAccount = NO;
326         NSArray                         *accounts = [[sharedAdium accountController] accounts];
328         if (!onlineAccounts) onlineAccounts = [NSMutableSet set];
329         if (!ownIconAccounts) ownIconAccounts = [NSMutableSet set];
330         
331         //Figure out what accounts are online and what of those have their own custom icon
332         enumerator = [accounts objectEnumerator];
333         while ((account = [enumerator nextObject])) {
334                 if ([account online]) {
335                         [onlineAccounts addObject:account];
336                         if ([account preferenceForKey:KEY_USER_ICON group:GROUP_ACCOUNT_STATUS ignoreInheritedValues:YES]) {
337                                 [ownIconAccounts addObject:account];
338                                 atLeastOneOwnIconAccount = YES;
339                         }
340                 }
341         }
342         
343         //At least one account is using its own icon rather than the global preference
344         if (atLeastOneOwnIconAccount) {
345                 NSString        *accountID = [[sharedAdium preferenceController] preferenceForKey:@"Active Icon Selection Account"
346                                                                                                                                                                 group:GROUP_ACCOUNT_STATUS];
347                 
348                 activeAccount = (accountID ? [[sharedAdium accountController] accountWithInternalObjectID:accountID] : nil);
349                 
350                 //If the activeAccount isn't in ownIconAccounts we don't want anything to do with it
351                 if (![ownIconAccounts containsObject:activeAccount]) activeAccount = nil;
352                 
353                 /* However, if all accounts are using their own icon, we should return one of them.
354                  * Let's use the first one in the accounts list.
355                  */
356                 if (!activeAccount && ([ownIconAccounts count] == [onlineAccounts count])) {
357                         enumerator = [accounts objectEnumerator];
358                         while ((account = [enumerator nextObject])) {
359                                 if ([account online]) {
360                                         activeAccount = account;
361                                         break;
362                                 }
363                         }
364                 }
365         }
367         return activeAccount;
370 - (NSImage *)imageForImagePicker
372         AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil ownIconAccounts:nil];
373         NSImage   *image;
374         
375         if (activeAccount) {
376                 image = [activeAccount userIcon];
377         } else {
378                 NSData *data = [[adium preferenceController] preferenceForKey:KEY_USER_ICON group:GROUP_ACCOUNT_STATUS];
379                 if (!data) data = [[adium preferenceController] preferenceForKey:KEY_DEFAULT_USER_ICON group:GROUP_ACCOUNT_STATUS];
380                 
381                 image = [[[NSImage alloc] initWithData:data] autorelease];
382         }
384         return image;
387 - (void)updateImagePicker
389         [imagePicker setImage:[[self imageForImagePicker] imageByScalingToSize:[imagePicker frame].size]];
392 - (NSImage *)imageForImageViewWithImagePicker:(AIImageViewWithImagePicker *)picker
394         return [self imageForImagePicker];
398  * @brief The image picker changed images
399  */
400 - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)picker didChangeToImageData:(NSData *)imageData
402         AIAccount       *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil
403                                                                                                                                                   ownIconAccounts:nil];
405         if (activeAccount) {
406                 [activeAccount setPreference:imageData
407                                                           forKey:KEY_USER_ICON
408                                                            group:GROUP_ACCOUNT_STATUS];
410         } else {
411                 [[adium preferenceController] setPreference:imageData
412                                                                                          forKey:KEY_USER_ICON
413                                                                                           group:GROUP_ACCOUNT_STATUS];
414         }
417 #pragma mark Status menu
419 * @brief Add state menu items to our location
421  * Implemented as required by the StateMenuPlugin protocol.
423  * @param menuItemArray An <tt>NSArray</tt> of <tt>NSMenuItem</tt> objects to be added to the menu
424  */
425 - (void)statusMenu:(AIStatusMenu *)inStatusMenu didRebuildStatusMenuItems:(NSArray *)menuItemArray
427     NSMenu                      *menu = [[NSMenu alloc] init];
428         NSEnumerator    *enumerator = [menuItemArray objectEnumerator];
429         NSMenuItem              *menuItem;
430         
431         //Add a menu item for each state
432         while ((menuItem = [enumerator nextObject])) {
433                 [menu addItem:menuItem];
434         }
435         
436         [statusMenuView setMenu:menu];
437         [menu release];
441  * Update popup button to match selected menu item
442  */
443 - (void)updateStatusMenuSelection:(NSNotification *)notification
445         AIStatus        *activeStatus = [[adium statusController] activeStatusState];
446         NSString        *title = [activeStatus title];
447         if (!title) NSLog(@"Warning: Title for %@ is (null)",activeStatus);
449         [statusMenuView setTitle:(title ? title : @"")];
450         /*
451         [statusMenuView setImage:[activeStatus iconOfType:AIStatusIconList
452                                                                                         direction:AIIconFlipped]];
453          */
454         [imageView_status setImage:[activeStatus iconOfType:AIStatusIconList
455                                                                                           direction:AIIconNormal]];
456         [statusMenuView setToolTip:[activeStatus statusMessageTooltipString]];
458         [self updateImagePicker];
459         [self updateNameView];
462 #pragma mark Name view
465  * @brief Determine the account which will be displayed / modified by the name view
467  * @param onlineAccounts If non-nil, the NSMutableSet will have all online accounts
468  * @param onlineAccounts If non-nil, the NSMutableSet will have all online accounts with a per-account display name set
470  * @result The 'active' account for display name purposes, or nil if the global display name is active
471  */
472 + (AIAccount *)activeAccountForDisplayNameGettingOnlineAccounts:(NSMutableSet *)onlineAccounts ownDisplayNameAccounts:(NSMutableSet *)ownDisplayNameAccounts
474         NSObject<AIAdium>       *sharedAdium = [AIObject sharedAdiumInstance];
475         AIAccount                       *account;
476         AIAccount                       *activeAccount = nil;
477         NSEnumerator            *enumerator;
478         BOOL                            atLeastOneOwnDisplayNameAccount = NO;
479         NSArray                         *accounts = [[sharedAdium accountController] accounts];
480         
481         if (!onlineAccounts) onlineAccounts = [NSMutableSet set];
482         if (!ownDisplayNameAccounts) ownDisplayNameAccounts = [NSMutableSet set];
483         
484         //Figure out what accounts are online and what of those have their own custom display name
485         enumerator = [[[sharedAdium accountController] accounts] objectEnumerator];
486         while ((account = [enumerator nextObject])) {
487                 if ([account online]) {
488                         [onlineAccounts addObject:account];
489                         if ([[[account preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME group:GROUP_ACCOUNT_STATUS ignoreInheritedValues:YES] attributedString] length]) {
490                                 [ownDisplayNameAccounts addObject:account];
491                                 atLeastOneOwnDisplayNameAccount = YES;
492                         }
493                 }
494         }
495         
496         //At least one account is using its own display name rather than the global preference
497         if (atLeastOneOwnDisplayNameAccount) {
498                 NSString        *accountID = [[sharedAdium preferenceController] preferenceForKey:@"Active Display Name Account"
499                                                                                                                                                                 group:GROUP_ACCOUNT_STATUS];
500                 
501                 activeAccount = (accountID ? [[sharedAdium accountController] accountWithInternalObjectID:accountID] : nil);
502                 
503                 //If the activeAccount isn't in ownDisplayNameAccounts we don't want anything to do with it
504                 if (![ownDisplayNameAccounts containsObject:activeAccount]) activeAccount = nil;
505                 
506                 /* However, if all accounts are using their own display name, we should return one of them.
507                         * Let's use the first one in the accounts list.
508                         */
509                 if (!activeAccount && ([ownDisplayNameAccounts count] == [onlineAccounts count])) {
510                         enumerator = [accounts objectEnumerator];
511                         while ((account = [enumerator nextObject])) {
512                                 if ([account online]) {
513                                         activeAccount = account;
514                                         break;
515                                 }
516                         }
517                 }
518         }
519         
520         return activeAccount;
523 - (void)nameViewSelectedAccount:(id)sender
525         [[adium preferenceController] setPreference:[[sender representedObject] internalObjectID]
526                                                                                  forKey:@"Active Display Name Account"
527                                                                                   group:GROUP_ACCOUNT_STATUS];
530 - (void)nameView:(AIContactListNameButton *)inNameView didChangeToString:(NSString *)inName userInfo:(NSDictionary *)userInfo
532         AIAccount       *activeAccount = [userInfo objectForKey:@"activeAccount"];
533         NSData          *newDisplayName = ((inName && [inName length]) ?
534                                                                    [[NSAttributedString stringWithString:inName] dataRepresentation] :
535                                                                    nil);
537         if (activeAccount) {
538                 [activeAccount setPreference:newDisplayName
539                                                           forKey:KEY_ACCOUNT_DISPLAY_NAME
540                                                            group:GROUP_ACCOUNT_STATUS];
541         } else {
542                 [[adium preferenceController] setPreference:newDisplayName
543                                                                                          forKey:KEY_ACCOUNT_DISPLAY_NAME
544                                                                                           group:GROUP_ACCOUNT_STATUS];
545         }
548 - (void)nameViewChangeName:(id)sender
550         AIAccount       *activeAccount = [[self class] activeAccountForDisplayNameGettingOnlineAccounts:nil
551                                                                                                                                                   ownDisplayNameAccounts:nil];
552         NSString        *startingString = nil;
554         if (activeAccount) {
555                 startingString = [[[activeAccount preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
556                                                                                                          group:GROUP_ACCOUNT_STATUS] attributedString] string];         
558         } else {
559                 startingString = [[[[adium preferenceController] preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
560                                                                                                                                         group:GROUP_ACCOUNT_STATUS] attributedString] string];
561         }
563         NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
564         if (activeAccount) {
565                 [userInfo setObject:activeAccount
566                                          forKey:@"activeAccount"];
567         }
569         [nameView editNameStartingWithString:startingString
570                                                  notifyingTarget:self
571                                                                 selector:@selector(nameView:didChangeToString:userInfo:)
572                                                                 userInfo:userInfo];
575 - (NSMenu *)nameViewMenuWithActiveAccount:(AIAccount *)activeAccount accountsUsingOwnName:(NSSet *)ownDisplayNameAccounts onlineAccounts:(NSSet *)onlineAccounts
577         NSEnumerator *enumerator;
578         AIAccount *account;
579         NSMenu *menu = [[NSMenu alloc] init];
580         NSMenuItem *menuItem;
582         menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Display Name For:", nil)
583                                                                                   target:nil
584                                                                                   action:nil
585                                                                    keyEquivalent:@""];
586         [menuItem setEnabled:NO];
587         [menu addItem:menuItem];
588         [menuItem release];
589         
590         enumerator = [ownDisplayNameAccounts objectEnumerator];
591         while ((account = [enumerator nextObject])) {
592                 //Put a check before the account if it is the active account
593                 menuItem = [[NSMenuItem alloc] initWithTitle:[account formattedUID]
594                                                                                           target:self
595                                                                                           action:@selector(nameViewSelectedAccount:)
596                                                                            keyEquivalent:@""];
597                 [menuItem setRepresentedObject:account];
598                 [menuItem setImage:[AIServiceIcons serviceIconForObject:account type:AIServiceIconSmall direction:AIIconNormal]];
599                 
600                 if (activeAccount == account) {
601                         [menuItem setState:NSOnState];
602                 }
603                 [menuItem setIndentationLevel:1];
604                 [menu addItem:menuItem];
605                 
606                 [menuItem release];
607         }
608         
609         //Show "All Other Accounts" if some accounts are using the global preference
610         if ([ownDisplayNameAccounts count] != [onlineAccounts count]) {
611                 menuItem = [[NSMenuItem alloc] initWithTitle:ALL_OTHER_ACCOUNTS
612                                                                                           target:self
613                                                                                           action:@selector(nameViewSelectedAccount:)
614                                                                            keyEquivalent:@""];
615                 if (!activeAccount) {
616                         [menuItem setState:NSOnState];
617                 }
618                 [menuItem setIndentationLevel:1];
619                 [menu addItem:menuItem];
620                 [menuItem release];
621         }
622         
623         [menu addItem:[NSMenuItem separatorItem]];
624         
625         menuItem = [[NSMenuItem alloc] initWithTitle:[AILocalizedString(@"Change Display Name", nil) stringByAppendingEllipsis]
626                                                                                   target:self
627                                                                                   action:@selector(nameViewChangeName:)
628                                                                    keyEquivalent:@""];
629         [menu addItem:menuItem];
630         [menuItem release];     
631         
632         return [menu autorelease];
635 - (void)updateNameView
637         NSMutableSet *ownDisplayNameAccounts = [NSMutableSet set];
638         NSMutableSet *onlineAccounts = [NSMutableSet set];
639         AIAccount        *activeAccount = [[self class] activeAccountForDisplayNameGettingOnlineAccounts:onlineAccounts
640                                                                                                                                                   ownDisplayNameAccounts:ownDisplayNameAccounts];
641         NSString         *alias = nil;
643         if (activeAccount) {
644                 //There is a specific account active whose display name we should show
645                 alias = [activeAccount displayName];
646         } else {
647                 /* There isn't an account active. We should show the global preference if possible.  Using it directly would mean
648                  * that it displays exactly as typed by the user, whereas using it via an account's displayName means it is preprocessed
649                  * for any substitutions, which looks better.
650                 */
651                 NSMutableSet *onlineAccountsUsingGlobalPreference = [onlineAccounts mutableCopy];
652                 [onlineAccountsUsingGlobalPreference minusSet:ownDisplayNameAccounts];
653                 if ([onlineAccountsUsingGlobalPreference count]) {
654                         alias = [[onlineAccountsUsingGlobalPreference anyObject] displayName];
656                 } else {
657                         /* No online accounts... look for an enabled account using the global preference
658                          * 'cause we still want to use displayName if possible
659                          */
660                         NSEnumerator    *enumerator = [[[adium accountController] accounts] objectEnumerator];
661                         AIAccount               *account = nil;
662                         
663                         while ((account = [enumerator nextObject])) {
664                                 if ([account enabled] && 
665                                         ![[[account preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME 
666                                                                                    group:GROUP_ACCOUNT_STATUS
667                                                    ignoreInheritedValues:YES] attributedString] length]) break;
668                         }
670                         alias = [account displayName];
671                 }
673                 [onlineAccountsUsingGlobalPreference release];
674         }
675         
676         if ((!activeAccount && ![ownDisplayNameAccounts count]) || ([onlineAccounts count] == 1)) {
677                 //We're using the global preference, or we're the single online account has its own display name
678                 [nameView setHighlightOnHoverAndClick:NO];
679                 [nameView setTarget:self];
680                 [nameView setDoubleAction:@selector(nameViewChangeName:)];
681                 [nameView setMenu:nil];
682         } else {
683                 //Multiple possibilities, so we rock with a menu
684                 [nameView setHighlightOnHoverAndClick:YES];
685                 [nameView setDoubleAction:NULL];
686                 [nameView setMenu:[self nameViewMenuWithActiveAccount:activeAccount 
687                                                                                  accountsUsingOwnName:ownDisplayNameAccounts
688                                                                                            onlineAccounts:onlineAccounts]];
689         }
691         /* If we don't have an alias to display as our text yet, grab from the global preferences. This can be the case
692          * in a no-accounts-enabled situation.
693          */
694         if (!alias || ![alias length]) {
695                 alias = [[[[adium preferenceController] preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
696                                                                                                                    group:GROUP_ACCOUNT_STATUS] attributedString] string];
697                 if (!alias || ![alias length]) {
698                         alias = @"Adium";
699                 }
700         }
702         [nameView setTitle:alias];
703         [nameView setToolTip:alias];
706 #pragma mark Sliding
708 - (BOOL)keepListOnScreenWhenSliding
710         return YES;
713 //Toolbar --------------------------------------------------------------------------------------------------------------
714 #pragma mark Toolbar
715 //Install our toolbar
716 - (void)_configureToolbar
718     NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier:TOOLBAR_CONTACT_LIST] autorelease];
720         [toolbar setAutosavesConfiguration:YES];
721     [toolbar setDelegate:self];
722     [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
723     [toolbar setSizeMode:NSToolbarSizeModeSmall];
724     [toolbar setAllowsUserCustomization:NO];
726         /* Seemingly randomling, setToolbar: may throw:
727          * Exception:   NSInternalInconsistencyException
728          * Reason:              Uninitialized rectangle passed to [View initWithFrame:].
729          *
730          * With the same window positioning information as a user for whom this happens consistently, I can't reproduce. Let's
731          * fail to set the toolbar gracefully.
732          */
733         @try
734         {
735                 [[self window] setToolbar:toolbar];
736         }
737         @catch(id exc)
738         {
739                 NSLog(@"Warning: While setting the contact list's toolbar, exception %@ was thrown.", exc);
740         }
743 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
745         NSToolbarItem *statusAndIconItem = [[NSToolbarItem alloc] initWithItemIdentifier:@"StatusAndIcon"];
746         [statusAndIconItem setMinSize:NSMakeSize(100, [view_statusAndImage bounds].size.height)];
747         [statusAndIconItem setMaxSize:NSMakeSize(100000, [view_statusAndImage bounds].size.height)];
748         [statusAndIconItem setView:view_statusAndImage];
750         return [statusAndIconItem autorelease];
753 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
755     return [NSArray arrayWithObject:@"StatusAndIcon"];
758 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
760     return [NSArray arrayWithObject:@"StatusAndIcon"];
763 - (void)windowDidToggleToolbarShown:(NSWindow *)sender
765         [contactListController contactListDesiredSizeChanged];
768 - (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame
770         return [contactListController _desiredWindowFrameUsingDesiredWidth:YES
771                                                                                                                  desiredHeight:YES];
774 #pragma mark Filtering
776 static ESObjectWithStatus<AIContainingObject> *oldContactList = nil;
777 - (void)controlTextDidChange:(NSNotification *)aNotification
779         //XXX This is busted. I'm turning it off to protect the innocent. More considered removal should follow as discussed in #7832
780         return;
781         
782         NSSearchField *sender = [aNotification object];
783         NSString *queryString = [(NSSearchFieldCell *)[(NSTextField *)sender cell] stringValue];
784         if (!oldContactList) {
785                 oldContactList = [contactListController contactListRoot];
786                 [oldContactList retain];
787         }
788         if ([queryString isEqualToString:@""]) {
789                 [contactListController setHideRoot:YES];
790                 [contactListController setContactListRoot:oldContactList];
791                 [oldContactList release];
792                 oldContactList = nil;
793         } else {
794                 AIListSmartGroup *searchResults = [[AIListGroup alloc] initWithUID:AILocalizedString(@"Search Results", "Contact List Search Results")];
795                 [searchResults setDisplayName:AILocalizedString(@"Search Results", "Contact List Search Results")];
796                 AIListContact *contact;
797                 // recursively walk the contact list, because if we enumerate over the contactController's -allContacts method we end up with weird
798                 // duplicated contacts
799                 NSMutableArray *enumeratorStack = [NSMutableArray arrayWithObject:[[oldContactList containedObjects] objectEnumerator]];
800                 while ([enumeratorStack count] > 0) {
801                         while (( contact = [[enumeratorStack objectAtIndex:0] nextObject])) {
802                                 if ([contact isKindOfClass:[AIMetaContact class]])
803                                         [enumeratorStack insertObject:[[(AIMetaContact *)contact containedObjects] objectEnumerator] atIndex:0];
804                                 else if ([contact isKindOfClass:[AIListGroup class]])
805                                         [enumeratorStack insertObject:[[(AIListGroup *)contact containedObjects] objectEnumerator] atIndex:0];
806                                 else
807                                         if ([[contact account] online] && 
808                                         ([[contact displayName] rangeOfString:queryString options:NSCaseInsensitiveSearch].location != NSNotFound || 
809                                          [[contact UID] rangeOfString:queryString options:NSCaseInsensitiveSearch].location != NSNotFound)) {
810                                                 if ([[contact containingObject] isKindOfClass:[AIListGroup class]])
811                                                         [searchResults addObject:contact];
812                                                 else if ([[contact containingObject] isKindOfClass:[AIMetaContact class]])
813                                                         [searchResults addObject:[contact containingObject]];
814                                                 break;
815                                         }
816                         }
817                         [enumeratorStack removeObjectAtIndex:0];
818                 }
819                 [contactListController setContactListRoot:searchResults];
820                 [contactListController setHideRoot:NO];
821 #warning this really should get autoreleased....
822 //              [searchResults autorelease];
823         }
826 - (IBAction)activateFirstContact:(id)sender;
828         if([[(NSSearchFieldCell *)[(NSTextField *)sender cell] stringValue] length] == 0) return;
829         [contactListController performDefaultActionOnFirstItem];
832 @end