2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
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.
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.
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.
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;
52 @implementation AIStandardListWindowController
59 [[adium preferenceController] unregisterPreferenceObserver:self];
60 [[adium notificationCenter] removeObserver:self];
70 return @"ContactListWindow";
74 * @brief Window loaded
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
93 //Update our state menus when the status icon set changes
94 [[adium notificationCenter] addObserver:self
95 selector:@selector(updateStatusMenuSelection:)
96 name:AIStatusIconSetDidChangeNotification
98 [[adium notificationCenter] addObserver:self
99 selector:@selector(updateStatusMenuSelection:)
100 name:@"AIStatusFilteredStatusMessageChanged"
102 [self updateStatusMenuSelection:nil];
104 [[adium notificationCenter] addObserver:self
105 selector:@selector(listObjectAttributesChanged:)
106 name:ListObject_AttributesChanged
109 [[adium preferenceController] registerPreferenceObserver:self forGroup:GROUP_ACCOUNT_STATUS];
111 //Set our minimum size here rather than in the nib to avoid conflicts with autosizing
112 [[self window] setMinSize:NSMakeSize(135, 60)];
114 [searchField setDelegate:self];
116 [self _configureToolbar];
120 * @brief Window closing
122 - (void)windowWillClose:(id)sender
124 [[adium notificationCenter] removeObserver:self];
125 [searchField setDelegate:nil];
126 [statusMenu release];
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;
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;
144 case LIST_POSITION_NA:
145 case LIST_POSITION_FAR_LEFT:
146 case LIST_POSITION_LEFT:
147 case LIST_POSITION_BADGE_LEFT:
149 desiredImagePickerPosition = ContactListImagePickerOnLeft;
154 AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil ownIconAccounts:nil];
155 BOOL imagePickerIsVisible;
158 imagePickerIsVisible = ([activeAccount userIcon] != nil);
160 imagePickerIsVisible = [[[adium preferenceController] preferenceForKey:KEY_USE_USER_ICON group:GROUP_ACCOUNT_STATUS] boolValue];
163 if (!imagePickerIsVisible) {
164 desiredImagePickerPosition = ((desiredImagePickerPosition == ContactListImagePickerOnLeft) ?
165 ContactListImagePickerHiddenOnLeft :
166 ContactListImagePickerHiddenOnRight);
169 //Only proceed if this new position is different from the old one
170 if (desiredImagePickerPosition != imagePickerPosition) {
171 [self repositionImagePickerToPosition:desiredImagePickerPosition];
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"] ||
184 [self updateImagePicker];
185 [self positionImagePickerIfNeeded];
188 if ([key isEqualToString:@"Active Display Name Account"] ||
190 [self updateNameView];
195 * We move our image picker to mirror the contact list's own layout
197 if ([group isEqualToString:PREF_GROUP_LIST_LAYOUT]) {
198 [self positionImagePickerIfNeeded];
201 [super preferencesChangedForGroup:group
204 preferenceDict:prefDict
205 firstTime:firstTime];
208 - (void)listObjectAttributesChanged:(NSNotification *)inNotification
210 AIListObject *object = [inNotification object];
212 if ([object isKindOfClass:[AIAccount class]] &&
213 [[[inNotification userInfo] objectForKey:@"Keys"] containsObject:@"Display Name"]) {
214 [self updateNameView];
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
223 - (void)repositionImagePickerToPosition:(ContactListImagePickerPosition)desiredImagePickerPosition
225 NSRect nameAndStatusMenuFrame = [view_nameAndStatusMenu frame];
226 NSRect newNameAndStatusMenuFrame = nameAndStatusMenuFrame;
228 NSRect imagePickerFrame = [imagePicker frame];
229 NSRect newImagePickerFrame = imagePickerFrame;
231 switch (desiredImagePickerPosition)
233 case ContactListImagePickerOnLeft:
234 case ContactListImagePickerHiddenOnLeft:
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);
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];
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];
260 newNameAndStatusMenuFrame.origin.x = NSMinX(newImagePickerFrame);
263 [imagePicker setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)];
267 case ContactListImagePickerOnRight:
268 case ContactListImagePickerHiddenOnRight:
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];
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];
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);
295 [imagePicker setAutoresizingMask:(NSViewMinXMargin | NSViewMinYMargin)];
300 [view_nameAndStatusMenu setFrame:newNameAndStatusMenuFrame];
301 [[nameView superview] setNeedsDisplayInRect:nameAndStatusMenuFrame];
302 [view_nameAndStatusMenu setNeedsDisplay:YES];
304 [imagePicker setFrame:newImagePickerFrame];
305 [[imagePicker superview] setNeedsDisplayInRect:imagePickerFrame];
306 [imagePicker setNeedsDisplay:YES];
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
319 + (AIAccount *)activeAccountForIconsGettingOnlineAccounts:(NSMutableSet *)onlineAccounts ownIconAccounts:(NSMutableSet *)ownIconAccounts
321 NSObject<AIAdium> *sharedAdium = [AIObject sharedAdiumInstance];
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];
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;
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];
348 activeAccount = (accountID ? [[sharedAdium accountController] accountWithInternalObjectID:accountID] : nil);
350 //If the activeAccount isn't in ownIconAccounts we don't want anything to do with it
351 if (![ownIconAccounts containsObject:activeAccount]) activeAccount = nil;
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.
356 if (!activeAccount && ([ownIconAccounts count] == [onlineAccounts count])) {
357 enumerator = [accounts objectEnumerator];
358 while ((account = [enumerator nextObject])) {
359 if ([account online]) {
360 activeAccount = account;
367 return activeAccount;
370 - (NSImage *)imageForImagePicker
372 AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil ownIconAccounts:nil];
376 image = [activeAccount userIcon];
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];
381 image = [[[NSImage alloc] initWithData:data] autorelease];
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
400 - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)picker didChangeToImageData:(NSData *)imageData
402 AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil
403 ownIconAccounts:nil];
406 [activeAccount setPreference:imageData
408 group:GROUP_ACCOUNT_STATUS];
411 [[adium preferenceController] setPreference:imageData
413 group:GROUP_ACCOUNT_STATUS];
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
425 - (void)statusMenu:(AIStatusMenu *)inStatusMenu didRebuildStatusMenuItems:(NSArray *)menuItemArray
427 NSMenu *menu = [[NSMenu alloc] init];
428 NSEnumerator *enumerator = [menuItemArray objectEnumerator];
429 NSMenuItem *menuItem;
431 //Add a menu item for each state
432 while ((menuItem = [enumerator nextObject])) {
433 [menu addItem:menuItem];
436 [statusMenuView setMenu:menu];
441 * Update popup button to match selected menu item
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 : @"")];
451 [statusMenuView setImage:[activeStatus iconOfType:AIStatusIconList
452 direction:AIIconFlipped]];
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
472 + (AIAccount *)activeAccountForDisplayNameGettingOnlineAccounts:(NSMutableSet *)onlineAccounts ownDisplayNameAccounts:(NSMutableSet *)ownDisplayNameAccounts
474 NSObject<AIAdium> *sharedAdium = [AIObject sharedAdiumInstance];
476 AIAccount *activeAccount = nil;
477 NSEnumerator *enumerator;
478 BOOL atLeastOneOwnDisplayNameAccount = NO;
479 NSArray *accounts = [[sharedAdium accountController] accounts];
481 if (!onlineAccounts) onlineAccounts = [NSMutableSet set];
482 if (!ownDisplayNameAccounts) ownDisplayNameAccounts = [NSMutableSet set];
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;
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];
501 activeAccount = (accountID ? [[sharedAdium accountController] accountWithInternalObjectID:accountID] : nil);
503 //If the activeAccount isn't in ownDisplayNameAccounts we don't want anything to do with it
504 if (![ownDisplayNameAccounts containsObject:activeAccount]) activeAccount = nil;
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.
509 if (!activeAccount && ([ownDisplayNameAccounts count] == [onlineAccounts count])) {
510 enumerator = [accounts objectEnumerator];
511 while ((account = [enumerator nextObject])) {
512 if ([account online]) {
513 activeAccount = account;
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] :
538 [activeAccount setPreference:newDisplayName
539 forKey:KEY_ACCOUNT_DISPLAY_NAME
540 group:GROUP_ACCOUNT_STATUS];
542 [[adium preferenceController] setPreference:newDisplayName
543 forKey:KEY_ACCOUNT_DISPLAY_NAME
544 group:GROUP_ACCOUNT_STATUS];
548 - (void)nameViewChangeName:(id)sender
550 AIAccount *activeAccount = [[self class] activeAccountForDisplayNameGettingOnlineAccounts:nil
551 ownDisplayNameAccounts:nil];
552 NSString *startingString = nil;
555 startingString = [[[activeAccount preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
556 group:GROUP_ACCOUNT_STATUS] attributedString] string];
559 startingString = [[[[adium preferenceController] preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
560 group:GROUP_ACCOUNT_STATUS] attributedString] string];
563 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
565 [userInfo setObject:activeAccount
566 forKey:@"activeAccount"];
569 [nameView editNameStartingWithString:startingString
571 selector:@selector(nameView:didChangeToString:userInfo:)
575 - (NSMenu *)nameViewMenuWithActiveAccount:(AIAccount *)activeAccount accountsUsingOwnName:(NSSet *)ownDisplayNameAccounts onlineAccounts:(NSSet *)onlineAccounts
577 NSEnumerator *enumerator;
579 NSMenu *menu = [[NSMenu alloc] init];
580 NSMenuItem *menuItem;
582 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Display Name For:", nil)
586 [menuItem setEnabled:NO];
587 [menu addItem:menuItem];
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]
595 action:@selector(nameViewSelectedAccount:)
597 [menuItem setRepresentedObject:account];
598 [menuItem setImage:[AIServiceIcons serviceIconForObject:account type:AIServiceIconSmall direction:AIIconNormal]];
600 if (activeAccount == account) {
601 [menuItem setState:NSOnState];
603 [menuItem setIndentationLevel:1];
604 [menu addItem:menuItem];
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
613 action:@selector(nameViewSelectedAccount:)
615 if (!activeAccount) {
616 [menuItem setState:NSOnState];
618 [menuItem setIndentationLevel:1];
619 [menu addItem:menuItem];
623 [menu addItem:[NSMenuItem separatorItem]];
625 menuItem = [[NSMenuItem alloc] initWithTitle:[AILocalizedString(@"Change Display Name", nil) stringByAppendingEllipsis]
627 action:@selector(nameViewChangeName:)
629 [menu addItem:menuItem];
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;
644 //There is a specific account active whose display name we should show
645 alias = [activeAccount displayName];
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.
651 NSMutableSet *onlineAccountsUsingGlobalPreference = [onlineAccounts mutableCopy];
652 [onlineAccountsUsingGlobalPreference minusSet:ownDisplayNameAccounts];
653 if ([onlineAccountsUsingGlobalPreference count]) {
654 alias = [[onlineAccountsUsingGlobalPreference anyObject] displayName];
657 /* No online accounts... look for an enabled account using the global preference
658 * 'cause we still want to use displayName if possible
660 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
661 AIAccount *account = nil;
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;
670 alias = [account displayName];
673 [onlineAccountsUsingGlobalPreference release];
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];
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]];
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.
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]) {
702 [nameView setTitle:alias];
703 [nameView setToolTip:alias];
708 - (BOOL)keepListOnScreenWhenSliding
713 //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:].
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.
735 [[self window] setToolbar:toolbar];
739 NSLog(@"Warning: While setting the contact list's toolbar, exception %@ was thrown.", exc);
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
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
782 NSSearchField *sender = [aNotification object];
783 NSString *queryString = [(NSSearchFieldCell *)[(NSTextField *)sender cell] stringValue];
784 if (!oldContactList) {
785 oldContactList = [contactListController contactListRoot];
786 [oldContactList retain];
788 if ([queryString isEqualToString:@""]) {
789 [contactListController setHideRoot:YES];
790 [contactListController setContactListRoot:oldContactList];
791 [oldContactList release];
792 oldContactList = nil;
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];
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]];
817 [enumeratorStack removeObjectAtIndex:0];
819 [contactListController setContactListRoot:searchResults];
820 [contactListController setHideRoot:NO];
821 #warning this really should get autoreleased....
822 // [searchResults autorelease];
826 - (IBAction)activateFirstContact:(id)sender;
828 if([[(NSSearchFieldCell *)[(NSTextField *)sender cell] stringValue] length] == 0) return;
829 [contactListController performDefaultActionOnFirstItem];