Initial checkin of MYAddressField, a combo-box for entering email or IM addresses.
authorJens Alfke <jens@mooseyard.com>
Thu Jul 17 13:29:34 2008 -0700 (2008-07-17)
changeset 18d6ab9f52b4d7
parent 17 a1044ae95953
child 19 5ade3e09a827
child 20 5a71993a1a70
Initial checkin of MYAddressField, a combo-box for entering email or IM addresses.
MYAddressField.h
MYAddressField.m
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/MYAddressField.h	Thu Jul 17 13:29:34 2008 -0700
     1.3 @@ -0,0 +1,43 @@
     1.4 +//
     1.5 +//  MYAddressField.h
     1.6 +//  YourMove
     1.7 +//
     1.8 +//  Created by Jens Alfke on 7/16/08.
     1.9 +//  Copyright 2008 Jens Alfke. All rights reserved.
    1.10 +//
    1.11 +
    1.12 +#import <Cocoa/Cocoa.h>
    1.13 +@class MYAddressItem, ABPerson;
    1.14 +
    1.15 +
    1.16 +@interface MYAddressField : NSComboBox
    1.17 +{
    1.18 +    NSString *_property, *_prefix;
    1.19 +    NSMutableArray *_addresses;
    1.20 +    NSArray *_defaultAddresses;
    1.21 +    MYAddressItem *_selectedAddress;
    1.22 +}
    1.23 +
    1.24 +@property (getter=isExpanded) BOOL expanded;
    1.25 +
    1.26 +@property (copy) NSString *addressProperty;
    1.27 +@property (copy) NSArray *defaultAddresses;
    1.28 +
    1.29 +@property (readonly,retain) MYAddressItem* selectedAddress;
    1.30 +
    1.31 +@end
    1.32 +
    1.33 +
    1.34 +
    1.35 +@interface MYAddressItem : NSObject
    1.36 +{
    1.37 +    NSString *_name, *_addressType, *_address, *_uuid;
    1.38 +}
    1.39 +- (id) initWithName: (NSString*)name
    1.40 +        addressType: (NSString*)addressType address: (NSString*)address;
    1.41 +- (id) initWithPerson: (ABPerson*)person
    1.42 +          addressType: (NSString*)addressType address: (NSString*)address;
    1.43 +- (id) initWithString: (NSString*)str addressType: (NSString*)addressType;
    1.44 +@property (readonly) NSString *name, *addressType, *address;
    1.45 +@property (readonly) ABPerson *person;
    1.46 +@end
    1.47 \ No newline at end of file
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/MYAddressField.m	Thu Jul 17 13:29:34 2008 -0700
     2.3 @@ -0,0 +1,223 @@
     2.4 +//
     2.5 +//  MYAddressField.m
     2.6 +//  YourMove
     2.7 +//
     2.8 +//  Created by Jens Alfke on 7/16/08.
     2.9 +//  Copyright 2008 Jens Alfke. All rights reserved.
    2.10 +//
    2.11 +
    2.12 +#import "MYAddressField.h"
    2.13 +#import "RegexKitLite.h"
    2.14 +#import <AddressBook/AddressBook.h>
    2.15 +
    2.16 +
    2.17 +@interface MYAddressField ()
    2.18 +@property (retain) MYAddressItem *selectedAddress;
    2.19 +@end
    2.20 +
    2.21 +
    2.22 +@implementation MYAddressField
    2.23 +
    2.24 +
    2.25 +@synthesize defaultAddresses=_defaultAddresses, addressProperty=_property, selectedAddress=_selectedAddress;
    2.26 +
    2.27 +
    2.28 +- (void) _computeAddresses
    2.29 +{
    2.30 +    NSMutableArray *newAddresses = $marray();
    2.31 +    if( _property ) {
    2.32 +        if( _prefix.length ) {
    2.33 +            // Find all the people in the address book matching _prefix:
    2.34 +            ABAddressBook *ab = [ABAddressBook sharedAddressBook];
    2.35 +            ABSearchElement *search = [ABPerson searchElementForProperty: _property
    2.36 +                                                                   label: nil
    2.37 +                                                                     key: nil
    2.38 +                                                                   value: nil
    2.39 +                                                              comparison: kABNotEqual];
    2.40 +            for( ABPerson *person in [ab recordsMatchingSearchElement: search] ) {
    2.41 +                ABMultiValue *values = [person valueForProperty: _property];
    2.42 +                NSString *first = [person valueForProperty: kABFirstNameProperty];
    2.43 +                NSString *last = [person valueForProperty: kABLastNameProperty];
    2.44 +                BOOL nameMatches = _prefix==nil || ([first.lowercaseString hasPrefix: _prefix] 
    2.45 +                                                    || [last.lowercaseString hasPrefix: _prefix]);
    2.46 +                for( int i=0; i<values.count; i++ ) {
    2.47 +                    NSString *address = [values valueAtIndex: i];
    2.48 +                    if( nameMatches || [address.lowercaseString hasPrefix: _prefix] ) {
    2.49 +                        MYAddressItem *item = [[MYAddressItem alloc] initWithPerson: person
    2.50 +                                                                        addressType: _property
    2.51 +                                                                            address: address];
    2.52 +                        [newAddresses addObject: item];
    2.53 +                        [item release];
    2.54 +                    }
    2.55 +                }
    2.56 +            }
    2.57 +        } else if( _defaultAddresses ) {
    2.58 +            [newAddresses addObjectsFromArray: _defaultAddresses];
    2.59 +        }
    2.60 +    }
    2.61 +    
    2.62 +    [newAddresses sortUsingSelector: @selector(compare:)];
    2.63 +    
    2.64 +    if( ifSetObj(&_addresses,newAddresses) )
    2.65 +        [self reloadData];
    2.66 +}
    2.67 +
    2.68 +- (NSArray*) addresses
    2.69 +{
    2.70 +    if( ! _addresses )
    2.71 +        [self _computeAddresses];
    2.72 +    return _addresses;
    2.73 +}
    2.74 +
    2.75 +
    2.76 +- (void) awakeFromNib
    2.77 +{
    2.78 +    if( ! _addresses )
    2.79 +        _addresses = [[NSMutableArray alloc] init];
    2.80 +    _property = [kABEmailProperty retain];
    2.81 +    self.completes = NO;
    2.82 +    self.usesDataSource = YES;
    2.83 +    self.dataSource = self;
    2.84 +    self.delegate = self;
    2.85 +}
    2.86 +
    2.87 +- (void) dealloc
    2.88 +{
    2.89 +    [_addresses release];
    2.90 +    [_property release];
    2.91 +    [_prefix release];
    2.92 +    [_selectedAddress release];
    2.93 +    [super dealloc];
    2.94 +}
    2.95 +
    2.96 +
    2.97 +- (BOOL) isExpanded
    2.98 +{
    2.99 +    id ax = NSAccessibilityUnignoredDescendant(self);
   2.100 +    return [[ax accessibilityAttributeValue: NSAccessibilityExpandedAttribute] boolValue];
   2.101 +}
   2.102 +
   2.103 +
   2.104 +- (void) setExpanded: (BOOL)expanded
   2.105 +{
   2.106 +    id ax = NSAccessibilityUnignoredDescendant(self);
   2.107 +    [ax accessibilitySetValue: $object(expanded) forAttribute: NSAccessibilityExpandedAttribute];
   2.108 +}
   2.109 +
   2.110 +
   2.111 +- (void) controlTextDidChange: (NSNotification*)n
   2.112 +{
   2.113 +    if( _prefix.length == 0 )
   2.114 +        self.expanded = YES;
   2.115 +    
   2.116 +    if( ifSetObj(&_prefix, self.stringValue.lowercaseString) )
   2.117 +        [self _computeAddresses];
   2.118 +    MYAddressItem *item = [[MYAddressItem alloc] initWithString: self.stringValue 
   2.119 +                                                    addressType: _property];
   2.120 +    self.selectedAddress = item;
   2.121 +    [item release];
   2.122 +
   2.123 +    if( _prefix.length == 0 )
   2.124 +        self.expanded = NO;
   2.125 +
   2.126 +    //Log(@"Address selection = %@",self.selectedAddress);
   2.127 +}
   2.128 +
   2.129 +- (void)comboBoxSelectionDidChange:(NSNotification *)notification
   2.130 +{
   2.131 +    int sel = self.indexOfSelectedItem;
   2.132 +    self.selectedAddress = sel>=0 ?[self.addresses objectAtIndex: sel] :nil;
   2.133 +    //Log(@"Address selection = %@",self.selectedAddress);
   2.134 +}
   2.135 +
   2.136 +
   2.137 +#pragma mark -
   2.138 +#pragma mark DATA SOURCE:
   2.139 +
   2.140 +
   2.141 +- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox
   2.142 +{
   2.143 +    return self.addresses.count;
   2.144 +}
   2.145 +
   2.146 +- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index
   2.147 +{
   2.148 +    return [[self.addresses objectAtIndex: index] description];
   2.149 +}
   2.150 +
   2.151 +
   2.152 +@end
   2.153 +
   2.154 +
   2.155 +
   2.156 +
   2.157 +@implementation MYAddressItem
   2.158 +
   2.159 +- (id) initWithName: (NSString*)name
   2.160 +        addressType: (NSString*)addressType address: (NSString*)address
   2.161 +{
   2.162 +    self = [super init];
   2.163 +    if( self ) {
   2.164 +        _name = name.length ?[name copy] :nil;
   2.165 +        _addressType = [addressType copy];
   2.166 +        _address = [address copy];
   2.167 +    }
   2.168 +    return self;
   2.169 +}
   2.170 +    
   2.171 +- (id) initWithPerson: (ABPerson*)person
   2.172 +          addressType: (NSString*)addressType address: (NSString*)address
   2.173 +{
   2.174 +    NSString *first = [person valueForProperty: kABFirstNameProperty] ?: @"";
   2.175 +    NSString *last = [person valueForProperty: kABLastNameProperty] ?: @"";
   2.176 +    NSString *name = $sprintf(@"%@ %@", first,last);
   2.177 +    
   2.178 +    self = [self initWithName: name addressType: addressType address: address];
   2.179 +    if( self )
   2.180 +        _uuid = person.uniqueId.copy;
   2.181 +    return self;
   2.182 +}
   2.183 +
   2.184 +- (id) initWithString: (NSString*)str addressType: (NSString*)addressType
   2.185 +{
   2.186 +    #define kJustAddrRegex "[-a-zA-Z0-9%_+.]+(?:@[-a-zA-Z0-9.]+)"
   2.187 +    static NSString* const kNameAndAddrRegex = @"^\\s*(\\S+)?\\s*<("kJustAddrRegex")>\\s*$";
   2.188 +    static NSString* const kAddrRegex = @"^\\s*("kJustAddrRegex")\\s*$";
   2.189 +
   2.190 +    NSString *name = nil;
   2.191 +    NSString *address = [str stringByMatching: kNameAndAddrRegex capture: 2];
   2.192 +    if( address ) {
   2.193 +        name = [str stringByMatching: kNameAndAddrRegex capture: 1];
   2.194 +    } else {
   2.195 +        address = [str stringByMatching: kAddrRegex];
   2.196 +    }
   2.197 +    if( ! address ) {
   2.198 +        [self release];
   2.199 +        return nil;
   2.200 +    }
   2.201 +    return [self initWithName: name addressType: addressType address: address];
   2.202 +}
   2.203 +
   2.204 +@synthesize name=_name, addressType=_addressType, address=_address;
   2.205 +
   2.206 +- (ABPerson*) person
   2.207 +{
   2.208 +    if( _uuid )
   2.209 +        return (ABPerson*) [[ABAddressBook sharedAddressBook] recordForUniqueId: _uuid];
   2.210 +    else
   2.211 +        return nil;
   2.212 +}
   2.213 +
   2.214 +- (NSString*) description
   2.215 +{
   2.216 +    return $sprintf(@"%@%@<%@>", _name,(_name ?@" ":@""),_address);
   2.217 +}
   2.218 +
   2.219 +- (NSComparisonResult) compare: (MYAddressItem*)other
   2.220 +{
   2.221 +    NSString *str1 = _name ?:_address;
   2.222 +    NSString *str2 = other->_name ?: other->_address;
   2.223 +    return [str1 localizedCaseInsensitiveCompare: str2];
   2.224 +}
   2.225 +
   2.226 +@end