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/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;
51 @implementation AIStandardListWindowController
58 [[adium preferenceController] unregisterPreferenceObserver:self];
59 [[adium notificationCenter] removeObserver:self];
69 return @"ContactListWindow";
73 * @brief Window loaded
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
92 //Update our state menus when the status icon set changes
93 [[adium notificationCenter] addObserver:self
94 selector:@selector(updateStatusMenuSelection:)
95 name:AIStatusIconSetDidChangeNotification
97 [[adium notificationCenter] addObserver:self
98 selector:@selector(updateStatusMenuSelection:)
99 name:@"AIStatusFilteredStatusMessageChanged"
101 [self updateStatusMenuSelection:nil];
103 [[adium notificationCenter] addObserver:self
104 selector:@selector(listObjectAttributesChanged:)
105 name:ListObject_AttributesChanged
108 [[adium preferenceController] registerPreferenceObserver:self forGroup:GROUP_ACCOUNT_STATUS];
110 //Set our minimum size here rather than in the nib to avoid conflicts with autosizing
111 [[self window] setMinSize:NSMakeSize(135, 60)];
113 [searchField setDelegate:self];
115 [self _configureToolbar];
119 * @brief Window closing
121 - (void)windowWillClose:(id)sender
123 [[adium notificationCenter] removeObserver:self];
124 [searchField setDelegate:nil];
125 [statusMenu release];
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;
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;
143 case LIST_POSITION_NA:
144 case LIST_POSITION_FAR_LEFT:
145 case LIST_POSITION_LEFT:
146 case LIST_POSITION_BADGE_LEFT:
148 desiredImagePickerPosition = ContactListImagePickerOnLeft;
153 AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil ownIconAccounts:nil];
154 BOOL imagePickerIsVisible;
157 imagePickerIsVisible = ([activeAccount userIcon] != nil);
159 imagePickerIsVisible = [[[adium preferenceController] preferenceForKey:KEY_USE_USER_ICON group:GROUP_ACCOUNT_STATUS] boolValue];
162 if (!imagePickerIsVisible) {
163 desiredImagePickerPosition = ((desiredImagePickerPosition == ContactListImagePickerOnLeft) ?
164 ContactListImagePickerHiddenOnLeft :
165 ContactListImagePickerHiddenOnRight);
168 //Only proceed if this new position is different from the old one
169 if (desiredImagePickerPosition != imagePickerPosition) {
170 [self repositionImagePickerToPosition:desiredImagePickerPosition];
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"] ||
183 [self updateImagePicker];
184 [self positionImagePickerIfNeeded];
187 if ([key isEqualToString:@"Active Display Name Account"] ||
189 [self updateNameView];
194 * We move our image picker to mirror the contact list's own layout
196 if ([group isEqualToString:PREF_GROUP_LIST_LAYOUT]) {
197 [self positionImagePickerIfNeeded];
200 [super preferencesChangedForGroup:group
203 preferenceDict:prefDict
204 firstTime:firstTime];
207 - (void)listObjectAttributesChanged:(NSNotification *)inNotification
209 AIListObject *object = [inNotification object];
211 if ([object isKindOfClass:[AIAccount class]] &&
212 [[[inNotification userInfo] objectForKey:@"Keys"] containsObject:@"Display Name"]) {
213 [self updateNameView];
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
222 - (void)repositionImagePickerToPosition:(ContactListImagePickerPosition)desiredImagePickerPosition
224 NSRect nameAndStatusMenuFrame = [view_nameAndStatusMenu frame];
225 NSRect newNameAndStatusMenuFrame = nameAndStatusMenuFrame;
227 NSRect imagePickerFrame = [imagePicker frame];
228 NSRect newImagePickerFrame = imagePickerFrame;
230 switch (desiredImagePickerPosition)
232 case ContactListImagePickerOnLeft:
233 case ContactListImagePickerHiddenOnLeft:
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);
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];
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];
259 newNameAndStatusMenuFrame.origin.x = NSMinX(newImagePickerFrame);
262 [imagePicker setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)];
266 case ContactListImagePickerOnRight:
267 case ContactListImagePickerHiddenOnRight:
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];
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];
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);
294 [imagePicker setAutoresizingMask:(NSViewMinXMargin | NSViewMinYMargin)];
299 [view_nameAndStatusMenu setFrame:newNameAndStatusMenuFrame];
300 [[nameView superview] setNeedsDisplayInRect:nameAndStatusMenuFrame];
301 [view_nameAndStatusMenu setNeedsDisplay:YES];
303 [imagePicker setFrame:newImagePickerFrame];
304 [[imagePicker superview] setNeedsDisplayInRect:imagePickerFrame];
305 [imagePicker setNeedsDisplay:YES];
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
318 + (AIAccount *)activeAccountForIconsGettingOnlineAccounts:(NSMutableSet *)onlineAccounts ownIconAccounts:(NSMutableSet *)ownIconAccounts
320 NSObject<AIAdium> *sharedAdium = [AIObject sharedAdiumInstance];
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];
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;
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];
347 activeAccount = (accountID ? [[sharedAdium accountController] accountWithInternalObjectID:accountID] : nil);
349 //If the activeAccount isn't in ownIconAccounts we don't want anything to do with it
350 if (![ownIconAccounts containsObject:activeAccount]) activeAccount = nil;
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.
355 if (!activeAccount && ([ownIconAccounts count] == [onlineAccounts count])) {
356 enumerator = [accounts objectEnumerator];
357 while ((account = [enumerator nextObject])) {
358 if ([account online]) {
359 activeAccount = account;
366 return activeAccount;
369 - (void)updateImagePicker
371 AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil ownIconAccounts:nil];
375 image = [activeAccount userIcon];
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];
380 image = [[[NSImage alloc] initWithData:data] autorelease];
383 [imagePicker setImage:image];
387 * @brief The image picker changed images
389 - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)picker didChangeToImageData:(NSData *)imageData
391 AIAccount *activeAccount = [[self class] activeAccountForIconsGettingOnlineAccounts:nil
392 ownIconAccounts:nil];
395 [activeAccount setPreference:imageData
397 group:GROUP_ACCOUNT_STATUS];
400 [[adium preferenceController] setPreference:imageData
402 group:GROUP_ACCOUNT_STATUS];
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
414 - (void)statusMenu:(AIStatusMenu *)inStatusMenu didRebuildStatusMenuItems:(NSArray *)menuItemArray
416 NSMenu *menu = [[NSMenu alloc] init];
417 NSEnumerator *enumerator = [menuItemArray objectEnumerator];
418 NSMenuItem *menuItem;
420 //Add a menu item for each state
421 while ((menuItem = [enumerator nextObject])) {
422 [menu addItem:menuItem];
425 [statusMenuView setMenu:menu];
430 * Update popup button to match selected menu item
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 : @"")];
440 [statusMenuView setImage:[activeStatus iconOfType:AIStatusIconList
441 direction:AIIconFlipped]];
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
461 + (AIAccount *)activeAccountForDisplayNameGettingOnlineAccounts:(NSMutableSet *)onlineAccounts ownDisplayNameAccounts:(NSMutableSet *)ownDisplayNameAccounts
463 NSObject<AIAdium> *sharedAdium = [AIObject sharedAdiumInstance];
465 AIAccount *activeAccount = nil;
466 NSEnumerator *enumerator;
467 BOOL atLeastOneOwnDisplayNameAccount = NO;
468 NSArray *accounts = [[sharedAdium accountController] accounts];
470 if (!onlineAccounts) onlineAccounts = [NSMutableSet set];
471 if (!ownDisplayNameAccounts) ownDisplayNameAccounts = [NSMutableSet set];
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;
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];
490 activeAccount = (accountID ? [[sharedAdium accountController] accountWithInternalObjectID:accountID] : nil);
492 //If the activeAccount isn't in ownDisplayNameAccounts we don't want anything to do with it
493 if (![ownDisplayNameAccounts containsObject:activeAccount]) activeAccount = nil;
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.
498 if (!activeAccount && ([ownDisplayNameAccounts count] == [onlineAccounts count])) {
499 enumerator = [accounts objectEnumerator];
500 while ((account = [enumerator nextObject])) {
501 if ([account online]) {
502 activeAccount = account;
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] :
527 [activeAccount setPreference:newDisplayName
528 forKey:KEY_ACCOUNT_DISPLAY_NAME
529 group:GROUP_ACCOUNT_STATUS];
531 [[adium preferenceController] setPreference:newDisplayName
532 forKey:KEY_ACCOUNT_DISPLAY_NAME
533 group:GROUP_ACCOUNT_STATUS];
537 - (void)nameViewChangeName:(id)sender
539 AIAccount *activeAccount = [[self class] activeAccountForDisplayNameGettingOnlineAccounts:nil
540 ownDisplayNameAccounts:nil];
541 NSString *startingString = nil;
544 startingString = [[[activeAccount preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
545 group:GROUP_ACCOUNT_STATUS] attributedString] string];
548 startingString = [[[[adium preferenceController] preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
549 group:GROUP_ACCOUNT_STATUS] attributedString] string];
552 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
554 [userInfo setObject:activeAccount
555 forKey:@"activeAccount"];
558 [nameView editNameStartingWithString:startingString
560 selector:@selector(nameView:didChangeToString:userInfo:)
564 - (NSMenu *)nameViewMenuWithActiveAccount:(AIAccount *)activeAccount accountsUsingOwnName:(NSSet *)ownDisplayNameAccounts onlineAccounts:(NSSet *)onlineAccounts
566 NSEnumerator *enumerator;
568 NSMenu *menu = [[NSMenu alloc] init];
569 NSMenuItem *menuItem;
571 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Display Name For:", nil)
575 [menuItem setEnabled:NO];
576 [menu addItem:menuItem];
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]
584 action:@selector(nameViewSelectedAccount:)
586 [menuItem setRepresentedObject:account];
587 [menuItem setImage:[AIServiceIcons serviceIconForObject:account type:AIServiceIconSmall direction:AIIconNormal]];
589 if (activeAccount == account) {
590 [menuItem setState:NSOnState];
592 [menuItem setIndentationLevel:1];
593 [menu addItem:menuItem];
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
602 action:@selector(nameViewSelectedAccount:)
604 if (!activeAccount) {
605 [menuItem setState:NSOnState];
607 [menuItem setIndentationLevel:1];
608 [menu addItem:menuItem];
612 [menu addItem:[NSMenuItem separatorItem]];
614 menuItem = [[NSMenuItem alloc] initWithTitle:[AILocalizedString(@"Change Display Name", nil) stringByAppendingEllipsis]
616 action:@selector(nameViewChangeName:)
618 [menu addItem:menuItem];
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;
633 //There is a specific account active whose display name we should show
634 alias = [activeAccount displayName];
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.
640 NSMutableSet *onlineAccountsUsingGlobalPreference = [onlineAccounts mutableCopy];
641 [onlineAccountsUsingGlobalPreference minusSet:ownDisplayNameAccounts];
642 if ([onlineAccountsUsingGlobalPreference count]) {
643 alias = [[onlineAccountsUsingGlobalPreference anyObject] displayName];
646 /* No online accounts... look for an enabled account using the global preference
647 * 'cause we still want to use displayName if possible
649 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
650 AIAccount *account = nil;
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;
659 alias = [account displayName];
662 [onlineAccountsUsingGlobalPreference release];
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];
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]];
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.
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]) {
691 [nameView setTitle:alias];
692 [nameView setToolTip:alias];
697 - (BOOL)keepListOnScreenWhenSliding
702 //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:].
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.
724 [[self window] setToolbar:toolbar];
728 NSLog(@"Warning: While setting the contact list's toolbar, exception %@ was thrown.", exc);
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
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
771 NSSearchField *sender = [aNotification object];
772 NSString *queryString = [(NSSearchFieldCell *)[(NSTextField *)sender cell] stringValue];
773 if (!oldContactList) {
774 oldContactList = [contactListController contactListRoot];
775 [oldContactList retain];
777 if ([queryString isEqualToString:@""]) {
778 [contactListController setHideRoot:YES];
779 [contactListController setContactListRoot:oldContactList];
780 [oldContactList release];
781 oldContactList = nil;
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];
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]];
806 [enumeratorStack removeObjectAtIndex:0];
808 [contactListController setContactListRoot:searchResults];
809 [contactListController setHideRoot:NO];
810 #warning this really should get autoreleased....
811 // [searchResults autorelease];
815 - (IBAction)activateFirstContact:(id)sender;
817 if([[(NSSearchFieldCell *)[(NSTextField *)sender cell] stringValue] length] == 0) return;
818 [contactListController performDefaultActionOnFirstItem];