* Adium is the legal property of its developers, whose names are listed in the copyright file included * with this source distribution. * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #import "AIInfoInspectorPane.h" #import <Adium/AIInterfaceControllerProtocol.h> #import <Adium/AIHTMLDecoder.h> #import <AIUtilities/AIDateFormatterAdditions.h> #import <AddressBook/AddressBook.h> #import <Adium/AIContactControllerProtocol.h> #import <Adium/AIStatusIcons.h> #import <Adium/AIListGroup.h> #import <Adium/AIListObject.h> #import <Adium/AIListContact.h> #import <Adium/AIListBookmark.h> #import <Adium/AIMetaContact.h> #import <Adium/AIService.h> #import <Adium/AIServiceIcons.h> #import <Adium/AIContentControllerProtocol.h> #import <AIUtilities/AIImageAdditions.h> #import <AIUtilities/AIAttributedStringAdditions.h> #import <AIUtilities/AIStringAdditions.h> #import <AIUtilities/AITextAttributes.h> #import "AIContactInfoImageViewWithImagePicker.h" #import <AIUtilities/AIDelayedTextField.h> #define WIDTH_PROFILE_HEADER 100.0f @interface AIInfoInspectorPane () - (void)updateUserIcon:(AIListObject *)inObject; - (void)updateAccountName:(AIListObject *)inObject; - (void)updateStatusIcon:(AIListObject *)inObject; - (void)updateAlias:(AIListObject *)inObject; - (void)addAddressBookInfoToProfileArray:(NSMutableArray *)profileArray forContact:(AIListContact *)inContact; - (void)addTooltipEntriesToProfileArray:(NSMutableArray *)profileArray forContact:(AIListContact *)inContact; - (NSAttributedString *)attributedStringProfileForListObject:(AIListObject *)inObject; - (void)updateProfile:(NSAttributedString *)infoString context:(AIListObject *)object; - (void)setAttributedString:(NSAttributedString *)infoString intoTextView:(NSTextView *)textView; @implementation AIInfoInspectorPane [NSBundle loadNibNamed:[self nibName] owner:self]; //Register as AIListObjectObserver [[AIContactObserverManager sharedManager] registerListObjectObserver:self]; [userIcon setAnimates:YES]; [userIcon setMaxSize:NSMakeSize(256,256)]; [userIcon setDelegate:self]; [aliasLabel setLocalizedString:AILocalizedString(@"Alias:","Label beside the field for a contact's alias in the settings tab of the Get Info window")]; [inspectorContentView release]; [[AIContactObserverManager sharedManager] unregisterListObjectObserver:self]; -(NSView *)inspectorContentView return inspectorContentView; return @"AIInfoInspectorPane"; -(void)updateForListObject:(AIListObject *)inObject [contactAlias fireImmediately]; displayedObject = inObject; [self updateUserIcon:inObject]; [self updateAccountName:inObject]; [self updateStatusIcon:inObject]; [self updateAlias:inObject]; if ([inObject isKindOfClass:[AIListContact class]] && ![inObject isKindOfClass:[AIListBookmark class]]) { [[AIContactObserverManager sharedManager] updateListContactStatus:(AIListContact *)inObject]; [profileProgress startAnimation:self]; [profileProgress setHidden:NO]; [self updateProfile:[self attributedStringProfileForListObject:inObject] - (void)updateUserIcon:(AIListObject *)inObject NSSize userIconSize, imagePickerSize; if (!(currentIcon = [inObject userIcon])) { currentIcon = [NSImage imageNamed:@"default-icon" forClass:[self class]]; /* NSScaleProportionally will lock an animated GIF into a single frame. We therefore use NSScaleNone if * we are already at the right size or smaller than the right size; otherwise we scale proportionally to userIconSize = [currentIcon size]; imagePickerSize = [userIcon frame].size; [userIcon setImageScaling:(((userIconSize.width <= imagePickerSize.width) && (userIconSize.height <= imagePickerSize.height)) ? [userIcon setImage:currentIcon]; [userIcon setTitle:(inObject ? [NSString stringWithFormat:AILocalizedString(@"%@'s Image",nil),inObject.displayName] : AILocalizedString(@"Image Picker",nil))]; //Show the reset image button if a preference is set on this object, overriding its serverside icon [userIcon setShowResetImageButton:([inObject preferenceForKey:KEY_USER_ICON group:PREF_GROUP_USERICONS] != nil)]; -(void)updateAccountName:(AIListObject *)inObject [accountName setStringValue:@""]; NSString *displayName = inObject.formattedUID; displayName = inObject.displayName; [accountName setStringValue:displayName]; -(void)updateStatusIcon:(AIListObject *)inObject if([inObject isKindOfClass:[AIListGroup class]]) { [statusImage setHidden:YES]; [statusImage setHidden:NO]; [statusImage setImage:[AIStatusIcons statusIconForListObject:inObject type:AIStatusIconList direction:AIIconNormal]]; #define KEY_VALUE @"Value" - (void)addAttributedString:(NSAttributedString *)string toTable:(NSTextTable *)table colspan:(NSInteger)colspan alignment:(NSTextAlignment)alignment toAttributedString:(NSMutableAttributedString *)text NSTextTableBlock *block = [[NSTextTableBlock alloc] initWithTable:table NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; NSInteger textLength = [text length]; [block setVerticalAlignment:NSTextBlockTopAlignment]; [block setWidth:5.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinYEdge]; [block setWidth:5.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxYEdge]; [block setWidth:1.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge]; [block setWidth:5.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge]; if (col == 0 && !header && colspan == 1) { [block setValue:WIDTH_PROFILE_HEADER type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockWidth]; [style setTextBlocks:[NSArray arrayWithObject:block]]; [style setAlignment:alignment]; [text appendAttributedString:string]; [text appendAttributedString:[NSAttributedString stringWithString:@"\n"]]; [text addAttribute:NSFontAttributeName value:[NSFont boldSystemFontOfSize:13] range:NSMakeRange(textLength, [text length] - textLength)]; [block setWidth:1.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMaxYEdge]; [block setBorderColor:[NSColor darkGrayColor]]; [text addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(textLength, [text length] - textLength)]; [text addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(textLength, [text length] - textLength)]; - (NSMutableArray *)metaContactProfileArrayForContact:(AIMetaContact *)metaContact NSMutableArray *array = [NSMutableArray array]; NSMutableDictionary *addedKeysDict = [NSMutableDictionary dictionary]; NSMutableDictionary *ownershipDict = [NSMutableDictionary dictionary]; NSArray *contacts = metaContact.online ? metaContact.uniqueContainedObjects : metaContact.listContactsIncludingOfflineAccounts; for (AIListContact *listContact in contacts) { //If one or more contacts are online, skip offline ones if (metaContact.online && !listContact.online) continue; for (NSDictionary *lineDict in listContact.profileArray) { NSString *key = [lineDict objectForKey:KEY_KEY]; AIUserInfoEntryType entryType = [[lineDict objectForKey:KEY_TYPE] intValue]; NSInteger insertionIndex = -1; case AIUserInfoSectionBreak: /* Skip double section breaks */ if ([[[array lastObject] objectForKey:KEY_TYPE] integerValue] == AIUserInfoSectionBreak) case AIUserInfoSectionHeader: /* Use the most recent header if we have multiple headers in a row */ if ([[[array lastObject] objectForKey:KEY_TYPE] integerValue] == AIUserInfoSectionHeader) [array removeLastObject]; case AIUserInfoLabelValuePair: NSMutableSet *previousDictValuesOnThisKey = [addedKeysDict objectForKey:key]; if (previousDictValuesOnThisKey) { /* If any previously added dictionary has the same key and value as the this new one, skip this new one entirely */ NSSet *existingValues = [previousDictValuesOnThisKey valueForKeyPath:[@"nonretainedObjectValue." stringByAppendingString:KEY_VALUE]]; if ([existingValues containsObject:[lineDict valueForKey:KEY_VALUE]]) for (NSValue *prevDictValue in [[previousDictValuesOnThisKey copy] autorelease]) { NSDictionary *prevDict = [prevDictValue nonretainedObjectValue]; NSMutableDictionary *newDict = [prevDict mutableCopy]; AIListContact *ownerOfPrevDict = [[ownershipDict objectForKey:prevDictValue] nonretainedObjectValue]; [newDict setObject:[NSString stringWithFormat:AILocalizedString(@"%@'s %@", nil), ownerOfPrevDict.formattedUID, //Array of dicts which will be returned insertionIndex = [array indexOfObjectIdenticalTo:prevDict]; [array replaceObjectAtIndex:insertionIndex //Known dictionaries on this key [previousDictValuesOnThisKey removeObject:prevDictValue]; [previousDictValuesOnThisKey addObject:[NSValue valueWithNonretainedObject:newDict]]; //Ownership of new dictionary [ownershipDict removeObjectForKey:prevDictValue]; [ownershipDict setObject:[NSValue valueWithNonretainedObject:newDict] forKey:[NSValue valueWithNonretainedObject:ownerOfPrevDict]]; NSMutableDictionary *newDict = [lineDict mutableCopy]; [newDict setObject:[NSString stringWithFormat:AILocalizedString(@"%@'s %@", "(name)'s (information type), e.g. tekjew's status"), listContact.formattedUID, lineDict = [newDict autorelease]; [previousDictValuesOnThisKey addObject:[NSValue valueWithNonretainedObject:lineDict]]; [addedKeysDict setObject:[NSMutableSet setWithObject:[NSValue valueWithNonretainedObject:lineDict]] if (insertionIndex != -1) { //Group items with the same key together if ([[[array objectAtIndex:insertionIndex] objectForKey:KEY_KEY] compare: [lineDict objectForKey:KEY_KEY]] == NSOrderedAscending) [array insertObject:lineDict atIndex:insertionIndex]; [array addObject:lineDict]; [ownershipDict setObject:[NSValue valueWithNonretainedObject:listContact] forKey:[NSValue valueWithNonretainedObject:lineDict]]; - (void)removeDuplicateEntriesFromProfileArray:(NSMutableArray *)array NSUInteger count = [array count]; for (i = 0; i < (count - 1); i++) { NSDictionary *lineDict = [array objectAtIndex:i]; //Look only for label/value pairs if ([[lineDict objectForKey:KEY_TYPE] integerValue] == AIUserInfoLabelValuePair) { NSString *thisKey = [[lineDict objectForKey:KEY_KEY] lowercaseString]; for (j = i + 1; j < count; j++) { NSDictionary *otherLineDict = [array objectAtIndex:j]; if (([[otherLineDict objectForKey:KEY_TYPE] integerValue] == AIUserInfoLabelValuePair) && [[[otherLineDict objectForKey:KEY_KEY] lowercaseString] isEqualToString:thisKey]) { /* Same key. Compare values, which may be NSString or NSAttributedString objects */ id thisValue = [lineDict objectForKey:KEY_VALUE]; id otherValue = [otherLineDict objectForKey:KEY_VALUE]; if ([thisValue isKindOfClass:[otherValue class]]) { /* Same class. Compare directly. */ if ([thisValue isEqual:otherValue]) { [array removeObjectAtIndex:j]; /* Different class. Go to NSAttributedString to compare. */ NSAttributedString *thisAttributedValue = ([thisValue isKindOfClass:[NSAttributedString class]] ? (thisValue ? [NSAttributedString stringWithString:thisValue] : nil)); NSAttributedString *otherAttributedValue = ([otherValue isKindOfClass:[NSAttributedString class]] ? (otherValue ? [NSAttributedString stringWithString:otherValue] : nil)); if ([thisAttributedValue isEqualToAttributedString:otherAttributedValue]) { [array removeObjectAtIndex:j]; - (NSAttributedString *)attributedStringProfileForListObject:(AIListObject *)inObject NSMutableArray *profileArray; // We don't know what to do for non-list contacts. if (![inObject isKindOfClass:[AIListContact class]]) { return [NSAttributedString stringWithString:@""]; // XXX Case out if we only have HTML (nothing currently does this) if ([inObject isKindOfClass:[AIMetaContact class]]) { profileArray = [self metaContactProfileArrayForContact:(AIMetaContact *)inObject]; profileArray = [[[(AIListContact *)inObject profileArray] mutableCopy] autorelease]; if (!profileArray) profileArray = [NSMutableArray array]; [self addTooltipEntriesToProfileArray:profileArray forContact:(AIListContact *)inObject]; [self addAddressBookInfoToProfileArray:profileArray forContact:(AIListContact *)inObject]; // Don't do anything if we have nothing to display. if ([profileArray count] == 0) { AILogWithSignature(@"No profile array items found for %@", inObject); return [NSAttributedString stringWithString:@""]; [self removeDuplicateEntriesFromProfileArray:profileArray]; NSTextTable *table = [[[NSTextTable alloc] init] autorelease]; [table setNumberOfColumns:2]; [table setLayoutAlgorithm:NSTextTableAutomaticLayoutAlgorithm]; [table setHidesEmptyCells:YES]; NSMutableAttributedString *result = [[[NSMutableAttributedString alloc] init] autorelease]; NSEnumerator *enumerator = [profileArray objectEnumerator]; BOOL shownAnyContent = NO; for (NSInteger row = 0; (lineDict = [enumerator nextObject]); row++) { if ([[lineDict objectForKey:KEY_TYPE] integerValue] == AIUserInfoSectionBreak && shownAnyContent == NO) { NSAttributedString *value = nil, *key = nil; if ([lineDict objectForKey:KEY_VALUE]) { id theValue = [lineDict objectForKey:KEY_VALUE]; if ([theValue isKindOfClass:[NSString class]]) { value = [AIHTMLDecoder decodeHTML:(NSString *)theValue]; } else if ([theValue isKindOfClass:[NSAttributedString class]]) { value = (NSAttributedString *)theValue; NSLog(@"*** WARNING! Invalid value passed in profile array: %@", lineDict); value = [adium.contentController filterAttributedString:value usingFilterType:AIFilterDisplay direction:AIFilterIncoming if ([lineDict objectForKey:KEY_KEY]) { // We don't need to filter the key. key = [NSAttributedString stringWithString:[[lineDict objectForKey:KEY_KEY] lowercaseString]]; switch ([[lineDict objectForKey:KEY_TYPE] integerValue]) { case AIUserInfoLabelValuePair: [self addAttributedString:key color:[NSColor grayColor] alignment:NSRightTextAlignment toAttributedString:result]; [self addAttributedString:value colspan:(key ? 1 : 2) /* If there's no key, we need to fill both columns. */ alignment:NSLeftTextAlignment toAttributedString:result]; case AIUserInfoSectionHeader: [self addAttributedString:key color:[NSColor darkGrayColor] alignment:NSLeftTextAlignment toAttributedString:result]; case AIUserInfoSectionBreak: [self addAttributedString:[NSAttributedString stringWithString:@" "] color:[NSColor controlTextColor] alignment:NSLeftTextAlignment toAttributedString:result]; - (void)updateAlias:(AIListObject *)inObject NSString *currentAlias = nil; if ([inObject isKindOfClass:[AIListContact class]]) { AIListContact *parentContact = [(AIListContact *)inObject parentContact]; currentAlias = [parentContact preferenceForKey:@"Alias" group:PREF_GROUP_ALIASES]; if (inObject == parentContact) { AILogWithSignature(@"%@: current alias %@.", AILogWithSignature(@"updating alias for %@; parent %@ --> current alias %@.", inObject, [(AIListContact *)inObject parentContact], currentAlias); currentAlias = [inObject preferenceForKey:@"Alias" group:PREF_GROUP_ALIASES]; if (!currentAlias && ![inObject.displayName isEqualToString:inObject.formattedUID]) { [[contactAlias cell] setPlaceholderString:inObject.displayName]; [[contactAlias cell] setPlaceholderString:nil]; //Fill in the current alias [contactAlias setStringValue:currentAlias]; [contactAlias setStringValue:@""]; - (IBAction)setAlias:(id)sender AIListObject *contactToUpdate = displayedObject; if ([contactToUpdate isKindOfClass:[AIListContact class]]) { contactToUpdate = [(AIListContact *)contactToUpdate parentContact]; NSString *currentAlias = [contactAlias stringValue]; [contactToUpdate setDisplayName:currentAlias]; [self updateAccountName:displayedObject]; - (void)updateProfile:(NSAttributedString *)infoString context:(AIListObject *)object [profileProgress stopAnimation:self]; [profileProgress setHidden:YES]; [self setAttributedString:infoString intoTextView:profileView]; - (void)setAttributedString:(NSAttributedString *)infoString intoTextView:(NSTextView *)textView NSColor *backgroundColor = nil; if (infoString && [infoString length]) { [[textView textStorage] setAttributedString:infoString]; backgroundColor = [infoString attribute:AIBodyColorAttributeName longestEffectiveRange:nil inRange:NSMakeRange(0,[infoString length])]; [[textView textStorage] setAttributedString:[NSAttributedString stringWithString:@""]]; [textView setInsertionPointColor:(backgroundColor ? backgroundColor : [NSColor whiteColor])]; [textView setBackgroundColor:(backgroundColor ? backgroundColor : [NSColor whiteColor])]; [[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidChangeNotification object:textView]; - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent //Update if our object or an object contained by our metacontact (if applicable) was updated if ([displayedObject isKindOfClass:[AIMetaContact class]] && ((inObject != displayedObject) && ![(AIMetaContact *)displayedObject containsObject:inObject])) else if (inObject != displayedObject) // Update the status icon if it changes. if (inModifiedKeys == nil || [inModifiedKeys containsObject:@"isOnline"] || [inModifiedKeys containsObject:@"idleSince"] || [inModifiedKeys containsObject:@"signedOff"] || [inModifiedKeys containsObject:@"isMobile"] || [inModifiedKeys containsObject:@"IsBlocked"] || [inModifiedKeys containsObject:@"listObjectStatusType"]) { [self updateStatusIcon:displayedObject]; // Update the profile if it changes. if (inModifiedKeys == nil || [inModifiedKeys containsObject:@"ProfileArray"]) { [self updateProfile:[self attributedStringProfileForListObject:displayedObject] context:displayedObject]; // Cause everything to update if everything's probably changed. if ([inModifiedKeys containsObject:@"notAStranger"] || [inModifiedKeys containsObject:@"serverDisplayName"]) { [self updateForListObject:displayedObject]; #pragma mark AIImageViewWithImagePicker Delegate // AIImageViewWithImagePicker Delegate --------------------------------------------------------------------- - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)sender didChangeToImageData:(NSData *)imageData [displayedObject setUserIconData:imageData]; [self updateUserIcon:displayedObject]; - (void)deleteInImageViewWithImagePicker:(AIImageViewWithImagePicker *)sender [displayedObject setUserIconData:nil]; [self updateUserIcon:displayedObject]; If the userIcon was bigger than our image view's frame, it will have been clipped before being passed to the AIImageViewWithImagePicker. This delegate method lets us pass the original, unmodified userIcon. - (NSImage *)imageForImageViewWithImagePicker:(AIImageViewWithImagePicker *)picker return ([displayedObject userIcon]); - (NSImage *)emptyPictureImageForImageViewWithImagePicker:(AIImageViewWithImagePicker *)picker return [AIServiceIcons serviceIconForObject:displayedObject type:AIServiceIconLarge direction:AIIconNormal]; - (NSString *)fileNameForImageInImagePicker:(AIImageViewWithImagePicker *)picker NSString *fileName = [displayedObject.displayName safeFilenameString]; if ([fileName hasPrefix:@"."]) { fileName = [fileName substringFromIndex:1]; #pragma mark Address Book - (void)addMultiValue:(ABMultiValue *)value forProperty:(NSString *)property ofType:(ABPropertyType)propertyType toProfileArray:(NSMutableArray *)profileArray NSUInteger count = [value count]; for (i = 0; i < count; i++) { NSString *label = ABLocalizedPropertyOrLabel([value labelAtIndex:i]); id innerValue = [value valueAtIndex:i]; case kABMultiStringProperty: if ([(NSString *)innerValue length]) { [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:@"%@ (%@)", ABLocalizedPropertyOrLabel(property), label], KEY_KEY, (NSString *)innerValue, KEY_VALUE, case kABMultiIntegerProperty: case kABMultiRealProperty: if ([(NSNumber *)innerValue integerValue] != 0) { [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:@"%@ (%@)", ABLocalizedPropertyOrLabel(property), label], KEY_KEY, [(NSNumber *)innerValue stringValue], KEY_VALUE, case kABMultiDateProperty: [NSDateFormatter withLocalizedDateFormatterPerform:^(NSDateFormatter *dateFormatter){ [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:@"%@ (%@)", ABLocalizedPropertyOrLabel(property), label], KEY_KEY, [dateFormatter stringFromDate:(NSDate *)innerValue], KEY_VALUE, case kABMultiArrayProperty: case kABMultiDictionaryProperty: case kABMultiDataProperty: /* Ignore Array, Dictionary, and Data properties */ - (void)addAddressBookInfoToProfileArray:(NSMutableArray *)profileArray forContact:(AIListContact *)inContact ABPerson *person = [inContact addressBookPerson]; NSString *title = [person valueForProperty:kABTitleProperty]; NSString *firstName = [person valueForProperty:kABFirstNameProperty]; NSString *middleName = [person valueForProperty:kABMiddleNameProperty]; NSString *lastName = [person valueForProperty:kABLastNameProperty]; NSString *suffix = [person valueForProperty:kABSuffixProperty]; NSMutableString *name = [NSMutableString string]; [name appendString:title]; if (firstName || middleName || lastName) [name appendString:@" "]; [name appendString:firstName]; if (middleName || lastName) [name appendString:@" "]; [name appendString:middleName]; [name appendString:@" "]; [name appendString:lastName]; [name appendString:@", "]; [name appendString:suffix]; [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: AILocalizedString(@"Full Name", nil), KEY_KEY, NSArray *propertiesToInclude; propertiesToInclude = [NSArray arrayWithObjects: kABJobTitleProperty, kABDepartmentProperty, kABOrganizationProperty, /* Work info */ kABHomePageProperty, kABURLsProperty, /* Web sites */ kABEmailProperty, kABPhoneProperty, kABAddressProperty, /* Contact info */ kABBirthdayProperty, kABOtherDatesProperty, /* Dates */ kABRelatedNamesProperty, /* Relationships */ kABNoteProperty, /* Notes */ for (property in propertiesToInclude) { if ([person valueForProperty:property]) { id value = [person valueForProperty:property]; ABPropertyType propertyType = [ABPerson typeOfProperty:property]; [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: ABLocalizedPropertyOrLabel(property), KEY_KEY, (NSString *)value, KEY_VALUE, if ([value integerValue] != 0) { [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: ABLocalizedPropertyOrLabel(property), KEY_KEY, [(NSNumber *)value stringValue], KEY_VALUE, [NSDateFormatter withLocalizedDateFormatterPerform:^(NSDateFormatter *dateFormatter){ [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: ABLocalizedPropertyOrLabel(property), KEY_KEY, [dateFormatter stringFromDate:(NSDate *)value], KEY_VALUE, case kABDictionaryProperty: /* Ignore arrays, dictionaries, and data */ case kABMultiStringProperty: case kABMultiIntegerProperty: case kABMultiRealProperty: case kABMultiDateProperty: case kABMultiArrayProperty: case kABMultiDictionaryProperty: case kABMultiDataProperty: [self addMultiValue:value forProperty:property ofType:propertyType toProfileArray:profileArray]; - (void)addTooltipEntriesToProfileArray:(NSMutableArray *)profileArray forContact:(AIListContact *)inContact NSArray *tooltipEntries = [[adium.interfaceController contactListTooltipPrimaryEntries] arrayByAddingObjectsFromArray:[adium.interfaceController contactListTooltipSecondaryEntries]]; for (id <AIContactListTooltipEntry> tooltipEntry in tooltipEntries) { if ([tooltipEntry shouldDisplayInContactInspector]) { if ((label = [tooltipEntry labelForObject:inContact]) && (value = [tooltipEntry entryForObject:inContact])) { [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: