Frameworks/Adium Framework/Source/AIListObject.m
author Zachary West <zacw@adium.im>
Fri Nov 27 15:50:57 2009 -0500 (2009-11-27)
changeset 2835 1e8c89f99dfe
parent 2828 bd512b533b98
child 3042 c6ef8efaf14f
permissions -rw-r--r--
Simplify the "status message" contact/account property into one getter method. Correct error -1728 from AS on contact's status messages. Fixes #13460.

The totally undocumented -1728 error appears to be caused by runtime type disagreeing with event type. "status message" is apparently assumed to only have 1 code, so specifying one for contacts and one for accounts was confusing it when it was going to fetch it.
David@0
     1
/* 
David@0
     2
 * Adium is the legal property of its developers, whose names are listed in the copyright file included
David@0
     3
 * with this source distribution.
David@0
     4
 * 
David@0
     5
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
David@0
     6
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
David@0
     7
 * or (at your option) any later version.
David@0
     8
 * 
David@0
     9
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
David@0
    10
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
David@0
    11
 * Public License for more details.
David@0
    12
 * 
David@0
    13
 * You should have received a copy of the GNU General Public License along with this program; if not,
David@0
    14
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
David@0
    15
 */
David@0
    16
David@0
    17
#import <Adium/AIListObject.h>
David@0
    18
#import <Adium/AIContactControllerProtocol.h>
David@0
    19
#import <Adium/AIListContact.h>
David@0
    20
#import <Adium/AIListGroup.h>
David@0
    21
#import <Adium/AIService.h>
David@0
    22
#import <Adium/AIUserIcons.h>
David@0
    23
#import <AIUtilities/AIMutableOwnerArray.h>
David@0
    24
#import <AIUtilities/AIImageAdditions.h>
David@13
    25
#import <Adium/AIContactObserverManager.h>
David@434
    26
#import <Adium/AIContactHidingController.h>
David@547
    27
#import <Adium/AIStatus.h>
David@0
    28
David@0
    29
#define ObjectStatusCache	@"Object Status Cache"
David@0
    30
#define DisplayName			@"Display Name"
David@0
    31
#define LongDisplayName		@"Long Display Name"
David@0
    32
#define Key					@"Key"
David@0
    33
#define Group				@"Group"
David@0
    34
#define DisplayServiceID	@"DisplayServiceID"
David@0
    35
#define FormattedUID		@"FormattedUID"
David@0
    36
#define AlwaysVisible		@"AlwaysVisible"
David@0
    37
David@595
    38
@interface AIListObject ()
David@1142
    39
- (void)setContainingGroup:(AIListGroup *)inGroup;
zacw@1962
    40
- (void)setupObservedValues;
zacw@2159
    41
- (void)updateOrderCache;
David@595
    42
@end
David@595
    43
David@0
    44
/*!
David@0
    45
 * @class AIListObject
David@0
    46
 * @brief Base class for all contacts, groups, and accounts
David@0
    47
 */
David@0
    48
@implementation AIListObject
David@0
    49
David@0
    50
/*!
David@0
    51
 * @brief Initialize
David@0
    52
 *
David@0
    53
 * Designated initializer for AIListObject
David@0
    54
 */
David@0
    55
- (id)initWithUID:(NSString *)inUID service:(AIService *)inService
David@0
    56
{
David@0
    57
	if ((self = [super init])) {
David@594
    58
		m_groups = [[NSMutableSet alloc] initWithCapacity:1];
Evan@1669
    59
David@0
    60
		UID = [inUID retain];	
David@0
    61
		service = inService;
zacw@1224
    62
		
zacw@1962
    63
		// Delay until the next run loop so bookmarks can instantiate their values first.
zacw@1962
    64
		[self performSelector:@selector(setupObservedValues) withObject:nil afterDelay:0.0];
David@0
    65
	}
David@0
    66
David@0
    67
	return self;
David@0
    68
}
David@0
    69
David@0
    70
/*!
David@0
    71
 * @brief Deallocate
David@0
    72
 */
David@0
    73
- (void)dealloc
David@0
    74
{
David@0
    75
	[UID release]; UID = nil;
David@0
    76
	[internalObjectID release]; internalObjectID = nil;
David@594
    77
	[m_groups release]; m_groups = nil;
David@0
    78
David@594
    79
	[super dealloc];
David@0
    80
}
David@0
    81
zacw@1962
    82
- (void)setupObservedValues
zacw@1962
    83
{
zacw@2180
    84
	[self setValue:[self preferenceForKey:@"Visible" group:PREF_GROUP_ALWAYS_VISIBLE]
zacw@2180
    85
	   forProperty:AlwaysVisible
zacw@2180
    86
			notify:NotifyNow];
David@0
    87
}
David@0
    88
David@0
    89
//Identification -------------------------------------------------------------------------------------------------------
David@0
    90
#pragma mark Identification
David@0
    91
David@0
    92
/*!
David@0
    93
 * @brief UID for this object
David@0
    94
 *
David@0
    95
 * The UID is the name of the object.  If the object's name is not case sensitive, it is normalized.  If the object's
David@0
    96
 * name should be compared ignoring spaces, it has no spaces.  For an account, this is the account name.  For a contact,
David@0
    97
 * this is the screen name, buddy name, etc.
David@0
    98
 */
David@715
    99
@synthesize UID;
David@0
   100
David@0
   101
/*!
David@0
   102
 * @brief Service of this object
David@0
   103
 */
David@715
   104
@synthesize service;
David@0
   105
David@0
   106
/*!
David@0
   107
 * @brief Internal ID for this object
David@0
   108
 *
David@0
   109
 * An object ID generated by Adium that is shared by all objects which are, to most intents and purposes, identical to
David@0
   110
 * this object.  Ths ID is composed of the service ID and UID, so any object with identical services and object IDs
David@0
   111
 * will have the same value here.
David@0
   112
 */
David@0
   113
- (NSString *)internalObjectID
David@0
   114
{
David@0
   115
	if (!internalObjectID) {
David@715
   116
		internalObjectID = [[AIListObject internalObjectIDForServiceID:self.service.serviceID UID:self.UID] retain];
David@0
   117
	}
David@0
   118
	return internalObjectID;
David@0
   119
}
David@0
   120
David@0
   121
/*!
David@0
   122
 * @brief Generate an internal object ID
David@0
   123
 *
David@0
   124
 * @result The internalObjectID for an object with the specified serviceID and UID
David@0
   125
 */
David@0
   126
+ (NSString *)internalObjectIDForServiceID:(NSString *)inServiceID UID:(NSString *)inUID
David@0
   127
{
David@0
   128
	return [NSString stringWithFormat:@"%@.%@",inServiceID, inUID];
David@0
   129
}
David@0
   130
David@0
   131
//Visibility -----------------------------------------------------------------------------------------------------------
David@0
   132
#pragma mark Visibility
David@0
   133
David@0
   134
/*!
David@0
   135
 * @brief Sets if list object should always be visible
David@0
   136
 */
zacw@2180
   137
- (void)setAlwaysVisible:(BOOL)inVisible
zacw@2180
   138
{
zacw@2180
   139
	[self setPreference:[NSNumber numberWithBool:inVisible] 
zacw@2180
   140
				 forKey:@"Visible" 
zacw@2180
   141
				  group:PREF_GROUP_ALWAYS_VISIBLE];
zacw@2180
   142
	
zacw@2180
   143
	// This causes our container to update our visibility.
zacw@2180
   144
	[self setValue:[NSNumber numberWithBool:inVisible]
zacw@2180
   145
				   forProperty:AlwaysVisible
zacw@2180
   146
				   notify:NotifyNow];
David@0
   147
}
David@0
   148
David@0
   149
/*!
zacw@2180
   150
 * @brief Should this object ignore visibility settings?
zacw@2180
   151
 *
David@0
   152
 * @returns If object should always be visible
David@0
   153
 */
zacw@2180
   154
- (BOOL)alwaysVisible
zacw@2180
   155
{
zacw@2180
   156
	return [self boolValueForProperty:AlwaysVisible];
David@0
   157
}
David@0
   158
David@0
   159
//Grouping / Ownership -------------------------------------------------------------------------------------------------
David@0
   160
#pragma mark Grouping / Ownership
David@586
   161
Peter@1069
   162
- (NSSet *) groups
Peter@1069
   163
{
Peter@1069
   164
	return [[m_groups copy] autorelease];
Peter@1069
   165
}
David@586
   166
David@705
   167
- (void) addContainingGroup:(AIListGroup *)inGroup
David@595
   168
{
David@705
   169
	NSParameterAssert(inGroup && [inGroup canContainObject:self]);
David@705
   170
	if (![self.groups containsObject:inGroup]) {
David@705
   171
		
David@705
   172
		if (inGroup)
David@705
   173
			[m_groups addObject:inGroup];
David@705
   174
	}
David@595
   175
}
David@595
   176
David@598
   177
- (void) removeContainingGroup:(AIListGroup *)group
David@595
   178
{
David@705
   179
	NSParameterAssert(group != nil && [m_groups containsObject:group]);
David@705
   180
	[m_groups removeObject:group];
David@595
   181
}
David@595
   182
David@596
   183
- (NSSet *)containingObjects
David@596
   184
{
David@596
   185
	return self.groups;
David@596
   186
}
David@594
   187
zacw@2131
   188
- (void)removeFromGroup:(AIListObject <AIContainingObject> *)group
David@853
   189
{
zacw@2131
   190
	NSString *error = [NSString stringWithFormat:@"%@ needs an implementation of -removeFromGroup:", NSStringFromClass([self class])];
David@853
   191
	NSAssert(NO, error);
David@853
   192
}
David@853
   193
David@0
   194
/*!
David@0
   195
 * @brief Set the local grouping for this object
David@0
   196
 *
David@0
   197
 * PRIVATE: This is only for use by AIListObjects conforming to the AIContainingObject protocol.
David@0
   198
 */
David@1142
   199
- (void)setContainingGroup:(AIListGroup *)inGroup
David@0
   200
{
David@705
   201
	[m_groups removeAllObjects];
David@705
   202
	if(inGroup)
David@705
   203
		[self addContainingGroup:inGroup];
David@0
   204
}
David@0
   205
David@893
   206
- (void) moveContainedObject:(AIListObject *)listObject toIndex:(NSInteger)index
David@893
   207
{
David@1007
   208
	AIListObject<AIContainingObject> *container = (AIListObject<AIContainingObject> *)self;
zacw@1064
   209
	
zacw@2338
   210
	// We can't enforce this, since we're asked to set it for objects we don't yet *officially* contain.
zacw@2338
   211
	//NSAssert([container.containedObjects containsObject:listObject], @"Asked to set an index for an object which doesn't exist.");
zacw@2332
   212
	
David@893
   213
	if (index == 0) {
David@893
   214
		//Moved to the top of a group.  New index is between 0 and the lowest current index
zacw@1064
   215
		[container listObject:listObject didSetOrderIndex: self.smallestOrder / 2.0];
David@893
   216
		
David@1007
   217
	} else if (index >= container.visibleCount) {
David@893
   218
		//Moved to the bottom of a group.  New index is one higher than the highest current index
zacw@1064
   219
		[container listObject:listObject didSetOrderIndex: self.largestOrder + 1.0];
David@893
   220
		
David@893
   221
	} else {
David@893
   222
		//Moved somewhere in the middle.  New index is the average of the next largest and smallest index
Evan@2778
   223
		AIListObject	*previousObject = [container.visibleContainedObjects objectAtIndex:index-1];
Evan@2778
   224
		AIListObject	*nextObject = [container.visibleContainedObjects objectAtIndex:index];
David@1009
   225
		CGFloat nextLowest = [container orderIndexForObject:previousObject];
David@1009
   226
		CGFloat nextHighest = [container orderIndexForObject:nextObject];
David@893
   227
		
David@893
   228
		/* XXX - Fixme as per below
David@893
   229
		 * It's possible that nextLowest > nextHighest if ordering is not strictly based on the ordering indexes themselves.
David@893
   230
		 * For example, a group sorted by status then manually could look like (status - ordering index):
David@893
   231
		 *
David@893
   232
		 * Away Contact - 100
David@893
   233
		 * Away Contact - 120
David@893
   234
		 * Offline Contact - 110
David@893
   235
		 * Offline Contact - 113
David@893
   236
		 * Offline Contact - 125
David@893
   237
		 * 
David@893
   238
		 * Dropping between Away Contact and Offline Contact should make an Away Contact be > 120 but an Offline Contact be < 110.
David@893
   239
		 * Only the sort controller knows the answer as to where this contact should be positioned in the end.
David@893
   240
		 */
zacw@1064
   241
		
David@1011
   242
		[container listObject: listObject didSetOrderIndex: (nextHighest + nextLowest) / 2.0];
David@893
   243
	}	
David@893
   244
}
David@893
   245
David@0
   246
//Properties ------------------------------------------------------------------------------------------------------
David@0
   247
#pragma mark Properties
David@0
   248
/*!
David@0
   249
 * @brief Called after properties have been modified; informs the contact controller.
David@0
   250
 *
David@0
   251
 * @param keys The properties
David@0
   252
 * @param silent YES indicates that this should not trigger 'noisy' notifications - it is appropriate for notifications as an account signs on and notes tons of contacts.
David@0
   253
 */
David@0
   254
- (void)didModifyProperties:(NSSet *)keys silent:(BOOL)silent
David@0
   255
{
David@13
   256
	[[AIContactObserverManager sharedManager] listObjectStatusChanged:self
David@0
   257
									modifiedStatusKeys:keys
David@0
   258
												silent:silent];
David@0
   259
}
David@0
   260
/*!
David@0
   261
 * @brief Called after status changes have been modified and notifications posted
David@0
   262
 *
David@0
   263
 * When we notify of queued status changes, our containing group should notify as well so it can stay in sync with
David@0
   264
 * any changes it may have made in object:didChangeValueForProperty:notify:
David@0
   265
 *
David@0
   266
 * @param silent YES indicates that this should not trigger 'noisy' notifications - it is appropriate for notifications as an account signs on and notes tons of contacts.
David@0
   267
 */
David@0
   268
- (void)didNotifyOfChangedPropertiesSilently:(BOOL)silent
David@0
   269
{
David@583
   270
	//Let our containing objects know about the notification request
David@594
   271
	for (AIListContact<AIContainingObject> *container in self.containingObjects)
David@583
   272
		[container notifyOfChangedPropertiesSilently:silent];
David@0
   273
}
David@0
   274
David@0
   275
/*!
David@108
   276
 * @brief Notification of changed properties
David@0
   277
 *
David@0
   278
 * Subclasses may wish to override these - they must be sure to call super's implementation, too!
David@0
   279
 */
David@0
   280
- (void)object:(id)inObject didChangeValueForProperty:(NSString *)key notify:(NotifyTiming)notify
David@0
   281
{				
David@583
   282
	//Inform our containing groups about the new property value
David@594
   283
	for (AIListContact<AIContainingObject> *container in self.containingObjects)
David@583
   284
		[container object:self didChangeValueForProperty:key notify:notify];
David@0
   285
	
David@0
   286
	[super object:inObject didChangeValueForProperty:key notify:notify];
David@0
   287
}
David@0
   288
David@0
   289
//AIMutableOwnerArray delegate ------------------------------------------------------------------------------------------
David@0
   290
#pragma mark AIMutableOwnerArray delegate
David@0
   291
David@0
   292
/*!
David@0
   293
 * @brief One of our mutable owners set an object
David@0
   294
 *
David@0
   295
 * A mutable owner array (one of our displayArrays) set an object
David@0
   296
 */
David@0
   297
- (void)mutableOwnerArray:(AIMutableOwnerArray *)inArray didSetObject:(id)anObject withOwner:(id)inOwner priorityLevel:(float)priority
David@0
   298
{
David@594
   299
	for (AIListContact<AIContainingObject> *container in self.containingObjects)
David@583
   300
		[container listObject:self mutableOwnerArray:inArray didSetObject:anObject withOwner:inOwner priorityLevel:priority];
David@0
   301
}
David@0
   302
David@0
   303
/*!
David@0
   304
 * @brief Another object changed one of our mutable owner arrays
David@0
   305
 *
David@0
   306
 * Empty implementation by default - we do not need to take any action when a mutable owner array changes
David@0
   307
 */
David@0
   308
- (void)listObject:(AIListObject *)listObject mutableOwnerArray:(AIMutableOwnerArray *)inArray didSetObject:(id)anObject withOwner:(AIListObject *)inOwner priorityLevel:(float)priority
David@0
   309
{
David@0
   310
David@0
   311
}
David@0
   312
David@0
   313
//Object specific preferences ------------------------------------------------------------------------------------------
David@0
   314
#pragma mark Object specific preferences
David@0
   315
/*!
David@0
   316
 * @brief Set a preference value
David@0
   317
 */
David@0
   318
- (void)setPreference:(id)value forKey:(NSString *)key group:(NSString *)group
David@0
   319
{   
David@95
   320
	[adium.preferenceController setPreference:value forKey:key group:group object:self];
David@0
   321
}
David@0
   322
- (void)setPreferences:(NSDictionary *)prefs inGroup:(NSString *)group
David@0
   323
{
David@95
   324
	[adium.preferenceController setPreferences:prefs inGroup:group object:self];	
David@0
   325
}
David@0
   326
David@0
   327
- (void)setFormattedUID:(NSString *)inFormattedUID notify:(NotifyTiming)notify
David@0
   328
{
David@0
   329
	[self setValue:inFormattedUID
David@0
   330
				   forProperty:FormattedUID
David@0
   331
				   notify:notify];
David@0
   332
}
David@0
   333
David@0
   334
/*!
David@0
   335
 * @brief Retrieve a preference value
David@0
   336
 */
David@0
   337
- (id)preferenceForKey:(NSString *)key group:(NSString *)group
David@0
   338
{
David@95
   339
		return [adium.preferenceController preferenceForKey:key group:group objectIgnoringInheritance:self];
David@0
   340
}
David@0
   341
David@0
   342
/*!
David@0
   343
 * @brief Path for storing our reference file
David@0
   344
 */
David@0
   345
- (NSString *)pathToPreferences
David@0
   346
{
David@0
   347
    return OBJECT_PREFS_PATH;
David@0
   348
}
David@0
   349
David@0
   350
//Display Name  -------------------------------------------------------------------------------------
David@0
   351
#pragma mark Display Name 
David@0
   352
/*
David@0
   353
 * A list object basically has 4 different variations of display.
David@0
   354
 *
David@0
   355
 * - UID, the base UID of the contact "aiser123"
David@0
   356
 * - formattedUID, formating or alteration of the UID provided by the account code "AIser 123"
David@0
   357
 * - DisplayName, short formatted name provided by plugins "Adam Iser"
David@0
   358
 * - LongDisplayName, long formatted name provided by plugins "Adam Iser (AIser 123)"
David@0
   359
 *
David@0
   360
 * A value will always be returned by these methods, so if there is no long display name present it will fall back to
David@0
   361
 * display name, formattedUID, and finally UID (which is guaranteed to be present).  Use whichever one seems best
David@0
   362
 * suited for what is being displayed.
David@0
   363
 */
David@0
   364
David@0
   365
/*!
David@0
   366
 * @brief Server-formatted UID
David@0
   367
 *
David@0
   368
 * @result NSString of the server-formatted UID if present; otherwise the same as the UID
David@0
   369
 */
David@0
   370
- (NSString *)formattedUID
David@0
   371
{
David@0
   372
	NSString  *outName = [self valueForProperty:FormattedUID];
David@749
   373
	return outName ? outName : UID;	
David@0
   374
}
David@0
   375
David@0
   376
/*!
David@0
   377
 * @brief Long display name
David@0
   378
 *
David@0
   379
 * Though in many cases the same as the display name, a long display name allows additional information about the object
David@0
   380
 * to be displayed.  One preference, for example, sets a long display names formatted as "Alias (Username)".
David@0
   381
 */
David@0
   382
- (NSString *)longDisplayName
David@0
   383
{
David@0
   384
    NSString	*outName = [self displayArrayObjectForKey:LongDisplayName];
David@0
   385
	
David@837
   386
    return outName ? outName : self.displayName;
David@0
   387
}
David@0
   388
David@0
   389
/*!
David@0
   390
* @brief Display name
David@0
   391
 *
David@0
   392
 * Display name, drawing first from any externally-provided display name, then falling back to 
David@0
   393
 * the formatted UID.
David@0
   394
 */
David@0
   395
- (NSString *)displayName
David@0
   396
{
David@0
   397
    NSString	*displayName = [self displayArrayObjectForKey:DisplayName];
David@427
   398
    return displayName ? displayName : self.formattedUID;
David@0
   399
}
David@0
   400
David@0
   401
/*!
David@0
   402
* @brief The way this object's name should be spoken
David@0
   403
 *
David@0
   404
 * If not found, the display name is returned.
David@0
   405
 */
David@0
   406
- (NSString *)phoneticName
David@0
   407
{
David@0
   408
	NSString	*phoneticName = [self displayArrayObjectForKey:@"Phonetic Name"];
David@837
   409
    return phoneticName ? phoneticName : self.displayName;
David@0
   410
}
David@0
   411
David@0
   412
//Apply an alias
David@0
   413
- (void)setDisplayName:(NSString *)alias
David@0
   414
{
David@0
   415
	if ([alias length] == 0) alias = nil; 
David@0
   416
	
David@740
   417
	NSString	*oldAlias = [self preferenceForKey:@"Alias" group:PREF_GROUP_ALIASES];
David@0
   418
	
David@0
   419
	if ((!alias && oldAlias) ||
David@0
   420
		(alias && !([alias isEqualToString:oldAlias]))) {
David@0
   421
		//Save the alias
David@0
   422
		AILogWithSignature(@"%@: %@", self, alias);
David@0
   423
		[self setPreference:alias forKey:@"Alias" group:PREF_GROUP_ALIASES];
David@0
   424
		
David@0
   425
		//XXX - There must be a cleaner way to do this alias stuff!  This works for now :)
David@1109
   426
		[[NSNotificationCenter defaultCenter] postNotificationName:Contact_ApplyDisplayName
David@0
   427
												  object:self
David@0
   428
												userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
David@0
   429
																					 forKey:@"Notify"]];
David@0
   430
	}
David@0
   431
}
David@0
   432
David@0
   433
#pragma mark Key-Value Pairing
David@0
   434
- (NSImage *)userIcon
David@0
   435
{
David@0
   436
	return [self internalUserIcon];
David@0
   437
}
David@0
   438
- (NSImage *)internalUserIcon
David@0
   439
{
David@0
   440
	return [AIUserIcons userIconForObject:self];
David@0
   441
}
David@0
   442
David@0
   443
- (NSData *)userIconData
David@0
   444
{
David@0
   445
	NSImage *userIcon = [self userIcon];
David@0
   446
	return ([userIcon PNGRepresentation]);
David@0
   447
}
David@0
   448
- (void)setUserIconData:(NSData *)inData
David@0
   449
{
David@0
   450
	[AIUserIcons setManuallySetUserIconData:inData forObject:self];
David@0
   451
}
David@0
   452
catfish@2477
   453
- (NSInteger)idleTime
David@0
   454
{
catfish@2477
   455
	return [self integerValueForProperty:@"Idle"];
David@0
   456
}
David@0
   457
David@0
   458
//A standard listObject is never a stranger
David@0
   459
- (BOOL)isStranger{
David@0
   460
	return NO;
David@0
   461
}
David@0
   462
David@0
   463
- (NSString *)notes
David@0
   464
{
David@0
   465
	NSString *notes;
David@0
   466
	
David@740
   467
    notes = [self preferenceForKey:@"Notes" group:PREF_GROUP_NOTES];
David@0
   468
	if (!notes) notes = [self valueForProperty:@"Notes"];
David@0
   469
	
David@0
   470
	return notes;
David@0
   471
}
David@0
   472
- (void)setNotes:(NSString *)notes
David@0
   473
{
David@0
   474
	if ([notes length] == 0) notes = nil; 
David@0
   475
David@740
   476
	NSString	*oldNotes = [self preferenceForKey:@"Notes" group:PREF_GROUP_NOTES];
David@0
   477
	if ((!notes && oldNotes) ||
David@0
   478
		(notes && (![notes isEqualToString:oldNotes]))) {
David@0
   479
		//Save the note
David@0
   480
		[self setPreference:notes forKey:@"Notes" group:PREF_GROUP_NOTES];
David@0
   481
	}
David@0
   482
}
David@0
   483
David@0
   484
#pragma mark Status states
David@0
   485
David@0
   486
/*!
David@0
   487
 * @brief The name for the specific status of this object
David@0
   488
 *
David@0
   489
 * The statusName provides further detail after the statusType.  It may be a string such as @"Busy" or @"BRB".
David@0
   490
 * Possible values are determined by installed services; many default possibilities are listed in AIStatusController.h.
David@0
   491
 *
David@0
   492
 * The statusName may be nil if no additional status information is available for the contact. For example, an AIM
David@0
   493
 * contact will never have a statusName value, as the possibilities enumerated by AIStatusType -- and therefore returned
David@837
   494
 * by -AIListObject.statusType -- cover all possibilities.  An ICQ contact, on the other hand, might have a statusType
David@0
   495
 * of AIAwayStatusType and then a statusName of @"Not Available" or @"DND".
David@0
   496
 *
David@0
   497
 * @result The statusName, or nil none exists
David@0
   498
 */
David@0
   499
- (NSString *)statusName
David@0
   500
{
David@0
   501
	return [self valueForProperty:@"StatusName"];
David@0
   502
}
David@0
   503
David@0
   504
/*!
David@0
   505
 * @brief The general type of this object's status
David@0
   506
 *
David@0
   507
 * @result The AIStatusType for this object, indicating if it is available, away, invisible, offline, etc.
David@0
   508
 */
David@0
   509
- (AIStatusType)statusType
David@0
   510
{
David@837
   511
	if (self.online) {
David@0
   512
		NSNumber *statusTypeNumber = [self valueForProperty:@"StatusType"];
David@0
   513
		if (statusTypeNumber)
David@0
   514
			return [statusTypeNumber intValue];
David@0
   515
		return AIAvailableStatusType;
David@0
   516
	}
David@0
   517
	return AIOfflineStatusType;
David@0
   518
}
David@0
   519
David@0
   520
/*!
David@0
   521
 * @brief Store the status name and type for this object
David@0
   522
 *
David@0
   523
 * This is used by account code to let the object know its name and status type
David@0
   524
 * @param statusName The statusName, which further specifies the statusType, or nil if none is available
David@0
   525
 * @param statusType The AIStatusType describing this object's status
David@0
   526
 * @param notify The NotifyTiming for this operation
David@0
   527
 */
David@0
   528
- (void)setStatusWithName:(NSString *)statusName statusType:(AIStatusType)statusType notify:(NotifyTiming)notify
David@0
   529
{
David@837
   530
	AIStatusType	currentStatusType = self.statusType;
David@837
   531
	NSString		*oldStatusName = self.statusName;
David@0
   532
	
David@0
   533
	if (currentStatusType != statusType) {
David@0
   534
		[self setValue:[NSNumber numberWithInt:statusType] forProperty:@"StatusType" notify:NotifyLater];
David@0
   535
	}
David@0
   536
	
David@0
   537
	if ((!statusName && oldStatusName) || (statusName && ![statusName isEqualToString:oldStatusName])) {
David@0
   538
		[self setValue:statusName forProperty:@"StatusName" notify:NotifyLater];
David@0
   539
	}
David@0
   540
	
David@0
   541
	if (notify) [self notifyOfChangedPropertiesSilently:NO];
David@0
   542
}
David@0
   543
David@0
   544
/*!
David@0
   545
 * @brief Return the status message for this object
David@0
   546
 *
David@0
   547
 * The statusMessage may supplement the statusType and statusName with a message describing the object's status; in AIM,
David@0
   548
 * for example, both available and away statuses can have an associated, user-set message.
David@0
   549
 *
David@0
   550
 * @result The NSAttributedString statusMessagae, or nil if none is set
David@0
   551
 */
David@0
   552
- (NSAttributedString *)statusMessage
David@0
   553
{
David@0
   554
	return [self valueForProperty:@"StatusMessage"];
David@0
   555
}
David@0
   556
David@0
   557
/*!
David@0
   558
 * @brief Return the status message for this object as NSString
David@0
   559
 *
David@0
   560
 * The statusMessageString may supplement the statusType and statusName with a message describing the object's status; in AIM,
David@0
   561
 * for example, both available and away statuses can have an associated, user-set message.
David@0
   562
 *
David@0
   563
 * @result The NSString statusMessage, or nil if none is set
David@0
   564
 */
David@0
   565
- (NSString *)statusMessageString;
David@0
   566
{
David@0
   567
	return [[self valueForProperty:@"StatusMessage"] string];
David@0
   568
}
David@0
   569
David@0
   570
/*!
David@0
   571
 * @brief Is this object connected via a mobile device?
David@0
   572
 *
David@0
   573
 * The default implementation simply returns NO.  Only an AIListContact can be mobile... but a base implementation here
David@0
   574
 * makes code elsewhere much simpler.
David@0
   575
 */
David@0
   576
- (BOOL)isMobile
David@0
   577
{
David@0
   578
	return NO;
David@0
   579
}
David@0
   580
David@0
   581
/*!
David@0
   582
 * @brief Is this contact blocked?
David@0
   583
 *
David@0
   584
 * @result A boolean indicating if the object is blocked
David@0
   585
 */
David@0
   586
- (BOOL)isBlocked
David@0
   587
{
David@0
   588
	return NO;
David@0
   589
}
David@0
   590
David@0
   591
/*!
David@0
   592
 * @brief Set the current status message
David@0
   593
 *
David@0
   594
 * @param statusMessage Status message. May be nil.
David@0
   595
 * @param notify How to notify of the change. See -[ESObjectWithProperties setValue:forProperty:notify:].
David@0
   596
 */
David@0
   597
- (void)setStatusMessage:(NSAttributedString *)statusMessage notify:(NotifyTiming)notify
David@0
   598
{
David@0
   599
	if (!statusMessage ||
David@0
   600
	   ![[self valueForProperty:@"StatusMessage"] isEqualToAttributedString:statusMessage]) {
David@0
   601
		[self setValue:statusMessage forProperty:@"StatusMessage" notify:notify];
David@0
   602
	}
David@0
   603
}
David@0
   604
David@0
   605
- (void)setBaseAvailableStatusAndNotify:(NotifyTiming)notify
David@0
   606
{
David@0
   607
	[self setStatusWithName:nil
David@0
   608
				 statusType:AIAvailableStatusType
David@0
   609
					 notify:NotifyLater];
David@0
   610
	[self setStatusMessage:nil
David@0
   611
					 notify:NotifyLater];
David@0
   612
David@0
   613
	if (notify) [self notifyOfChangedPropertiesSilently:NO];
David@0
   614
}
David@0
   615
David@0
   616
- (BOOL)online
catfish@2475
   617
{
catfish@2483
   618
	return [self boolValueForProperty:@"Online"];
catfish@2475
   619
}
catfish@2475
   620
David@0
   621
- (AIStatusSummary)statusSummary
David@0
   622
{
David@936
   623
	if (self.online) {		
David@936
   624
		if (self.statusType == AIAwayStatusType || self.statusType == AIInvisibleStatusType)
David@936
   625
			return [self boolValueForProperty:@"IsIdle"] ? AIAwayAndIdleStatus : AIAwayStatus;
David@0
   626
		
David@936
   627
		if ([self boolValueForProperty:@"IsIdle"])
David@0
   628
			return AIIdleStatus;
David@936
   629
		
David@936
   630
		return AIAvailableStatus;
David@936
   631
	} 
David@936
   632
	
David@936
   633
	//We don't know the status of an stranger who isn't showing up as online
David@936
   634
	return self.isStranger ? AIUnknownStatus : AIOfflineStatus;
David@0
   635
}
David@0
   636
David@0
   637
- (void)notifyOfChangedPropertiesSilently:(BOOL)silent
David@0
   638
{
David@0
   639
	[super notifyOfChangedPropertiesSilently:silent];
David@0
   640
}
David@0
   641
David@0
   642
/*!
David@0
   643
 * @brief Are sounds for this object muted?
David@0
   644
 */
David@0
   645
- (BOOL)soundsAreMuted
David@0
   646
{
David@0
   647
	return NO;
David@0
   648
}
David@0
   649
David@0
   650
#pragma mark Methods for AIContainingObject-compliant classes to inherit
David@0
   651
- (void)listObject:(AIListObject *)listObject didSetOrderIndex:(float)orderIndexForObject
David@0
   652
{
David@0
   653
	NSDictionary		*dict = [self preferenceForKey:@"OrderIndexDictionary"
zacw@1064
   654
												 group:ObjectStatusCache];
David@0
   655
	NSMutableDictionary *newDict = (dict ? [[dict mutableCopy] autorelease] : [NSMutableDictionary dictionary]);
zacw@2826
   656
	
zacw@2826
   657
	// Sanity check - are we trying to assign infinity?
zacw@2826
   658
	if (orderIndexForObject == INFINITY) {
zacw@2828
   659
		AILogWithSignature(@"Correcting for INFINITY index, inObj=%@ allObj=%@", listObject, [newDict allKeysForObject:[NSNumber numberWithFloat:INFINITY]]);
zacw@2826
   660
		
zacw@2826
   661
		// Remove any objects that currently are currently set to INFINITY, they'll regenerate their position to the last place.
zacw@2827
   662
		for (NSString *key in [newDict allKeysForObject:[NSNumber numberWithFloat:INFINITY]]) {
zacw@2827
   663
			[newDict removeObjectForKey:key];
zacw@2827
   664
		}
zacw@2826
   665
		
zacw@2826
   666
		// Update the preference.
zacw@2826
   667
		[self setPreference:newDict
zacw@2826
   668
					 forKey:@"OrderIndexDictionary"
zacw@2826
   669
					  group:ObjectStatusCache];
zacw@2826
   670
		
zacw@2826
   671
		// Update our largest cache.
zacw@2826
   672
		[self updateOrderCache];
zacw@2826
   673
		
zacw@2826
   674
		// Assume an index of largest+1
zacw@2826
   675
		orderIndexForObject = self.largestOrder + 1;
zacw@2826
   676
	}
zacw@2826
   677
	
David@0
   678
	NSNumber *orderIndexForObjectNumber = [NSNumber numberWithFloat:orderIndexForObject];
David@0
   679
	
David@0
   680
	//Prevent setting an order index which we already have
David@0
   681
	NSArray *existingKeys = [dict allKeysForObject:orderIndexForObjectNumber];
David@1007
   682
	while (existingKeys.count && ![existingKeys isEqualToArray:[NSArray arrayWithObject:listObject.internalObjectID]]) {
David@1007
   683
		if (existingKeys.count == 1) {
David@0
   684
			AILogWithSignature(@"*** Warning: %@ had order index %f, but %@ already had an object with that order index. Setting to %f instead. Incrementing.",
zacw@2380
   685
							   listObject, orderIndexForObject, self, orderIndexForObject+1);
David@0
   686
zacw@2380
   687
			orderIndexForObject++;
David@0
   688
			orderIndexForObjectNumber = [NSNumber numberWithFloat:orderIndexForObject];
David@0
   689
			existingKeys = [dict allKeysForObject:orderIndexForObjectNumber];
David@0
   690
			
David@0
   691
		} else {
David@0
   692
			/* How could this happen? -evands */
David@0
   693
			AILogWithSignature(@"More than one object has %f! We'll grant it to %@", orderIndexForObject, listObject);
David@0
   694
David@1007
   695
			for (NSString *key in [existingKeys objectEnumerator]) {
David@0
   696
				[newDict removeObjectForKey:key];
David@0
   697
			}
David@0
   698
David@0
   699
			existingKeys = nil;
David@0
   700
		}		
David@0
   701
	}
David@0
   702
David@0
   703
	[newDict setObject:orderIndexForObjectNumber
David@838
   704
				forKey:listObject.internalObjectID];
David@0
   705
	
David@0
   706
	[self setPreference:newDict
David@0
   707
				 forKey:@"OrderIndexDictionary"
David@0
   708
				  group:ObjectStatusCache];
zacw@2159
   709
	
zacw@2159
   710
	[self updateOrderCache];
David@0
   711
}
David@0
   712
David@0
   713
//Order index
David@0
   714
- (float)orderIndexForObject:(AIListObject *)listObject
David@0
   715
{
David@0
   716
	NSDictionary *dict = [self preferenceForKey:@"OrderIndexDictionary"
David@0
   717
										  group:ObjectStatusCache 
David@740
   718
						 ];
David@838
   719
	NSNumber *orderIndexForObjectNumber = [dict objectForKey:listObject.internalObjectID];
David@0
   720
	float orderIndexForObject = (orderIndexForObjectNumber ? [orderIndexForObjectNumber floatValue] : 0);
David@0
   721
	
David@0
   722
	//Evan: I don't know how we got up to infinity.. perhaps pref corruption in a previous version?
David@0
   723
	//In any case, check against it; if we stored it, reset to a reasonable number.
David@0
   724
	//XXX is this still needed?
David@0
   725
	if  (!(orderIndexForObject < INFINITY)) orderIndexForObject = 0;
David@0
   726
zacw@2154
   727
	if (!orderIndexForObject) {
zacw@2154
   728
		orderIndexForObject = self.largestOrder + 1;
David@1010
   729
		[(AIListObject<AIContainingObject> *)self listObject:listObject didSetOrderIndex: orderIndexForObject];
David@0
   730
	}
David@0
   731
	
David@0
   732
	return orderIndexForObject;
David@0
   733
}
David@0
   734
zacw@2159
   735
- (CGFloat)smallestOrder
zacw@2154
   736
{
zacw@2159
   737
	if (!cachedSmallestOrder) {
zacw@2159
   738
		[self updateOrderCache];
zacw@2159
   739
	}
zacw@2159
   740
zacw@2159
   741
	return cachedSmallestOrder;
zacw@2159
   742
}
zacw@2159
   743
zacw@2159
   744
- (CGFloat)largestOrder
zacw@2159
   745
{
zacw@2159
   746
	if (!cachedLargestOrder) {
zacw@2159
   747
		[self updateOrderCache];
zacw@2159
   748
	}
zacw@2159
   749
	
zacw@2159
   750
	return cachedLargestOrder;	
zacw@2159
   751
}
zacw@2159
   752
zacw@2159
   753
- (void)updateOrderCache
zacw@2159
   754
{
zacw@2159
   755
	CGFloat smallest = INFINITY, largest = 0;
zacw@2159
   756
	
zacw@2154
   757
	NSDictionary *orderIndex = [self preferenceForKey:@"OrderIndexDictionary" group:ObjectStatusCache];
zacw@2154
   758
	
zacw@2154
   759
	for (NSNumber *index in orderIndex.allValues) {
zacw@2154
   760
		smallest = MIN(smallest, index.floatValue);
zacw@2154
   761
		largest = MAX(largest, index.floatValue);
zacw@2154
   762
	}
zacw@2154
   763
	
zacw@2381
   764
	cachedSmallestOrder = (smallest == INFINITY ? 1 : smallest);
zacw@2159
   765
	cachedLargestOrder = largest;
zacw@2154
   766
}
David@0
   767
David@0
   768
#pragma mark Comparison
David@0
   769
/*
David@0
   770
- (BOOL)isEqual:(id)anObject
David@0
   771
{
David@0
   772
	return ([anObject isMemberOfClass:[self class]] &&
David@111
   773
			[[(AIListObject *)anObject internalObjectID] isEqualToString:self.internalObjectID]);
David@0
   774
}
David@0
   775
*/
David@0
   776
David@0
   777
- (NSComparisonResult)compare:(AIListObject *)other {
David@0
   778
	NSParameterAssert([other isKindOfClass:[AIListObject class]]);
David@838
   779
	return [self.internalObjectID caseInsensitiveCompare:other.internalObjectID];
David@0
   780
}
David@0
   781
David@0
   782
#pragma mark Icons
David@0
   783
- (NSImage *)menuIcon
David@0
   784
{
David@0
   785
	return [AIUserIcons menuUserIconForObject:self];
David@0
   786
}
David@0
   787
David@0
   788
- (NSImage *)statusIcon
David@0
   789
{
David@0
   790
	NSImage *statusIcon = [self valueForProperty:@"List State Icon"];
David@0
   791
	if (!statusIcon) statusIcon = [self valueForProperty:@"List Status Icon"];
David@0
   792
	if (!statusIcon) statusIcon = [AIStatusIcons statusIconForUnknownStatusWithIconType:AIStatusIconList
David@0
   793
																			 direction:AIIconNormal];
David@0
   794
	return statusIcon;
David@0
   795
}
David@0
   796
David@0
   797
#pragma mark Debugging
David@0
   798
- (NSString *)description
David@0
   799
{
David@111
   800
	return [NSString stringWithFormat:@"<%@:%x %@>",NSStringFromClass([self class]), self, self.internalObjectID];
David@0
   801
}
David@0
   802
David@0
   803
#pragma mark Applescript
David@0
   804
- (int)scriptingStatusType
David@0
   805
{
David@837
   806
	AIStatusType statusType = self.statusType;
David@0
   807
	switch (statusType) {
David@0
   808
		case AIAvailableStatusType:
David@0
   809
			return AIAvailableStatusTypeAS;
David@0
   810
		case AIOfflineStatusType:
David@0
   811
			return AIOfflineStatusTypeAS;
David@0
   812
		case AIAwayStatusType:
David@0
   813
			return AIAwayStatusTypeAS;
David@0
   814
		case AIInvisibleStatusType:
David@0
   815
			return AIInvisibleStatusTypeAS;
David@0
   816
	}
David@0
   817
	return 0;
David@0
   818
}
zacw@2835
   819
zacw@2835
   820
/**
zacw@2835
   821
 * @brief Returns the current status message as rich text
zacw@2835
   822
 */
zacw@2835
   823
- (NSTextStorage *)scriptingStatusMessage
zacw@2835
   824
{
zacw@2835
   825
	return [[[NSTextStorage alloc] initWithAttributedString:self.statusMessage] autorelease];
zacw@2835
   826
}
zacw@2835
   827
David@0
   828
@end
Evan@794
   829
Evan@794
   830
/*
Evan@794
   831
 * Trivial plugin compatibility; these methods were removed from the public API
Evan@794
   832
 * but have trivial new-API implementations
Evan@794
   833
 */
Evan@794
   834
@implementation AIListObject (PluginCompatibility)
Evan@794
   835
- (NSString *)serviceID
Evan@794
   836
{
Evan@798
   837
	return self.service.serviceID;
Evan@794
   838
}
Evan@794
   839
@end