Frameworks/Adium Framework/Source/AIChat.m
author Evan Schoenberg
Sat Nov 21 20:07:58 2009 -0600 (2009-11-21)
changeset 2929 5616a54b1173
parent 2777 a55a449072d4
child 2936 366008549f0c
permissions -rw-r--r--
A manually specified display name must override any server-provided one. A chat can get a display name from the account's own information via -[CBPurpleAccount updateTitle:forChat:]. If an alias is specified for a chat, ensure that it is displayed by setting it at highest priority in the AIMutableOwnerArray.

Fixes #12771, including the comment within that ticket that changes to the alias via the Get Info window previously didn't live-update.
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
#import <Adium/AIAccount.h>
David@0
    17
#import <Adium/AIChat.h>
David@0
    18
#import <Adium/AIContentMessage.h>
zacw@1301
    19
#import <Adium/AIContentTopic.h>
David@0
    20
#import <Adium/AIListContact.h>
David@715
    21
#import <Adium/AIService.h>
David@0
    22
#import <Adium/ESFileTransfer.h>
David@0
    23
#import <Adium/AIHTMLDecoder.h>
David@0
    24
#import <Adium/AIServiceIcons.h>
David@0
    25
#import <Adium/AIUserIcons.h>
David@825
    26
#import <Adium/AIContactHidingController.h>
David@0
    27
#import <Adium/AIContactControllerProtocol.h>
David@0
    28
#import <Adium/AIContentControllerProtocol.h>
David@0
    29
#import <Adium/AIChatControllerProtocol.h>
David@0
    30
#import <Adium/AIInterfaceControllerProtocol.h>
zacw@1005
    31
#import <Adium/AISortController.h>
David@0
    32
David@0
    33
#import <AIUtilities/AIArrayAdditions.h>
David@0
    34
#import <AIUtilities/AIMutableOwnerArray.h>
zacw@1301
    35
#import <AIUtilities/AIAttributedStringAdditions.h>
David@0
    36
David@0
    37
#import "AIMessageWindowController.h"
David@0
    38
#import "AIMessageWindow.h"
David@0
    39
#import "AIInterfaceControllerProtocol.h"
David@0
    40
#import "AIWebKitMessageViewController.h"
David@0
    41
David@0
    42
David@84
    43
@interface AIChat ()
David@0
    44
- (id)initForAccount:(AIAccount *)inAccount;
David@0
    45
- (void)clearUniqueChatID;
David@0
    46
- (void)clearListObjectStatuses;
David@0
    47
@end
David@0
    48
David@0
    49
@implementation AIChat
David@0
    50
David@0
    51
static int nextChatNumber = 0;
David@0
    52
David@0
    53
+ (id)chatForAccount:(AIAccount *)inAccount
David@0
    54
{
David@0
    55
    return [[[self alloc] initForAccount:inAccount] autorelease];
David@0
    56
}
David@0
    57
David@0
    58
- (id)initForAccount:(AIAccount *)inAccount
David@0
    59
{
David@0
    60
    if ((self = [super init])) {
David@0
    61
		name = nil;
David@0
    62
		account = [inAccount retain];
David@828
    63
		participatingContacts = [[NSMutableArray alloc] init];
zacw@1395
    64
		participatingContactsFlags = [[NSMutableDictionary alloc] init];
zacw@1395
    65
		participatingContactsAliases = [[NSMutableDictionary alloc] init];
David@0
    66
		dateOpened = [[NSDate date] retain];
David@0
    67
		uniqueChatID = nil;
David@0
    68
		ignoredListContacts = nil;
David@0
    69
		isOpen = NO;
David@0
    70
		isGroupChat = NO;
David@0
    71
		expanded = YES;
David@0
    72
		customEmoticons = nil;
David@0
    73
		hasSentOrReceivedContent = NO;
zacw@1690
    74
		showJoinLeave = YES;
David@0
    75
		pendingOutgoingContentObjects = [[NSMutableArray alloc] init];
David@0
    76
David@0
    77
		AILog(@"[AIChat: %x initForAccount]",self);
David@0
    78
	}
David@0
    79
David@0
    80
    return self;
David@0
    81
}
David@0
    82
David@0
    83
/*!
David@0
    84
 * @brief Deallocate
David@0
    85
 */
David@0
    86
- (void)dealloc
David@0
    87
{
David@0
    88
	AILog(@"[%@ dealloc]",self);
David@0
    89
David@0
    90
	[account release];
David@1006
    91
	[self removeAllParticipatingContactsSilently];
David@828
    92
	[participatingContacts release];
sholt@2777
    93
	[participatingContactsFlags release];
sholt@2777
    94
	[participatingContactsAliases release];
David@0
    95
	[dateOpened release];
David@0
    96
	[ignoredListContacts release];
David@0
    97
	[pendingOutgoingContentObjects release];
David@0
    98
	[uniqueChatID release]; uniqueChatID = nil;
David@0
    99
	[customEmoticons release]; customEmoticons = nil;
zacw@1301
   100
	[topic release]; [topicSetter release];
zacw@1301
   101
	
David@0
   102
	[super dealloc];
David@0
   103
}
David@0
   104
David@0
   105
//Big image
David@0
   106
- (NSImage *)chatImage
David@0
   107
{
David@108
   108
	AIListContact 	*listObject = self.listObject;
David@0
   109
	NSImage			*image = nil;
David@0
   110
David@0
   111
	if (listObject) {
David@828
   112
		image = listObject.parentContact.userIcon;
David@0
   113
		if (!image) image = [AIServiceIcons serviceIconForObject:listObject type:AIServiceIconLarge direction:AIIconNormal];
David@0
   114
	} else {
David@108
   115
		image = [AIServiceIcons serviceIconForObject:self.account type:AIServiceIconLarge direction:AIIconNormal];
David@0
   116
	}
David@0
   117
David@0
   118
	return image;
David@0
   119
}
David@0
   120
David@0
   121
//lil image
David@0
   122
- (NSImage *)chatMenuImage
David@0
   123
{
David@828
   124
	AIListObject 	*listObject = self.listObject;
David@0
   125
	NSImage			*chatMenuImage = nil;
David@0
   126
	
David@828
   127
	if (listObject) {
David@0
   128
		chatMenuImage = [AIUserIcons menuUserIconForObject:listObject];
zacw@1354
   129
	} else {
zacw@1354
   130
		chatMenuImage = [AIServiceIcons serviceIconForObject:account
zacw@1354
   131
														type:AIServiceIconSmall
zacw@1354
   132
												   direction:AIIconNormal];
David@0
   133
	}
David@0
   134
David@0
   135
	return chatMenuImage;
David@0
   136
}
David@0
   137
David@0
   138
David@0
   139
//Associated Account ---------------------------------------------------------------------------------------------------
David@0
   140
#pragma mark Associated Account
David@0
   141
- (AIAccount *)account
David@0
   142
{
David@0
   143
    return account;
David@0
   144
}
David@0
   145
David@0
   146
- (void)setAccount:(AIAccount *)inAccount
David@0
   147
{
David@0
   148
	if (inAccount != account) {
David@0
   149
		[account release];
David@0
   150
		account = [inAccount retain];
David@0
   151
		
David@0
   152
		//The uniqueChatID may depend upon the account, so clear it
David@0
   153
		[self clearUniqueChatID];
David@1109
   154
		[[NSNotificationCenter defaultCenter] postNotificationName:Chat_SourceChanged object:self]; //Notify
David@0
   155
	}
David@0
   156
}
David@0
   157
David@0
   158
/*@brief: holds information passed upon the creation of the chat:
David@0
   159
 * handle, server, etc.
David@0
   160
 */
David@0
   161
- (NSDictionary *)chatCreationDictionary
David@0
   162
{
David@0
   163
	return [self valueForProperty:@"ChatCreationInfo"];
David@0
   164
}
David@0
   165
David@0
   166
- (void)setChatCreationDictionary:(NSDictionary *)inDict
David@0
   167
{
David@0
   168
	[self setValue:inDict
David@0
   169
				   forProperty:@"ChatCreationInfo"
David@0
   170
				   notify:NotifyNever];
David@0
   171
}
David@0
   172
David@828
   173
@synthesize hasSentOrReceivedContent, isOpen, dateOpened;
David@0
   174
David@0
   175
//Status ---------------------------------------------------------------------------------------------------------------
David@0
   176
#pragma mark Status
David@0
   177
//Status
David@0
   178
- (void)didModifyProperties:(NSSet *)keys silent:(BOOL)silent
David@0
   179
{
David@95
   180
	[adium.chatController chatStatusChanged:self
David@0
   181
						   modifiedStatusKeys:keys
David@0
   182
									   silent:silent];	
David@0
   183
}
David@0
   184
David@0
   185
- (void)object:(id)inObject didChangeValueForProperty:(NSString *)key notify:(NotifyTiming)notify
David@0
   186
{
David@0
   187
	//If our unviewed content changes or typing status changes, and we have a single list object, 
David@0
   188
	//apply the change to that object as well so it can be cleanly reflected in the contact list.
David@0
   189
	if ([key isEqualToString:KEY_UNVIEWED_CONTENT] ||
David@0
   190
		[key isEqualToString:KEY_TYPING]) {
zacw@945
   191
		AIListObject	*listObject = nil;
zacw@945
   192
		
zacw@945
   193
		if (self.isGroupChat) {
zacw@945
   194
			listObject = (AIListContact *)[adium.contactController existingBookmarkForChat:self];
zacw@945
   195
		} else {
zacw@945
   196
			listObject = self.listObject;
zacw@945
   197
		}
David@0
   198
		
David@0
   199
		if (listObject) [listObject setValue:[self valueForProperty:key] forProperty:key notify:notify];
David@0
   200
	}
David@0
   201
	
David@0
   202
	[super object:inObject didChangeValueForProperty:key notify:notify];
David@0
   203
}
David@0
   204
David@0
   205
- (void)clearListObjectStatuses
David@0
   206
{
David@108
   207
	AIListObject	*listObject = self.listObject;
David@0
   208
	
David@0
   209
	if (listObject) {
David@0
   210
		[listObject setValue:nil forProperty:KEY_UNVIEWED_CONTENT notify:NotifyLater];
David@0
   211
		[listObject setValue:nil forProperty:KEY_TYPING notify:NotifyLater];
David@0
   212
	
David@0
   213
		[listObject notifyOfChangedPropertiesSilently:NO];
David@0
   214
	}
David@0
   215
	
David@0
   216
}
David@0
   217
//Secure chatting ------------------------------------------------------------------------------------------------------
David@0
   218
- (void)setSecurityDetails:(NSDictionary *)securityDetails
David@0
   219
{
David@0
   220
	[self setValue:securityDetails
David@0
   221
				   forProperty:@"SecurityDetails"
David@0
   222
				   notify:NotifyNow];
David@0
   223
}
David@0
   224
- (NSDictionary *)securityDetails
David@0
   225
{
David@0
   226
	return [self valueForProperty:@"SecurityDetails"];
David@0
   227
}
David@0
   228
David@0
   229
- (BOOL)isSecure
David@828
   230
{	
David@828
   231
	return self.encryptionStatus != EncryptionStatus_None;
David@0
   232
}
David@0
   233
David@0
   234
- (AIEncryptionStatus)encryptionStatus
David@0
   235
{
David@0
   236
	AIEncryptionStatus	encryptionStatus = EncryptionStatus_None;
David@0
   237
David@828
   238
	NSDictionary		*securityDetails = self.securityDetails;
David@0
   239
	if (securityDetails) {
David@0
   240
		NSNumber *detailsStatus;
David@0
   241
		if ((detailsStatus = [securityDetails objectForKey:@"EncryptionStatus"])) {
David@0
   242
			encryptionStatus = [detailsStatus intValue];
David@0
   243
			
David@0
   244
		} else {
David@0
   245
			/* If we don't have a specific encryption status, but do have security details, assume
David@0
   246
			 * encrypted and verified.
David@0
   247
			 */
David@0
   248
			encryptionStatus = EncryptionStatus_Verified;
David@0
   249
		}
David@0
   250
	}
David@0
   251
David@0
   252
	return encryptionStatus;
David@0
   253
}
David@0
   254
David@0
   255
- (BOOL)supportsSecureMessagingToggling
David@0
   256
{
David@828
   257
	return [account allowSecureMessagingTogglingForChat:self];
David@0
   258
}
David@0
   259
David@0
   260
//Name  ----------------------------------------------------------------------------------------------------------------
David@0
   261
#pragma mark Name
David@108
   262
David@108
   263
@synthesize name;
David@0
   264
David@0
   265
/*!
David@108
   266
 * @brief An identifier which can be used to look up this chat later
David@0
   267
 *
David@0
   268
 * Use uniqueChatID as a unique identifier for a contact-service combination.
David@108
   269
 * Only an account which created a chat should specify the identifier; it has no useful meaning outside that context.
David@0
   270
 */
David@108
   271
@synthesize identifier;
David@0
   272
David@0
   273
- (NSString *)displayName
David@0
   274
{
David@0
   275
    NSString	*outName = [self displayArrayObjectForKey:@"Display Name"];
David@828
   276
    return outName ? outName : (name ? name : self.listObject.displayName);
David@0
   277
}
David@0
   278
David@0
   279
- (void)setDisplayName:(NSString *)inDisplayName
David@0
   280
{
David@0
   281
	[[self displayArrayForKey:@"Display Name"] setObject:inDisplayName
Evan@2929
   282
											   withOwner:self
Evan@2929
   283
										   priorityLevel:Highest_Priority];
Evan@2929
   284
zacw@842
   285
	//The display array doesn't cause an attribute update; fake it.
zacw@842
   286
	[adium.chatController chatStatusChanged:self
zacw@842
   287
						 modifiedStatusKeys:[NSSet setWithObject:@"Display Name"]
zacw@842
   288
									 silent:NO];
David@0
   289
}
David@0
   290
David@0
   291
//Participating ListObjects --------------------------------------------------------------------------------------------
David@0
   292
#pragma mark Participating ListObjects
David@0
   293
zacw@1395
   294
/*!
zacw@1395
   295
 * @brief The display name for the contact in this chat.
zacw@1395
   296
 *
zacw@1395
   297
 * @param contact The AIListObject whose display name should be created
zacw@1395
   298
 *
zacw@1395
   299
 * If the user has an alias set, the alias is used, otherwise the display name.
zacw@1395
   300
 *
zacw@1395
   301
 * @returns Display name
zacw@1395
   302
 */
zacw@1395
   303
- (NSString *)displayNameForContact:(AIListObject *)contact
zacw@1395
   304
{
David@1421
   305
	return [self aliasForContact:contact] ?: contact.displayName;
zacw@1395
   306
}
zacw@1395
   307
zacw@1395
   308
/*!
zacw@1395
   309
 * @brief The flags for a given contact.
zacw@1395
   310
 */
zacw@1395
   311
- (AIGroupChatFlags)flagsForContact:(AIListObject *)contact
zacw@1395
   312
{
zacw@1395
   313
	return [[participatingContactsFlags objectForKey:contact.UID] integerValue];
zacw@1395
   314
}
zacw@1395
   315
zacw@1395
   316
/*!
zacw@1395
   317
 * @brief The alias for a given contact
zacw@1395
   318
 */
zacw@1395
   319
- (NSString *)aliasForContact:(AIListObject *)contact
zacw@1395
   320
{
zacw@2127
   321
	NSString *alias = [participatingContactsAliases objectForKey:contact.UID];
zacw@2127
   322
	
zacw@2129
   323
	if (!alias && self.isGroupChat) {
zacw@2127
   324
		alias = [self.account fallbackAliasForContact:(AIListContact *)contact inChat:self];
zacw@2127
   325
	}
zacw@2127
   326
	
zacw@2127
   327
	return alias;
zacw@1395
   328
}
zacw@1395
   329
zacw@1395
   330
/*!
zacw@1395
   331
 * @brief Set the flags for a contact
zacw@1395
   332
 *
zacw@1395
   333
 * Note that this doesn't set the bitwise or; this directly sets the value passed.
zacw@1395
   334
 */
zacw@1395
   335
- (void)setFlags:(AIGroupChatFlags)flags forContact:(AIListObject *)contact
zacw@1395
   336
{
zacw@1395
   337
	[participatingContactsFlags setObject:[NSNumber numberWithInteger:flags]
zacw@1395
   338
								   forKey:contact.UID];
zacw@1395
   339
}
zacw@1395
   340
zacw@1395
   341
/*!
zacw@1395
   342
 * @brief Set the alias for a contact.
zacw@1395
   343
 */
zacw@1395
   344
- (void)setAlias:(NSString *)alias forContact:(AIListObject *)contact
zacw@1395
   345
{
zacw@1395
   346
	[participatingContactsAliases setObject:alias
zacw@1395
   347
									 forKey:contact.UID];
zacw@1395
   348
}
zacw@1395
   349
zacw@1397
   350
AIGroupChatFlags highestFlag(AIGroupChatFlags flags)
zacw@1397
   351
{
zacw@1397
   352
	if ((flags & AIGroupChatFounder) == AIGroupChatFounder)
zacw@1397
   353
		return AIGroupChatFounder;
zacw@1397
   354
	
zacw@1397
   355
	if ((flags & AIGroupChatOp) == AIGroupChatOp)
zacw@1397
   356
		return AIGroupChatOp;
zacw@1397
   357
	
zacw@1397
   358
	if ((flags & AIGroupChatHalfOp) == AIGroupChatHalfOp)
zacw@1397
   359
		return AIGroupChatHalfOp;
zacw@1397
   360
	
zacw@1397
   361
	if ((flags & AIGroupChatVoice) == AIGroupChatVoice)
zacw@1397
   362
		return AIGroupChatVoice;
zacw@1397
   363
	
zacw@1397
   364
	return AIGroupChatNone;
zacw@1397
   365
}
zacw@1397
   366
zacw@1397
   367
NSComparisonResult userListSort (id objectA, id objectB, void *context)
zacw@1397
   368
{
zacw@1397
   369
	AIChat *chat = (AIChat *)context;
zacw@1397
   370
	
zacw@1397
   371
	AIGroupChatFlags flagA = highestFlag([chat flagsForContact:objectA]), flagB = highestFlag([chat flagsForContact:objectB]);
zacw@1397
   372
	
zacw@1397
   373
	if(flagA > flagB) {
zacw@1397
   374
		return NSOrderedAscending;
zacw@1397
   375
	} else if (flagA < flagB) {
zacw@1397
   376
		return NSOrderedDescending;
zacw@1397
   377
	} else {
zacw@1403
   378
		return [[chat displayNameForContact:objectA] caseInsensitiveCompare:[chat displayNameForContact:objectB]];
zacw@1397
   379
	}
zacw@1397
   380
}
zacw@1397
   381
zacw@1395
   382
/*!
zacw@1395
   383
 * @brief Resorts our participants
zacw@1395
   384
 *
zacw@1395
   385
 * This is called when our list objects change.
zacw@1395
   386
 */
zacw@1395
   387
- (void)resortParticipants
zacw@1395
   388
{
zacw@1397
   389
	[participatingContacts sortUsingFunction:userListSort context:self];
zacw@1395
   390
}
zacw@1395
   391
zacw@1395
   392
/*!
zacw@1395
   393
 * @brief Remove the saved values for a contact
zacw@1395
   394
 *
zacw@1395
   395
 * Removes any values which are dependent upon the contact, such as
zacw@1395
   396
 * its flags or alias.
zacw@1406
   397
*/
zacw@1404
   398
- (void)removeSavedValuesForContactUID:(NSString *)contactUID
zacw@1395
   399
{
zacw@1404
   400
	[participatingContactsFlags removeObjectForKey:contactUID];
zacw@1404
   401
	[participatingContactsAliases removeObjectForKey:contactUID];
zacw@1395
   402
}
zacw@1395
   403
David@0
   404
- (void)addParticipatingListObject:(AIListContact *)inObject notify:(BOOL)notify
David@0
   405
{
David@1013
   406
	[self addParticipatingListObjects:[NSArray arrayWithObject:inObject] notify:notify];
David@1013
   407
}
David@0
   408
David@1013
   409
- (void)addParticipatingListObjects:(NSArray *)inObjects notify:(BOOL)notify
David@1013
   410
{
David@1013
   411
	NSMutableArray *contacts = [[inObjects mutableCopy] autorelease];
David@1013
   412
David@1013
   413
	for (AIListObject *obj in inObjects) {
David@1013
   414
		if ([self containsObject:obj] || ![self canContainObject:obj])
David@1013
   415
			[contacts removeObject:obj];
David@0
   416
	}
David@1013
   417
	
David@1013
   418
	[participatingContacts addObjectsFromArray:contacts];
David@1013
   419
	[adium.chatController chat:self addedListContacts:contacts notify:notify];
David@0
   420
}
David@0
   421
David@0
   422
// Invite a list object to join the chat. Returns YES if the chat joins, NO otherwise
David@0
   423
- (BOOL)inviteListContact:(AIListContact *)inContact withMessage:(NSString *)inviteMessage
David@0
   424
{
David@108
   425
	return ([self.account inviteContact:inContact toChat:self withMessage:inviteMessage]);
David@0
   426
}
David@0
   427
David@828
   428
@synthesize preferredListObject = preferredContact;
David@0
   429
David@0
   430
//If this chat only has one participating list object, it is returned.  Otherwise, nil is returned
David@0
   431
- (AIListContact *)listObject
David@0
   432
{
Peter@1061
   433
	if (self.countOfContainedObjects == 1 && !self.isGroupChat) {
David@828
   434
		return [self.containedObjects objectAtIndex:0];
David@108
   435
	}
David@108
   436
David@108
   437
	return nil;
David@0
   438
}
David@108
   439
David@0
   440
- (void)setListObject:(AIListContact *)inListObject
David@0
   441
{
David@108
   442
	if (inListObject != self.listObject) {
Peter@1061
   443
		if (self.countOfContainedObjects) {
David@828
   444
			[participatingContacts removeObjectAtIndex:0];
David@0
   445
		}
David@950
   446
		[self addParticipatingListObject:inListObject notify:YES];
David@0
   447
David@0
   448
		//Clear any local caches relying on the list object
David@0
   449
		[self clearListObjectStatuses];
David@0
   450
		[self clearUniqueChatID];
David@0
   451
David@0
   452
		//Notify once the destination has been changed
David@1109
   453
		[[NSNotificationCenter defaultCenter] postNotificationName:Chat_DestinationChanged object:self];
David@0
   454
	}
David@0
   455
}
David@0
   456
David@0
   457
- (NSString *)uniqueChatID
David@0
   458
{
David@0
   459
	if (!uniqueChatID) {
David@812
   460
		if (self.isGroupChat) {
David@828
   461
			uniqueChatID = [[NSString alloc] initWithFormat:@"%@.%i", self.name, nextChatNumber++];
David@0
   462
		} else {			
David@828
   463
			uniqueChatID = [self.listObject.internalObjectID retain];
David@0
   464
		}
David@0
   465
David@0
   466
		if (!uniqueChatID) {
David@828
   467
			uniqueChatID = [[NSString alloc] initWithFormat:@"UnknownChat.%i", nextChatNumber++];
David@0
   468
			NSLog(@"Warning: Unknown chat %p",self);
David@0
   469
		}
David@0
   470
	}
David@0
   471
David@0
   472
	return uniqueChatID;
David@0
   473
}
David@0
   474
David@0
   475
- (void)clearUniqueChatID
David@0
   476
{
David@0
   477
	[uniqueChatID release]; uniqueChatID = nil;
David@0
   478
}
David@0
   479
Evan@1666
   480
- (NSString *)internalObjectID
Evan@1666
   481
{
Evan@1666
   482
	return self.uniqueChatID;
Evan@1666
   483
}
Evan@1666
   484
Evan@1666
   485
David@0
   486
//Content --------------------------------------------------------------------------------------------------------------
David@0
   487
#pragma mark Content
David@0
   488
David@0
   489
/*!
David@0
   490
 * @brief Informs the chat that the core and the account are ready to begin filtering and sending a content object
David@0
   491
 *
David@0
   492
 * If there is only one object in pendingOutgoingContentObjects after adding inObject, we should send immedaitely.
David@0
   493
 * However, if other objects are in it, we should wait for them to be removed, as they are chronologically first.
David@0
   494
 * If we are asked if we should begin sending the earliest object in pendingOutgoingContentObjects, the answer is YES.
David@0
   495
 *
David@0
   496
 * @param inObject The object being sent
David@0
   497
 * @result YES if the object should be sent immediately; NO if another object is in process so we should wait
David@0
   498
 */
David@0
   499
- (BOOL)shouldBeginSendingContentObject:(AIContentObject *)inObject
David@0
   500
{
David@3
   501
	NSInteger	currentIndex = [pendingOutgoingContentObjects indexOfObjectIdenticalTo:inObject];
David@0
   502
David@0
   503
	//Don't add the object twice when we are called from -[AIChat finishedSendingContentObject]
David@0
   504
	if (currentIndex == NSNotFound) {
David@0
   505
		[pendingOutgoingContentObjects addObject:inObject];		
David@0
   506
	}
David@0
   507
David@828
   508
	return pendingOutgoingContentObjects.count == 1 || currentIndex == 0;
David@0
   509
}
David@0
   510
David@0
   511
/*!
David@0
   512
 * @brief Informs the chat that an outgoing content object was sent and dispalyed.
David@0
   513
 *
David@0
   514
 * It is no longer pending, so we remove it from that array.
David@0
   515
 * If there are more pending objects, trigger sending the next.
David@0
   516
 *
David@0
   517
 * @param inObject The object with which we are finished
David@0
   518
 */
David@0
   519
- (void)finishedSendingContentObject:(AIContentObject *)inObject
David@0
   520
{
David@0
   521
	[pendingOutgoingContentObjects removeObjectIdenticalTo:inObject];
David@0
   522
	
David@828
   523
	if (pendingOutgoingContentObjects.count) {
David@95
   524
		[adium.contentController sendContentObject:[pendingOutgoingContentObjects objectAtIndex:0]];
David@0
   525
	}
David@0
   526
}
David@0
   527
David@0
   528
- (AIChatSendingAbilityType)messageSendingAbility
David@0
   529
{
David@0
   530
	AIChatSendingAbilityType sendingAbilityType;
David@0
   531
David@812
   532
	if (self.isGroupChat) {
David@828
   533
		if (self.account.online) {
David@0
   534
			//XXX Liar!
David@0
   535
			sendingAbilityType = AIChatCanSendMessageNow;
David@0
   536
		} else {
David@0
   537
			sendingAbilityType = AIChatCanNotSendMessage;
David@0
   538
		}
David@0
   539
David@0
   540
	} else {
David@828
   541
		if (self.account.online) {
David@108
   542
			AIListContact *listObject = self.listObject;
David@0
   543
			
David@828
   544
			if (listObject.online || listObject.isStranger) {
David@0
   545
				sendingAbilityType = AIChatCanSendMessageNow;
David@108
   546
			} else if ([self.account canSendOfflineMessageToContact:listObject]) {
David@0
   547
				sendingAbilityType = AIChatCanSendViaServersideOfflineMessage;				
David@108
   548
			} else if ([self.account maySendMessageToInvisibleContact:listObject]) {
Evan@47
   549
				sendingAbilityType = AIChatMayNotBeAbleToSendMessage;	
David@0
   550
			} else {
Evan@47
   551
				sendingAbilityType = AIChatCanNotSendMessage;
David@0
   552
			}
David@0
   553
David@0
   554
		} else {
David@0
   555
			sendingAbilityType = AIChatCanNotSendMessage;
David@0
   556
		}		
David@0
   557
	}
David@0
   558
	
David@0
   559
	return sendingAbilityType;
David@0
   560
}
David@0
   561
David@0
   562
- (BOOL)canSendImages
David@0
   563
{
David@108
   564
	return [self.account canSendImagesForChat:self];
David@0
   565
}
David@0
   566
David@108
   567
- (NSUInteger)unviewedContentCount
David@0
   568
{
David@0
   569
	return [self integerValueForProperty:KEY_UNVIEWED_CONTENT];
David@0
   570
}
David@0
   571
zacw@1505
   572
- (NSUInteger)unviewedMentionCount
zacw@1505
   573
{
zacw@1505
   574
	return [self integerValueForProperty:KEY_UNVIEWED_MENTION];	
zacw@1505
   575
}
zacw@1505
   576
David@0
   577
- (void)incrementUnviewedContentCount
David@0
   578
{
David@0
   579
	int currentUnviewed = [self integerValueForProperty:KEY_UNVIEWED_CONTENT];
David@0
   580
	[self setValue:[NSNumber numberWithInt:(currentUnviewed+1)]
David@0
   581
					 forProperty:KEY_UNVIEWED_CONTENT
David@0
   582
					 notify:NotifyNow];
David@0
   583
}
David@0
   584
zacw@1505
   585
- (void)incrementUnviewedMentionCount
zacw@1505
   586
{
zacw@1505
   587
	int currentUnviewed = [self integerValueForProperty:KEY_UNVIEWED_MENTION];
zacw@1505
   588
	[self setValue:[NSNumber numberWithInt:(currentUnviewed+1)]
zacw@1505
   589
	   forProperty:KEY_UNVIEWED_MENTION
zacw@1505
   590
			notify:NotifyNow];
zacw@1505
   591
}
zacw@1505
   592
David@0
   593
- (void)clearUnviewedContentCount
David@0
   594
{
zacw@1505
   595
	// We also want to clear mention for the same situations we clear normal content.
zacw@1505
   596
	[self setValue:nil forProperty:KEY_UNVIEWED_MENTION notify:NotifyNow];
David@0
   597
	[self setValue:nil forProperty:KEY_UNVIEWED_CONTENT notify:NotifyNow];
David@0
   598
}
David@0
   599
Evan@361
   600
#pragma mark Logging
Evan@361
   601
Evan@361
   602
- (BOOL)shouldLog
Evan@361
   603
{
Evan@361
   604
	return [self.account shouldLogChat:self];
Evan@361
   605
}
Evan@361
   606
David@0
   607
#pragma mark AIContainingObject protocol
David@0
   608
//AIContainingObject protocol
zacw@1092
   609
- (NSArray *)visibleContainedObjects
zacw@1092
   610
{
zacw@1092
   611
	return self.containedObjects;
zacw@1092
   612
}
David@0
   613
- (NSArray *)containedObjects
David@0
   614
{
Peter@1060
   615
	return [[participatingContacts copy] autorelease];
David@0
   616
}
Peter@1061
   617
- (NSUInteger)countOfContainedObjects
David@0
   618
{
Peter@1061
   619
	return [participatingContacts count];
David@0
   620
}
David@0
   621
David@0
   622
- (BOOL)containsObject:(AIListObject *)inObject
David@0
   623
{
David@1171
   624
	return [participatingContacts containsObjectIdenticalTo:inObject];
David@0
   625
}
David@0
   626
David@828
   627
- (id)visibleObjectAtIndex:(NSUInteger)index
David@0
   628
{
David@1171
   629
	return [participatingContacts objectAtIndex:index];
David@0
   630
}
David@0
   631
David@189
   632
- (NSUInteger)visibleIndexOfObject:(AIListObject *)obj
David@0
   633
{
David@825
   634
	if(![[AIContactHidingController sharedController] visibilityOfListObject:obj inContainer:self])
David@189
   635
		return NSNotFound;
David@1171
   636
	return [participatingContacts indexOfObject:obj];
David@0
   637
}
David@0
   638
David@0
   639
//Retrieve a specific object by service and UID
David@0
   640
- (AIListObject *)objectWithService:(AIService *)inService UID:(NSString *)inUID
David@0
   641
{
David@79
   642
	for (AIListContact *object in self) {
David@828
   643
		if ([inUID isEqualToString:object.UID] && object.service == inService)
David@79
   644
			return object;
David@0
   645
	}
David@0
   646
	
David@79
   647
	return nil;
David@0
   648
}
David@0
   649
David@382
   650
- (NSArray *)uniqueContainedObjects
David@0
   651
{
David@108
   652
	return self.containedObjects;
David@0
   653
}
David@0
   654
David@0
   655
- (void)removeObject:(AIListObject *)inObject
David@0
   656
{
David@827
   657
	if ([self containsObject:inObject]) {
David@827
   658
		AIListContact *contact = (AIListContact *)inObject; //if we contain it, it has to be an AIListContact
David@827
   659
		
David@827
   660
		//make sure removing it from the array doesn't deallocate it immediately, since we need it for -chat:removedListContact:
David@827
   661
		[inObject retain];
David@827
   662
		
David@828
   663
		[participatingContacts removeObject:inObject];
zacw@1395
   664
		
zacw@1404
   665
		[self removeSavedValuesForContactUID:inObject.UID];
David@0
   666
David@827
   667
		[adium.chatController chat:self removedListContact:contact];
David@0
   668
zacw@1431
   669
		if (contact.isStranger &&
zacw@1438
   670
			![adium.chatController allGroupChatsContainingContact:contact.parentContact].count &&
zacw@1431
   671
			![adium.chatController existingChatWithContact:contact.parentContact]) {
David@827
   672
			[adium.contactController accountDidStopTrackingContact:contact];
David@827
   673
		}
David@827
   674
		
David@827
   675
		[inObject release];
David@0
   676
	}
David@0
   677
}
David@0
   678
Evan@361
   679
- (void)removeObjectAfterAccountStopsTracking:(AIListObject *)object
Evan@361
   680
{
David@827
   681
	[self removeObject:object]; //does nothing if we've already removed it
Evan@361
   682
}
Evan@361
   683
David@0
   684
- (void)removeAllParticipatingContactsSilently
David@0
   685
{
David@828
   686
	for (AIListContact *listContact in self) {
zacw@1489
   687
		if (listContact.isStranger &&
zacw@1489
   688
			![adium.chatController existingChatWithContact:listContact.parentContact] &&
zacw@1489
   689
			![adium.chatController allGroupChatsContainingContact:listContact.parentContact].count) {
David@827
   690
			[adium.contactController accountDidStopTrackingContact:listContact];
David@0
   691
		}
David@0
   692
	}
David@0
   693
David@828
   694
	[participatingContacts removeAllObjects];
zacw@1395
   695
	[participatingContactsFlags removeAllObjects];
zacw@1395
   696
	[participatingContactsAliases removeAllObjects];
David@0
   697
David@1109
   698
	[[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
David@0
   699
											  object:self];
David@0
   700
}
David@0
   701
David@79
   702
@synthesize expanded;
David@79
   703
Peter@83
   704
- (BOOL)isExpandable
David@0
   705
{
David@0
   706
	return NO;
David@0
   707
}
David@0
   708
David@108
   709
- (NSUInteger)visibleCount
David@0
   710
{
Peter@1061
   711
	return self.countOfContainedObjects;
David@0
   712
}
David@0
   713
David@0
   714
- (NSString *)contentsBasedIdentifier
David@0
   715
{
David@715
   716
	return [NSString stringWithFormat:@"%@-%@.%@",self.name, self.account.service.serviceID, self.account.UID];
David@0
   717
}
David@0
   718
David@0
   719
//Not used
David@753
   720
- (float)smallestOrder { return 0; }
David@753
   721
- (float)largestOrder { return 1E10; }
David@753
   722
- (float)orderIndexForObject:(AIListObject *)listObject { return 0; }
David@753
   723
- (void)listObject:(AIListObject *)listObject didSetOrderIndex:(float)inOrderIndex {};
David@0
   724
David@0
   725
zacw@1442
   726
#pragma mark Ignoring
David@0
   727
/*!
David@0
   728
 * @brief Set the ignored state of a contact
David@0
   729
 *
David@0
   730
 * @param inContact The contact whose state is to be changed
David@0
   731
 * @param isIgnored YES to ignore the contact; NO to not ignore the contact
David@0
   732
 */
David@0
   733
- (void)setListContact:(AIListContact *)inContact isIgnored:(BOOL)isIgnored
David@0
   734
{
zacw@1442
   735
	if (self.account.accountManagesGroupChatIgnore) {
zacw@1442
   736
		[self.account setContact:inContact ignored:isIgnored inChat:self];
zacw@1442
   737
	} else {
zacw@1442
   738
		//Create ignoredListContacts if needed
zacw@1442
   739
		if (isIgnored && !ignoredListContacts) {
zacw@1442
   740
			ignoredListContacts = [[NSMutableSet alloc] init];	
zacw@1442
   741
		}
zacw@1442
   742
zacw@1442
   743
		if (isIgnored) {
zacw@1442
   744
			[ignoredListContacts addObject:inContact];
zacw@1442
   745
		} else {
zacw@1442
   746
			[ignoredListContacts removeObject:inContact];		
zacw@1442
   747
		}	
David@0
   748
	}
David@0
   749
}
David@0
   750
David@0
   751
/*!
David@0
   752
 * @brief Is the passed object ignored?
David@0
   753
 *
David@0
   754
 * @param inContact The contact to check
David@0
   755
 * @result YES if the contact is ignored; NO if it is not
David@0
   756
 */
David@0
   757
- (BOOL)isListContactIgnored:(AIListObject *)inContact
David@0
   758
{
zacw@1442
   759
	if (self.account.accountManagesGroupChatIgnore) {
zacw@1442
   760
		return [self.account contact:(AIListContact *)inContact isIgnoredInChat:self];
zacw@1442
   761
	} else {
zacw@1442
   762
		return [ignoredListContacts containsObject:inContact];
zacw@1442
   763
	}
David@0
   764
}
David@0
   765
David@0
   766
#pragma mark Comparison
David@0
   767
- (BOOL)isEqual:(id)inChat
David@0
   768
{
David@0
   769
	return (inChat == self);
David@0
   770
}
David@0
   771
David@0
   772
#pragma mark Debugging
David@0
   773
- (NSString *)description
David@0
   774
{
David@0
   775
	return [NSString stringWithFormat:@"%@:%@",
David@0
   776
		[super description],
David@0
   777
		(uniqueChatID ? uniqueChatID : @"<new>")];
David@0
   778
}
David@0
   779
zacw@1301
   780
#pragma mark Group Chats
zacw@1301
   781
zacw@1690
   782
@synthesize isGroupChat, showJoinLeave, hideUserIconAndStatus, topic, topicSetter;
zacw@1301
   783
zacw@1301
   784
/*!
zacw@1302
   785
 * @brief Does this chat support topics?
zacw@1302
   786
 */
zacw@1302
   787
- (BOOL)supportsTopic
zacw@1302
   788
{
zacw@1302
   789
	return account.groupChatsSupportTopic;
zacw@1302
   790
}
zacw@1302
   791
zacw@1302
   792
/*!
zacw@1301
   793
 * @brief Update the topic.
zacw@1301
   794
 */
zacw@1301
   795
- (void)updateTopic:(NSString *)inTopic withSource:(AIListContact *)contact
zacw@1301
   796
{
zacw@1301
   797
	[topic release];
zacw@1301
   798
	topic = [inTopic retain];
zacw@1301
   799
	
zacw@1301
   800
	self.topicSetter = contact;
zacw@1301
   801
	
zacw@1301
   802
	// Apply the new topic to the message view
zacw@1301
   803
	AIContentTopic *contentTopic = [AIContentTopic topicInChat:self
zacw@1301
   804
													withSource:contact
zacw@1301
   805
												   destination:nil
zacw@1301
   806
														  date:[NSDate date]
zacw@1738
   807
													   message:[NSAttributedString stringWithString:topic ?: @""]];
zacw@1301
   808
	
zacw@1488
   809
	// The content controller has huge problems with blank messages being let through.
zacw@1488
   810
	if (!topic.length) {
zacw@1488
   811
		contentTopic.message = CONTENT_TOPIC_MESSAGE_ACTUALLY_EMPTY;
zacw@1488
   812
		contentTopic.actuallyBlank = YES;
zacw@1488
   813
	}
zacw@1488
   814
	
zacw@1301
   815
	[adium.contentController receiveContentObject:contentTopic];
zacw@1301
   816
}
zacw@1301
   817
zacw@1301
   818
/*!
zacw@1301
   819
 * @brief Set the chat's topic, telling the account to update it.
zacw@1301
   820
 */
zacw@1301
   821
- (void)setTopic:(NSString *)inTopic
zacw@1301
   822
{
zacw@1313
   823
	if (self.supportsTopic) {
zacw@1475
   824
		// We mess with the topic, replacing nbsp with spaces; make sure we're not setting an identical one other than this.
zacw@1475
   825
		NSString *tempTopic = [topic stringByReplacingOccurrencesOfString:@"\u00A0" withString:@" "];
zacw@1475
   826
		if ([tempTopic isEqualToString:inTopic]) {
zacw@1303
   827
			AILogWithSignature(@"Not setting topic for %@, already the same.", self);
zacw@1303
   828
		} else {
zacw@1475
   829
			AILogWithSignature(@"Setting %@ topic to: %@", self, topic);
zacw@1303
   830
			[account setTopic:inTopic forChat:self];
zacw@1303
   831
		}
zacw@1301
   832
	} else {
zacw@1301
   833
		AILogWithSignature(@"Attempt to set %@ topic when account doesn't support it.");
zacw@1301
   834
	}
zacw@1301
   835
}
zacw@1301
   836
David@0
   837
#pragma mark Custom emoticons
David@0
   838
David@0
   839
- (void)addCustomEmoticon:(AIEmoticon *)inEmoticon
David@0
   840
{
David@0
   841
	if (!customEmoticons) customEmoticons = [[NSMutableSet alloc] init];
David@0
   842
	[customEmoticons addObject:inEmoticon];
David@0
   843
}
David@0
   844
David@108
   845
@synthesize customEmoticons;
David@0
   846
David@0
   847
#pragma mark Errors
David@0
   848
David@0
   849
/*!
David@0
   850
 * @brief Inform the chat that an error occurred
David@0
   851
 *
David@0
   852
 * @param type An NSNumber containing an AIChatErrorType
David@0
   853
 */
David@0
   854
- (void)receivedError:(NSNumber *)type
David@0
   855
{
David@0
   856
	//Notify observers
David@0
   857
	[self setValue:type forProperty:KEY_CHAT_ERROR notify:NotifyNow];
David@0
   858
David@0
   859
	//No need to continue to store the NSNumber
David@0
   860
	[self setValue:nil forProperty:KEY_CHAT_ERROR notify:NotifyNever];
David@0
   861
}
David@0
   862
David@0
   863
#pragma mark Room commands
David@0
   864
- (NSMenu *)actionMenu
David@0
   865
{	
zacw@1609
   866
	return [self.account actionMenuForChat:self];
David@0
   867
}
David@0
   868
- (void)setActionMenu:(NSMenu *)inMenu {};
David@0
   869
David@0
   870
#pragma mark Applescript
David@0
   871
David@0
   872
- (NSScriptObjectSpecifier *)objectSpecifier
David@0
   873
{
David@0
   874
	//the chat may not be in a window! Just reference it from the application...
David@0
   875
	//get my window
David@0
   876
	NSScriptClassDescription *containerClassDesc = (NSScriptClassDescription *)[NSScriptClassDescription classDescriptionForClass:[NSApp class]];
David@0
   877
	return [[[NSUniqueIDSpecifier allocWithZone:[self zone]]
David@0
   878
		initWithContainerClassDescription:containerClassDesc
David@0
   879
		containerSpecifier:nil key:@"chats" uniqueID:[self uniqueChatID]] autorelease];
David@0
   880
}
David@0
   881
David@0
   882
- (unsigned int)index
David@0
   883
{
David@0
   884
	//what we're going to do is find this tab in the tab view's hierarchy, so as to get its index
David@497
   885
	AIMessageWindowController *windowController = self.chatContainer.windowController;
David@0
   886
David@0
   887
	NSArray *chats = [windowController containedChats];
David@0
   888
	for (unsigned int i=0;i<[chats count];i++) {
David@0
   889
		if ([chats objectAtIndex:i] == self)
David@0
   890
			return i+1; //one based
David@0
   891
	}
David@0
   892
	NSAssert(NO, @"This chat is weird.");
David@0
   893
	return 0;
David@0
   894
}
David@0
   895
/*- (void)setIndex:(unsigned int)index
David@0
   896
{
David@497
   897
	AIMessageWindowController *windowController = self.chatContainer.windowController;
David@0
   898
	NSArray *chats = [windowController containedChats];
David@0
   899
	NSAssert (index-1 < [chats count], @"Don't let index be bigger than the count!");
David@0
   900
	NSLog(@"Trying to move %@ in %@ to %u",messageTab,window,index-1);
David@0
   901
	[windowController moveTabViewItem:messageTab toIndex:index-1]; //This is bad bad bad. Why?
David@0
   902
	
David@0
   903
}*/
David@0
   904
David@0
   905
- (NSString *)scriptingName
David@0
   906
{
David@828
   907
	NSString *aName = self.name;
David@0
   908
	if (!aName)
David@837
   909
		aName = self.listObject.UID;
David@0
   910
	return aName;
David@0
   911
}
David@0
   912
David@0
   913
- (id <AIChatContainer>)chatContainer
David@0
   914
{
David@0
   915
	return [self valueForProperty:@"MessageTabViewItem"];
David@0
   916
}
David@0
   917
David@0
   918
- (id)handleCloseScriptCommand:(NSCloseCommand *)closeCommand
David@0
   919
{
David@100
   920
	[adium.interfaceController closeChat:self];
David@0
   921
	return nil;
David@0
   922
}
David@0
   923
David@0
   924
- (void)setUniqueChatID:(NSString *)str
David@0
   925
{
David@0
   926
	[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
David@0
   927
}
David@0
   928
David@0
   929
- (AIAccount *)scriptingAccount
David@0
   930
{
David@108
   931
	return self.account;
David@0
   932
}
David@0
   933
David@0
   934
- (void)setScriptingAccount:(AIAccount *)a
David@0
   935
{
David@0
   936
	[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
David@0
   937
	[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't set the account of a chat."];
David@0
   938
}
David@0
   939
David@0
   940
- (NSString *)content
David@0
   941
{
David@0
   942
	/*AITranscriptLogEnumerator *e = [[[AITranscriptLogReader alloc] initWithChat:self] autorelease];
David@0
   943
	AIContentMessage *m;
David@0
   944
	NSMutableString *result = [[[NSMutableString alloc] init] autorelease];
David@0
   945
	while ((m = [e nextObject])) {
David@0
   946
		[result appendFormat:@"%@\n",[m messageString]];
David@0
   947
	}
David@0
   948
	return result;*/
David@0
   949
	[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
David@0
   950
	[[NSScriptCommand currentCommand] setScriptErrorString:@"Still unsupported."];
David@0
   951
	return nil;
David@0
   952
}
David@0
   953
David@0
   954
/*!
David@0
   955
 * @brief Applescript command to send a message in this chat
David@0
   956
 */
David@0
   957
- (id)sendScriptCommand:(NSScriptCommand *)command {
David@0
   958
	NSDictionary	*evaluatedArguments = [command evaluatedArguments];
David@0
   959
	NSString		*message = [evaluatedArguments objectForKey:@"message"];
David@0
   960
	NSURL			*fileURL = [evaluatedArguments objectForKey:@"withFile"];
David@0
   961
	
David@0
   962
	//Send any message we were told to send
David@0
   963
	if (message && [message length]) {
David@0
   964
		//Take the string and turn it into an attributed string (in case we were passed HTML)
David@0
   965
		NSAttributedString  *attributedMessage = [AIHTMLDecoder decodeHTML:message];
David@0
   966
		AIContentMessage	*messageContent;
David@0
   967
		messageContent = [AIContentMessage messageInChat:self
David@108
   968
											  withSource:self.account
David@108
   969
											 destination:self.listObject
David@0
   970
													date:nil
David@0
   971
												 message:attributedMessage
David@0
   972
											   autoreply:NO];
David@0
   973
		
David@95
   974
		[adium.contentController sendContentObject:messageContent];
David@0
   975
	}
David@0
   976
	
David@0
   977
	//Send any file we were told to send to every participating list object (anyone remember the AOL mass mailing zareW scene?)
David@828
   978
	if (fileURL && fileURL.path.length) {
David@0
   979
		
David@79
   980
		for (AIListContact *listContact in self) {
David@0
   981
			AIListContact   *targetFileTransferContact;
David@0
   982
			
David@0
   983
			//Make sure we know where we are sending the file by finding the best contact for
David@0
   984
			//sending CONTENT_FILE_TRANSFER_TYPE.
David@89
   985
			if ((targetFileTransferContact = [adium.contactController preferredContactForContentType:CONTENT_FILE_TRANSFER_TYPE
David@0
   986
																						forListContact:listContact])) {
David@100
   987
				[adium.fileTransferController sendFile:[fileURL path]
David@0
   988
										   toListContact:targetFileTransferContact];
David@0
   989
			} else {
David@0
   990
				AILogWithSignature(@"No contact available to receive files to %@", listContact);
David@0
   991
				NSBeep();
David@0
   992
			}
David@0
   993
		}
David@0
   994
	}
David@0
   995
	
David@0
   996
	return nil;
David@0
   997
}
David@0
   998
David@79
   999
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
David@79
  1000
{
David@108
  1001
	return [self.containedObjects countByEnumeratingWithState:state objects:stackbuf count:len];
David@79
  1002
}
David@79
  1003
David@52
  1004
- (BOOL) canContainObject:(id)obj
David@52
  1005
{
David@52
  1006
	return [obj isKindOfClass:[AIListContact class]];
David@52
  1007
}
David@52
  1008
David@0
  1009
@end