William Bowling is sharing code with you

Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.

Don't show this again

wbowling / adium (fork of adium / adium)

Fork of Adium for patches/improvements

Clone this repository (size: 338.7 MB): HTTPS / SSH
hg clone https://bitbucket.org/wbowling/adium
hg clone ssh://hg@bitbucket.org/wbowling/adium

adium / Source / AIInfoInspectorPane.m

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
//
//  AIInfoInspectorPane.m
//  Adium
//
//  Created by Elliott Harris on 1/16/08.
//  Copyright 2008 Adium. All rights reserved.
//

#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)updateServiceIcon:(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;
@end

@implementation AIInfoInspectorPane

- (id) init
{
        self = [super init];
        if (self != nil) {
                //Load Bundle
                [NSBundle loadNibNamed:[self nibName] owner:self];
                //Register as AIListObjectObserver
                [[AIContactObserverManager sharedManager] registerListObjectObserver:self];
                //Setup for userIcon
                [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 Infow indow")];
        }
        return self;
}

- (void) dealloc
{
        [inspectorContentView release];
        
        [[AIContactObserverManager sharedManager] unregisterListObjectObserver:self];

        [super dealloc];
}


-(NSView *)inspectorContentView
{
        return inspectorContentView;
}

-(NSString *)nibName
{
        return @"AIInfoInspectorPane";
}

-(void)updateForListObject:(AIListObject *)inObject
{
        [contactAlias fireImmediately];
        
        displayedObject = inObject;
        
        [self updateProfile:nil
                                context:inObject];
        
        [self updateUserIcon:inObject];
        [self updateAccountName:inObject];
        [self updateServiceIcon: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]
                                        context:inObject];
        }
}

- (void)updateUserIcon:(AIListObject *)inObject
{
        NSImage         *currentIcon;
        NSSize          userIconSize, imagePickerSize;

        //User Icon
        if (!(currentIcon = [inObject userIcon])) {
                currentIcon = [NSImage imageNamed:@"DefaultIcon" 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
         * fit the frame.
         */
        userIconSize = [currentIcon size];
        imagePickerSize = [userIcon frame].size;
        
        [userIcon setImageScaling:(((userIconSize.width <= imagePickerSize.width) && (userIconSize.height <= imagePickerSize.height)) ?
                                                                                 NSScaleNone :
                                                                                 NSScaleProportionally)];
        [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
{
        if(!inObject) {
                [accountName setStringValue:@""];
                return;
        }
        
        NSString *displayName = inObject.formattedUID;
        
        if (!displayName) {
                displayName = inObject.displayName;
        }
        
        [accountName setStringValue:displayName];
}

-(void)updateStatusIcon:(AIListObject *)inObject
{
        if([inObject isKindOfClass:[AIListGroup class]]) {
                [statusImage setHidden:YES];
        } else {
                [statusImage setHidden:NO];
                [statusImage setImage:[AIStatusIcons statusIconForListObject:inObject type:AIStatusIconList direction:AIIconNormal]];
        }
}

-(void)updateServiceIcon:(AIListObject *)inObject
{
        if([inObject isKindOfClass:[AIListGroup class]]) {
                [serviceImage setHidden:YES];
        } else {
                [serviceImage setHidden:NO];
                [serviceImage setImage:[AIServiceIcons serviceIconForObject:inObject type:AIServiceIconSmall direction:AIIconNormal]];
        }
}

#define KEY_KEY         @"Key"
#define KEY_VALUE       @"Value"
#define KEY_TYPE        @"Type"

- (void)addAttributedString:(NSAttributedString *)string
                                        toTable:(NSTextTable *)table
                                                row:(NSInteger)row
                                                col:(NSInteger)col
                                        colspan:(NSInteger)colspan
                                         header:(BOOL)header
                                          color:(NSColor *)color
                                  alignment:(NSTextAlignment)alignment
                 toAttributedString:(NSMutableAttributedString *)text
{
        NSTextTableBlock                *block = [[NSTextTableBlock alloc] initWithTable:table
                                                                                                                   startingRow:row
                                                                                                                           rowSpan:1
                                                                                                                startingColumn:col
                                                                                                                        columnSpan:colspan];
        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"]];
        
        if (header) {
                [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]];
        } 
        
        if (color) {
                [text addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(textLength, [text length] - textLength)];
        }
    [text addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(textLength, [text length] - textLength)];
        
    [style release];
    [block release];
        
}


- (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] integerValue];
                        NSInteger insertionIndex = -1;
        
                        switch (entryType) {
                                case AIUserInfoSectionBreak:
                                        /* Skip double section breaks */
                                        if ([[[array lastObject] objectForKey:KEY_TYPE] integerValue] == AIUserInfoSectionBreak)
                                                continue;
                                        break;
                                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];
                                        break;
                                case AIUserInfoLabelValuePair:
                                                /* No action needed */
                                        break;
                        }
                        
                        if (key) {
                                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]])
                                                continue;

                                        for (NSValue *prevDictValue in [[previousDictValuesOnThisKey copy] autorelease]) {
                                                NSDictionary            *prevDict = [prevDictValue nonretainedObjectValue];
                                                NSMutableDictionary *newDict = [prevDict mutableCopy];
                                                AIListContact *ownerOfPrevDict = [[ownershipDict objectForKey:prevDictValue] nonretainedObjectValue];
                                                if (ownerOfPrevDict) {
                                                        [newDict setObject:[NSString stringWithFormat:AILocalizedString(@"%@'s %@", nil),
                                                                                                ownerOfPrevDict.formattedUID,
                                                                                                key]
                                                                                forKey:KEY_KEY];
                                                }
                                                
                                                //Array of dicts which will be returned
                                                insertionIndex = [array indexOfObjectIdenticalTo:prevDict];
                                                [array replaceObjectAtIndex:insertionIndex
                                                                                 withObject:newDict];
                                                
                                                //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]];
                                                [newDict release];
                                        }
                                        
                                        NSMutableDictionary *newDict = [lineDict mutableCopy];
                                        [newDict setObject:[NSString stringWithFormat:AILocalizedString(@"%@'s %@", "(name)'s (information type), e.g. tekjew's status"),
                                                                                listContact.formattedUID,
                                                                                key]
                                                                forKey:KEY_KEY];                                        
                                        lineDict = [newDict autorelease];
                                        
                                        [previousDictValuesOnThisKey addObject:[NSValue valueWithNonretainedObject:lineDict]];

                                } else {
                                        [addedKeysDict setObject:[NSMutableSet setWithObject:[NSValue valueWithNonretainedObject:lineDict]]
                                                                          forKey:key];
                                }
                        }
                        
                        if (lineDict) {
                                if (insertionIndex != -1) {
                                        //Group items with the same key together
                                        if ([[[array objectAtIndex:insertionIndex] objectForKey:KEY_KEY] compare:
                                                [lineDict objectForKey:KEY_KEY]] == NSOrderedAscending)
                                                insertionIndex++;

                                        [array insertObject:lineDict atIndex:insertionIndex];                                   
                                } else {
                                        [array addObject:lineDict];
                                }
                                
                                [ownershipDict setObject:[NSValue valueWithNonretainedObject:listContact]
                                                                  forKey:[NSValue valueWithNonretainedObject:lineDict]];
                        }
                }
        }

        return array;
}

- (void)removeDuplicateEntriesFromProfileArray:(NSMutableArray *)array
{
        NSInteger i;
        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) {
                        NSInteger j;
                        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];
                                                        count--;
                                                }
                                        } else {
                                                /* Different class. Go to NSAttributedString to compare. */
                                                NSAttributedString *thisAttributedValue = ([thisValue isKindOfClass:[NSAttributedString class]] ?
                                                                                                                                   thisValue :
                                                                                                                                   (thisValue ? [NSAttributedString stringWithString:thisValue] : nil));
                                                NSAttributedString *otherAttributedValue = ([otherValue isKindOfClass:[NSAttributedString class]] ?
                                                                                                                                   otherValue :
                                                                                                                                        (otherValue ? [NSAttributedString stringWithString:otherValue] : nil));
                                                if ([thisAttributedValue isEqualToAttributedString:otherAttributedValue]) {
                                                        [array removeObjectAtIndex:j];
                                                        count--;
                                                }
                                        }
                                }                                       
                        }
                }
        }
}

- (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];
        } else {
                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 nil;
        }
        
        [self removeDuplicateEntriesFromProfileArray:profileArray];
        
        // Create the table
        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];
        NSDictionary                                    *lineDict;
        
        BOOL                                                    shownAnyContent = NO;
        
        for (NSInteger row = 0; (lineDict = [enumerator nextObject]); row++) {
                if ([[lineDict objectForKey:KEY_TYPE] integerValue] == AIUserInfoSectionBreak && shownAnyContent == NO) {
                        continue;
                }
                
                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;
                        } else {
                                NSLog(@"*** WARNING! Invalid value passed in profile array: %@", lineDict);
                        }

                        value = [adium.contentController filterAttributedString:value
                                                                                                usingFilterType:AIFilterDisplay
                                                                                                          direction:AIFilterIncoming
                                                                                                                context:inObject];
                }
                
                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:
                                if (key) {
                                        [self addAttributedString:key
                                                                          toTable:table
                                                                                  row:row
                                                                                  col:0
                                                                          colspan:1
                                                                           header:NO
                                                                                color:[NSColor grayColor]
                                                                        alignment:NSRightTextAlignment
                                                   toAttributedString:result];
                                }
                                
                                if (value) {
                                        [self addAttributedString:value
                                                                          toTable:table
                                                                                  row:row
                                                                                  col:(key ? 1 : 0)
                                                                          colspan:(key ? 1 : 2) /* If there's no key, we need to fill both columns. */
                                                                           header:NO
                                                                                color:nil
                                                                        alignment:NSLeftTextAlignment
                                                   toAttributedString:result];
                                }
                                break;
                                
                        case AIUserInfoSectionHeader:
                                [self addAttributedString:key
                                                                  toTable:table
                                                                          row:row
                                                                          col:0
                                                                  colspan:2
                                                                   header:YES
                                                                        color:[NSColor darkGrayColor]
                                                                alignment:NSLeftTextAlignment
                                           toAttributedString:result];
                                break;
                                
                                
                        case AIUserInfoSectionBreak:
                                [self addAttributedString:[NSAttributedString stringWithString:@" "]
                                                                  toTable:table
                                                                          row:row
                                                                          col:0
                                                                  colspan:2
                                                                   header:NO
                                                                        color:[NSColor controlTextColor]
                                                                alignment:NSLeftTextAlignment
                                           toAttributedString:result];
                                break;
                }
                
                shownAnyContent = YES;
        }
        
        return result;
}

- (void)updateAlias:(AIListObject *)inObject
{
        NSString *currentAlias = nil;
        
        
        if ([inObject isKindOfClass:[AIListContact class]]) {
                currentAlias = [[(AIListContact *)inObject parentContact] preferenceForKey:@"Alias"
                                                                                                                                                         group:PREF_GROUP_ALIASES];
                AILogWithSignature(@"inObject is %@; parentContact is %@. Preference is %@.",
                                                   inObject, [(AIListContact *)inObject parentContact], currentAlias);
        } else {
                currentAlias = [inObject preferenceForKey:@"Alias"
                                                                                        group:PREF_GROUP_ALIASES];              
        }
        
        if (!currentAlias && ![inObject.displayName isEqualToString:inObject.formattedUID]) {
                [[contactAlias cell] setPlaceholderString:inObject.displayName];
        } else {
                [[contactAlias cell] setPlaceholderString:nil];
        }
        
        //Fill in the current alias
        if (currentAlias) {
                [contactAlias setStringValue:currentAlias];
        } else {
                [contactAlias setStringValue:@""];
        }
}

- (IBAction)setAlias:(id)sender
{
        if(!displayedObject)
                return;
        
        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
{
        if (infoString) {
                [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
                                                                                atIndex:0 
                                                  longestEffectiveRange:nil 
                                                                                inRange:NSMakeRange(0,[infoString length])];
        } else {
                [[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]))
                return nil;
        else if (inObject != displayedObject)
                return nil;
        
        // Update the status icon if it changes.
        if (inModifiedKeys == nil ||
                [inModifiedKeys containsObject:@"Online"] ||
                [inModifiedKeys containsObject:@"IdleSince"] ||
                [inModifiedKeys containsObject:@"Signed Off"] ||
                [inModifiedKeys containsObject:@"IsMobile"] ||
                [inModifiedKeys containsObject:@"IsBlocked"] ||
                [inModifiedKeys containsObject:@"StatusType"]) {
                [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:@"Server Display Name"]) {
                [self updateForListObject:displayedObject];
        }
        
        return nil;
}

#pragma mark AIImageViewWithImagePicker Delegate
// AIImageViewWithImagePicker Delegate ---------------------------------------------------------------------
- (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)sender didChangeToImageData:(NSData *)imageData
{
        if (displayedObject) {
                [displayedObject setUserIconData:imageData];
        }
        
        [self updateUserIcon:displayedObject];
}

- (void)deleteInImageViewWithImagePicker:(AIImageViewWithImagePicker *)sender
{
        if (displayedObject) {
                //Remove the preference
                [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];
        }
        return fileName;
}

#pragma mark Address Book

- (void)addMultiValue:(ABMultiValue *)value forProperty:(NSString *)property ofType:(ABPropertyType)propertyType toProfileArray:(NSMutableArray *)profileArray
{
        NSUInteger count = [value count];
        NSInteger i;
        for (i = 0; i < count; i++) {
                NSString *label = ABLocalizedPropertyOrLabel([value labelAtIndex:i]);
                id innerValue = [value valueAtIndex:i];
                switch (propertyType) {
                        case kABMultiStringProperty:
                                if ([(NSString *)innerValue length]) {
                                        [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                         [NSString stringWithFormat:@"%@ (%@)", ABLocalizedPropertyOrLabel(property), label], KEY_KEY,
                                                                                         (NSString *)innerValue, KEY_VALUE,
                                                                                         nil]];
                                }
                                break;
                        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,
                                                                                         nil]];
                                }
                                break;                          
                        case kABMultiDateProperty:
                                if (innerValue) {
                                        [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                         [NSString stringWithFormat:@"%@ (%@)", ABLocalizedPropertyOrLabel(property), label], KEY_KEY,
                                                                                         [[NSDateFormatter localizedDateFormatter] stringFromDate:(NSDate *)innerValue], KEY_VALUE,
                                                                                         nil]];
                                }
                                break;
                        case kABMultiArrayProperty:
                        case kABMultiDictionaryProperty:
                        case kABMultiDataProperty:
                        default:
                                /* Ignore Array, Dictionary, and Data properties */
                                break;
                }
        }
}

- (void)addAddressBookInfoToProfileArray:(NSMutableArray *)profileArray forContact:(AIListContact *)inContact
{
        ABPerson *person = [inContact addressBookPerson];
        if (!person) return;
        
        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];
        if (title) {
                [name appendString:title];
                if (firstName || middleName || lastName)
                        [name appendString:@" "];
        }
        if (firstName) {
                [name appendString:firstName];
                if (middleName || lastName)
                        [name appendString:@" "];
        }                       
        if (middleName) {
                [name appendString:middleName];
                if (lastName)
                        [name appendString:@" "];
        }                       
        if (lastName) {
                [name appendString:lastName];
        }
        if (suffix) {
                if ([name length])
                        [name appendString:@", "];
                [name appendString:suffix];
        }
        
        if ([name length]) {
                [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                 AILocalizedString(@"Full Name", nil), KEY_KEY,
                                                                 name, KEY_VALUE, nil]];
        }
        
        NSString *property;
        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 */
                                                   nil];

        for (property in propertiesToInclude) {
                if ([person valueForProperty:property]) {
                        id value = [person valueForProperty:property];
                        ABPropertyType propertyType = [ABPerson typeOfProperty:property];
                        switch (propertyType) {
                                case kABErrorInProperty:
                                        /* Ignore errors */
                                        break;
                                case kABStringProperty:
                                        if ([value length]) {
                                                [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                                 ABLocalizedPropertyOrLabel(property), KEY_KEY,
                                                                                                 (NSString *)value, KEY_VALUE,
                                                                                                 nil]];
                                        }
                                        break;
                                case kABIntegerProperty:
                                case kABRealProperty:
                                        if ([value integerValue] != 0) {
                                                [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                                 ABLocalizedPropertyOrLabel(property), KEY_KEY,
                                                                                                 [(NSNumber *)value stringValue], KEY_VALUE,
                                                                                                 nil]];
                                        }
                                case kABDateProperty:
                                        if (value) {
                                                [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                                 ABLocalizedPropertyOrLabel(property), KEY_KEY,
                                                                                                 [[NSDateFormatter localizedDateFormatter] stringFromDate:(NSDate *)value], KEY_VALUE,
                                                                                                 nil]];
                                        }
                                case kABArrayProperty:
                                case kABDictionaryProperty:
                                case kABDataProperty:
                                        /* Ignore arrays, dictionaries, and data */
                                        break;
                                case kABMultiStringProperty:
                                case kABMultiIntegerProperty:
                                case kABMultiRealProperty:
                                case kABMultiDateProperty:
                                case kABMultiArrayProperty:
                                case kABMultiDictionaryProperty:
                                case kABMultiDataProperty:
                                        [self addMultiValue:value forProperty:property ofType:propertyType toProfileArray:profileArray];
                                        break;
                        }
                }                       
        }
}

- (void)addTooltipEntriesToProfileArray:(NSMutableArray *)profileArray forContact:(AIListContact *)inContact
{
        NSArray *tooltipEntries = [[adium.interfaceController contactListTooltipPrimaryEntries] arrayByAddingObjectsFromArray:[adium.interfaceController contactListTooltipSecondaryEntries]];
        for (id <AIContactListTooltipEntry> tooltipEntry in tooltipEntries) {
                /* Note: shouldDisplayInContactInspector is a required part of the AIContactListTooltipEntry protocol. However,
                 * we are checking for its implementation for compatibility with Adium 1.2 tooltip plugins since Adium 1.3
                 * provides no decent way to notify the user that a plugin is incompatible.
                 *
                 * XXX - Remove me in Adium 1.4
                 */
                if (![tooltipEntry respondsToSelector:@selector(shouldDisplayInContactInspector)] ||
                        [tooltipEntry shouldDisplayInContactInspector]) {
                        id label, value;
                        if ((label = [tooltipEntry labelForObject:inContact]) &&
                                (value = [tooltipEntry entryForObject:inContact])) {
                                [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                 label, KEY_KEY,
                                                                                 value, KEY_VALUE,
                                                                                 nil]];
                        }       
                }
        }
}

@end