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