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.
2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 #import <Adium/AIAccount.h>
17 #import <Adium/AIChat.h>
18 #import <Adium/AIContentMessage.h>
19 #import <Adium/AIContentTopic.h>
20 #import <Adium/AIListContact.h>
21 #import <Adium/AIService.h>
22 #import <Adium/ESFileTransfer.h>
23 #import <Adium/AIHTMLDecoder.h>
24 #import <Adium/AIServiceIcons.h>
25 #import <Adium/AIUserIcons.h>
26 #import <Adium/AIContactHidingController.h>
27 #import <Adium/AIContactControllerProtocol.h>
28 #import <Adium/AIContentControllerProtocol.h>
29 #import <Adium/AIChatControllerProtocol.h>
30 #import <Adium/AIInterfaceControllerProtocol.h>
31 #import <Adium/AISortController.h>
33 #import <AIUtilities/AIArrayAdditions.h>
34 #import <AIUtilities/AIMutableOwnerArray.h>
35 #import <AIUtilities/AIAttributedStringAdditions.h>
37 #import "AIMessageWindowController.h"
38 #import "AIMessageWindow.h"
39 #import "AIInterfaceControllerProtocol.h"
40 #import "AIWebKitMessageViewController.h"
44 - (id)initForAccount:(AIAccount *)inAccount;
45 - (void)clearUniqueChatID;
46 - (void)clearListObjectStatuses;
49 @implementation AIChat
51 static int nextChatNumber = 0;
53 + (id)chatForAccount:(AIAccount *)inAccount
55 return [[[self alloc] initForAccount:inAccount] autorelease];
58 - (id)initForAccount:(AIAccount *)inAccount
60 if ((self = [super init])) {
62 account = [inAccount retain];
63 participatingContacts = [[NSMutableArray alloc] init];
64 participatingContactsFlags = [[NSMutableDictionary alloc] init];
65 participatingContactsAliases = [[NSMutableDictionary alloc] init];
66 dateOpened = [[NSDate date] retain];
68 ignoredListContacts = nil;
72 customEmoticons = nil;
73 hasSentOrReceivedContent = NO;
75 pendingOutgoingContentObjects = [[NSMutableArray alloc] init];
77 AILog(@"[AIChat: %x initForAccount]",self);
88 AILog(@"[%@ dealloc]",self);
91 [self removeAllParticipatingContactsSilently];
92 [participatingContacts release];
93 [participatingContactsFlags release];
94 [participatingContactsAliases release];
96 [ignoredListContacts release];
97 [pendingOutgoingContentObjects release];
98 [uniqueChatID release]; uniqueChatID = nil;
99 [customEmoticons release]; customEmoticons = nil;
100 [topic release]; [topicSetter release];
106 - (NSImage *)chatImage
108 AIListContact *listObject = self.listObject;
109 NSImage *image = nil;
112 image = listObject.parentContact.userIcon;
113 if (!image) image = [AIServiceIcons serviceIconForObject:listObject type:AIServiceIconLarge direction:AIIconNormal];
115 image = [AIServiceIcons serviceIconForObject:self.account type:AIServiceIconLarge direction:AIIconNormal];
122 - (NSImage *)chatMenuImage
124 AIListObject *listObject = self.listObject;
125 NSImage *chatMenuImage = nil;
128 chatMenuImage = [AIUserIcons menuUserIconForObject:listObject];
130 chatMenuImage = [AIServiceIcons serviceIconForObject:account
131 type:AIServiceIconSmall
132 direction:AIIconNormal];
135 return chatMenuImage;
139 //Associated Account ---------------------------------------------------------------------------------------------------
140 #pragma mark Associated Account
141 - (AIAccount *)account
146 - (void)setAccount:(AIAccount *)inAccount
148 if (inAccount != account) {
150 account = [inAccount retain];
152 //The uniqueChatID may depend upon the account, so clear it
153 [self clearUniqueChatID];
154 [[NSNotificationCenter defaultCenter] postNotificationName:Chat_SourceChanged object:self]; //Notify
158 /*@brief: holds information passed upon the creation of the chat:
159 * handle, server, etc.
161 - (NSDictionary *)chatCreationDictionary
163 return [self valueForProperty:@"ChatCreationInfo"];
166 - (void)setChatCreationDictionary:(NSDictionary *)inDict
168 [self setValue:inDict
169 forProperty:@"ChatCreationInfo"
173 @synthesize hasSentOrReceivedContent, isOpen, dateOpened;
175 //Status ---------------------------------------------------------------------------------------------------------------
178 - (void)didModifyProperties:(NSSet *)keys silent:(BOOL)silent
180 [adium.chatController chatStatusChanged:self
181 modifiedStatusKeys:keys
185 - (void)object:(id)inObject didChangeValueForProperty:(NSString *)key notify:(NotifyTiming)notify
187 //If our unviewed content changes or typing status changes, and we have a single list object,
188 //apply the change to that object as well so it can be cleanly reflected in the contact list.
189 if ([key isEqualToString:KEY_UNVIEWED_CONTENT] ||
190 [key isEqualToString:KEY_TYPING]) {
191 AIListObject *listObject = nil;
193 if (self.isGroupChat) {
194 listObject = (AIListContact *)[adium.contactController existingBookmarkForChat:self];
196 listObject = self.listObject;
199 if (listObject) [listObject setValue:[self valueForProperty:key] forProperty:key notify:notify];
202 [super object:inObject didChangeValueForProperty:key notify:notify];
205 - (void)clearListObjectStatuses
207 AIListObject *listObject = self.listObject;
210 [listObject setValue:nil forProperty:KEY_UNVIEWED_CONTENT notify:NotifyLater];
211 [listObject setValue:nil forProperty:KEY_TYPING notify:NotifyLater];
213 [listObject notifyOfChangedPropertiesSilently:NO];
217 //Secure chatting ------------------------------------------------------------------------------------------------------
218 - (void)setSecurityDetails:(NSDictionary *)securityDetails
220 [self setValue:securityDetails
221 forProperty:@"SecurityDetails"
224 - (NSDictionary *)securityDetails
226 return [self valueForProperty:@"SecurityDetails"];
231 return self.encryptionStatus != EncryptionStatus_None;
234 - (AIEncryptionStatus)encryptionStatus
236 AIEncryptionStatus encryptionStatus = EncryptionStatus_None;
238 NSDictionary *securityDetails = self.securityDetails;
239 if (securityDetails) {
240 NSNumber *detailsStatus;
241 if ((detailsStatus = [securityDetails objectForKey:@"EncryptionStatus"])) {
242 encryptionStatus = [detailsStatus intValue];
245 /* If we don't have a specific encryption status, but do have security details, assume
246 * encrypted and verified.
248 encryptionStatus = EncryptionStatus_Verified;
252 return encryptionStatus;
255 - (BOOL)supportsSecureMessagingToggling
257 return [account allowSecureMessagingTogglingForChat:self];
260 //Name ----------------------------------------------------------------------------------------------------------------
266 * @brief An identifier which can be used to look up this chat later
268 * Use uniqueChatID as a unique identifier for a contact-service combination.
269 * Only an account which created a chat should specify the identifier; it has no useful meaning outside that context.
271 @synthesize identifier;
273 - (NSString *)displayName
275 NSString *outName = [self displayArrayObjectForKey:@"Display Name"];
276 return outName ? outName : (name ? name : self.listObject.displayName);
279 - (void)setDisplayName:(NSString *)inDisplayName
281 [[self displayArrayForKey:@"Display Name"] setObject:inDisplayName
283 priorityLevel:Highest_Priority];
285 //The display array doesn't cause an attribute update; fake it.
286 [adium.chatController chatStatusChanged:self
287 modifiedStatusKeys:[NSSet setWithObject:@"Display Name"]
291 //Participating ListObjects --------------------------------------------------------------------------------------------
292 #pragma mark Participating ListObjects
295 * @brief The display name for the contact in this chat.
297 * @param contact The AIListObject whose display name should be created
299 * If the user has an alias set, the alias is used, otherwise the display name.
301 * @returns Display name
303 - (NSString *)displayNameForContact:(AIListObject *)contact
305 return [self aliasForContact:contact] ?: contact.displayName;
309 * @brief The flags for a given contact.
311 - (AIGroupChatFlags)flagsForContact:(AIListObject *)contact
313 return [[participatingContactsFlags objectForKey:contact.UID] integerValue];
317 * @brief The alias for a given contact
319 - (NSString *)aliasForContact:(AIListObject *)contact
321 NSString *alias = [participatingContactsAliases objectForKey:contact.UID];
323 if (!alias && self.isGroupChat) {
324 alias = [self.account fallbackAliasForContact:(AIListContact *)contact inChat:self];
331 * @brief Set the flags for a contact
333 * Note that this doesn't set the bitwise or; this directly sets the value passed.
335 - (void)setFlags:(AIGroupChatFlags)flags forContact:(AIListObject *)contact
337 [participatingContactsFlags setObject:[NSNumber numberWithInteger:flags]
342 * @brief Set the alias for a contact.
344 - (void)setAlias:(NSString *)alias forContact:(AIListObject *)contact
346 [participatingContactsAliases setObject:alias
350 AIGroupChatFlags highestFlag(AIGroupChatFlags flags)
352 if ((flags & AIGroupChatFounder) == AIGroupChatFounder)
353 return AIGroupChatFounder;
355 if ((flags & AIGroupChatOp) == AIGroupChatOp)
356 return AIGroupChatOp;
358 if ((flags & AIGroupChatHalfOp) == AIGroupChatHalfOp)
359 return AIGroupChatHalfOp;
361 if ((flags & AIGroupChatVoice) == AIGroupChatVoice)
362 return AIGroupChatVoice;
364 return AIGroupChatNone;
367 NSComparisonResult userListSort (id objectA, id objectB, void *context)
369 AIChat *chat = (AIChat *)context;
371 AIGroupChatFlags flagA = highestFlag([chat flagsForContact:objectA]), flagB = highestFlag([chat flagsForContact:objectB]);
374 return NSOrderedAscending;
375 } else if (flagA < flagB) {
376 return NSOrderedDescending;
378 return [[chat displayNameForContact:objectA] caseInsensitiveCompare:[chat displayNameForContact:objectB]];
383 * @brief Resorts our participants
385 * This is called when our list objects change.
387 - (void)resortParticipants
389 [participatingContacts sortUsingFunction:userListSort context:self];
393 * @brief Remove the saved values for a contact
395 * Removes any values which are dependent upon the contact, such as
396 * its flags or alias.
398 - (void)removeSavedValuesForContactUID:(NSString *)contactUID
400 [participatingContactsFlags removeObjectForKey:contactUID];
401 [participatingContactsAliases removeObjectForKey:contactUID];
404 - (void)addParticipatingListObject:(AIListContact *)inObject notify:(BOOL)notify
406 [self addParticipatingListObjects:[NSArray arrayWithObject:inObject] notify:notify];
409 - (void)addParticipatingListObjects:(NSArray *)inObjects notify:(BOOL)notify
411 NSMutableArray *contacts = [[inObjects mutableCopy] autorelease];
413 for (AIListObject *obj in inObjects) {
414 if ([self containsObject:obj] || ![self canContainObject:obj])
415 [contacts removeObject:obj];
418 [participatingContacts addObjectsFromArray:contacts];
419 [adium.chatController chat:self addedListContacts:contacts notify:notify];
422 // Invite a list object to join the chat. Returns YES if the chat joins, NO otherwise
423 - (BOOL)inviteListContact:(AIListContact *)inContact withMessage:(NSString *)inviteMessage
425 return ([self.account inviteContact:inContact toChat:self withMessage:inviteMessage]);
428 @synthesize preferredListObject = preferredContact;
430 //If this chat only has one participating list object, it is returned. Otherwise, nil is returned
431 - (AIListContact *)listObject
433 if (self.countOfContainedObjects == 1 && !self.isGroupChat) {
434 return [self.containedObjects objectAtIndex:0];
440 - (void)setListObject:(AIListContact *)inListObject
442 if (inListObject != self.listObject) {
443 if (self.countOfContainedObjects) {
444 [participatingContacts removeObjectAtIndex:0];
446 [self addParticipatingListObject:inListObject notify:YES];
448 //Clear any local caches relying on the list object
449 [self clearListObjectStatuses];
450 [self clearUniqueChatID];
452 //Notify once the destination has been changed
453 [[NSNotificationCenter defaultCenter] postNotificationName:Chat_DestinationChanged object:self];
457 - (NSString *)uniqueChatID
460 if (self.isGroupChat) {
461 uniqueChatID = [[NSString alloc] initWithFormat:@"%@.%i", self.name, nextChatNumber++];
463 uniqueChatID = [self.listObject.internalObjectID retain];
467 uniqueChatID = [[NSString alloc] initWithFormat:@"UnknownChat.%i", nextChatNumber++];
468 NSLog(@"Warning: Unknown chat %p",self);
475 - (void)clearUniqueChatID
477 [uniqueChatID release]; uniqueChatID = nil;
480 - (NSString *)internalObjectID
482 return self.uniqueChatID;
486 //Content --------------------------------------------------------------------------------------------------------------
490 * @brief Informs the chat that the core and the account are ready to begin filtering and sending a content object
492 * If there is only one object in pendingOutgoingContentObjects after adding inObject, we should send immedaitely.
493 * However, if other objects are in it, we should wait for them to be removed, as they are chronologically first.
494 * If we are asked if we should begin sending the earliest object in pendingOutgoingContentObjects, the answer is YES.
496 * @param inObject The object being sent
497 * @result YES if the object should be sent immediately; NO if another object is in process so we should wait
499 - (BOOL)shouldBeginSendingContentObject:(AIContentObject *)inObject
501 NSInteger currentIndex = [pendingOutgoingContentObjects indexOfObjectIdenticalTo:inObject];
503 //Don't add the object twice when we are called from -[AIChat finishedSendingContentObject]
504 if (currentIndex == NSNotFound) {
505 [pendingOutgoingContentObjects addObject:inObject];
508 return pendingOutgoingContentObjects.count == 1 || currentIndex == 0;
512 * @brief Informs the chat that an outgoing content object was sent and dispalyed.
514 * It is no longer pending, so we remove it from that array.
515 * If there are more pending objects, trigger sending the next.
517 * @param inObject The object with which we are finished
519 - (void)finishedSendingContentObject:(AIContentObject *)inObject
521 [pendingOutgoingContentObjects removeObjectIdenticalTo:inObject];
523 if (pendingOutgoingContentObjects.count) {
524 [adium.contentController sendContentObject:[pendingOutgoingContentObjects objectAtIndex:0]];
528 - (AIChatSendingAbilityType)messageSendingAbility
530 AIChatSendingAbilityType sendingAbilityType;
532 if (self.isGroupChat) {
533 if (self.account.online) {
535 sendingAbilityType = AIChatCanSendMessageNow;
537 sendingAbilityType = AIChatCanNotSendMessage;
541 if (self.account.online) {
542 AIListContact *listObject = self.listObject;
544 if (listObject.online || listObject.isStranger) {
545 sendingAbilityType = AIChatCanSendMessageNow;
546 } else if ([self.account canSendOfflineMessageToContact:listObject]) {
547 sendingAbilityType = AIChatCanSendViaServersideOfflineMessage;
548 } else if ([self.account maySendMessageToInvisibleContact:listObject]) {
549 sendingAbilityType = AIChatMayNotBeAbleToSendMessage;
551 sendingAbilityType = AIChatCanNotSendMessage;
555 sendingAbilityType = AIChatCanNotSendMessage;
559 return sendingAbilityType;
562 - (BOOL)canSendImages
564 return [self.account canSendImagesForChat:self];
567 - (NSUInteger)unviewedContentCount
569 return [self integerValueForProperty:KEY_UNVIEWED_CONTENT];
572 - (NSUInteger)unviewedMentionCount
574 return [self integerValueForProperty:KEY_UNVIEWED_MENTION];
577 - (void)incrementUnviewedContentCount
579 int currentUnviewed = [self integerValueForProperty:KEY_UNVIEWED_CONTENT];
580 [self setValue:[NSNumber numberWithInt:(currentUnviewed+1)]
581 forProperty:KEY_UNVIEWED_CONTENT
585 - (void)incrementUnviewedMentionCount
587 int currentUnviewed = [self integerValueForProperty:KEY_UNVIEWED_MENTION];
588 [self setValue:[NSNumber numberWithInt:(currentUnviewed+1)]
589 forProperty:KEY_UNVIEWED_MENTION
593 - (void)clearUnviewedContentCount
595 // We also want to clear mention for the same situations we clear normal content.
596 [self setValue:nil forProperty:KEY_UNVIEWED_MENTION notify:NotifyNow];
597 [self setValue:nil forProperty:KEY_UNVIEWED_CONTENT notify:NotifyNow];
604 return [self.account shouldLogChat:self];
607 #pragma mark AIContainingObject protocol
608 //AIContainingObject protocol
609 - (NSArray *)visibleContainedObjects
611 return self.containedObjects;
613 - (NSArray *)containedObjects
615 return [[participatingContacts copy] autorelease];
617 - (NSUInteger)countOfContainedObjects
619 return [participatingContacts count];
622 - (BOOL)containsObject:(AIListObject *)inObject
624 return [participatingContacts containsObjectIdenticalTo:inObject];
627 - (id)visibleObjectAtIndex:(NSUInteger)index
629 return [participatingContacts objectAtIndex:index];
632 - (NSUInteger)visibleIndexOfObject:(AIListObject *)obj
634 if(![[AIContactHidingController sharedController] visibilityOfListObject:obj inContainer:self])
636 return [participatingContacts indexOfObject:obj];
639 //Retrieve a specific object by service and UID
640 - (AIListObject *)objectWithService:(AIService *)inService UID:(NSString *)inUID
642 for (AIListContact *object in self) {
643 if ([inUID isEqualToString:object.UID] && object.service == inService)
650 - (NSArray *)uniqueContainedObjects
652 return self.containedObjects;
655 - (void)removeObject:(AIListObject *)inObject
657 if ([self containsObject:inObject]) {
658 AIListContact *contact = (AIListContact *)inObject; //if we contain it, it has to be an AIListContact
660 //make sure removing it from the array doesn't deallocate it immediately, since we need it for -chat:removedListContact:
663 [participatingContacts removeObject:inObject];
665 [self removeSavedValuesForContactUID:inObject.UID];
667 [adium.chatController chat:self removedListContact:contact];
669 if (contact.isStranger &&
670 ![adium.chatController allGroupChatsContainingContact:contact.parentContact].count &&
671 ![adium.chatController existingChatWithContact:contact.parentContact]) {
672 [adium.contactController accountDidStopTrackingContact:contact];
679 - (void)removeObjectAfterAccountStopsTracking:(AIListObject *)object
681 [self removeObject:object]; //does nothing if we've already removed it
684 - (void)removeAllParticipatingContactsSilently
686 for (AIListContact *listContact in self) {
687 if (listContact.isStranger &&
688 ![adium.chatController existingChatWithContact:listContact.parentContact] &&
689 ![adium.chatController allGroupChatsContainingContact:listContact.parentContact].count) {
690 [adium.contactController accountDidStopTrackingContact:listContact];
694 [participatingContacts removeAllObjects];
695 [participatingContactsFlags removeAllObjects];
696 [participatingContactsAliases removeAllObjects];
698 [[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
702 @synthesize expanded;
709 - (NSUInteger)visibleCount
711 return self.countOfContainedObjects;
714 - (NSString *)contentsBasedIdentifier
716 return [NSString stringWithFormat:@"%@-%@.%@",self.name, self.account.service.serviceID, self.account.UID];
720 - (float)smallestOrder { return 0; }
721 - (float)largestOrder { return 1E10; }
722 - (float)orderIndexForObject:(AIListObject *)listObject { return 0; }
723 - (void)listObject:(AIListObject *)listObject didSetOrderIndex:(float)inOrderIndex {};
726 #pragma mark Ignoring
728 * @brief Set the ignored state of a contact
730 * @param inContact The contact whose state is to be changed
731 * @param isIgnored YES to ignore the contact; NO to not ignore the contact
733 - (void)setListContact:(AIListContact *)inContact isIgnored:(BOOL)isIgnored
735 if (self.account.accountManagesGroupChatIgnore) {
736 [self.account setContact:inContact ignored:isIgnored inChat:self];
738 //Create ignoredListContacts if needed
739 if (isIgnored && !ignoredListContacts) {
740 ignoredListContacts = [[NSMutableSet alloc] init];
744 [ignoredListContacts addObject:inContact];
746 [ignoredListContacts removeObject:inContact];
752 * @brief Is the passed object ignored?
754 * @param inContact The contact to check
755 * @result YES if the contact is ignored; NO if it is not
757 - (BOOL)isListContactIgnored:(AIListObject *)inContact
759 if (self.account.accountManagesGroupChatIgnore) {
760 return [self.account contact:(AIListContact *)inContact isIgnoredInChat:self];
762 return [ignoredListContacts containsObject:inContact];
766 #pragma mark Comparison
767 - (BOOL)isEqual:(id)inChat
769 return (inChat == self);
772 #pragma mark Debugging
773 - (NSString *)description
775 return [NSString stringWithFormat:@"%@:%@",
777 (uniqueChatID ? uniqueChatID : @"<new>")];
780 #pragma mark Group Chats
782 @synthesize isGroupChat, showJoinLeave, hideUserIconAndStatus, topic, topicSetter;
785 * @brief Does this chat support topics?
787 - (BOOL)supportsTopic
789 return account.groupChatsSupportTopic;
793 * @brief Update the topic.
795 - (void)updateTopic:(NSString *)inTopic withSource:(AIListContact *)contact
798 topic = [inTopic retain];
800 self.topicSetter = contact;
802 // Apply the new topic to the message view
803 AIContentTopic *contentTopic = [AIContentTopic topicInChat:self
807 message:[NSAttributedString stringWithString:topic ?: @""]];
809 // The content controller has huge problems with blank messages being let through.
811 contentTopic.message = CONTENT_TOPIC_MESSAGE_ACTUALLY_EMPTY;
812 contentTopic.actuallyBlank = YES;
815 [adium.contentController receiveContentObject:contentTopic];
819 * @brief Set the chat's topic, telling the account to update it.
821 - (void)setTopic:(NSString *)inTopic
823 if (self.supportsTopic) {
824 // We mess with the topic, replacing nbsp with spaces; make sure we're not setting an identical one other than this.
825 NSString *tempTopic = [topic stringByReplacingOccurrencesOfString:@"\u00A0" withString:@" "];
826 if ([tempTopic isEqualToString:inTopic]) {
827 AILogWithSignature(@"Not setting topic for %@, already the same.", self);
829 AILogWithSignature(@"Setting %@ topic to: %@", self, topic);
830 [account setTopic:inTopic forChat:self];
833 AILogWithSignature(@"Attempt to set %@ topic when account doesn't support it.");
837 #pragma mark Custom emoticons
839 - (void)addCustomEmoticon:(AIEmoticon *)inEmoticon
841 if (!customEmoticons) customEmoticons = [[NSMutableSet alloc] init];
842 [customEmoticons addObject:inEmoticon];
845 @synthesize customEmoticons;
850 * @brief Inform the chat that an error occurred
852 * @param type An NSNumber containing an AIChatErrorType
854 - (void)receivedError:(NSNumber *)type
857 [self setValue:type forProperty:KEY_CHAT_ERROR notify:NotifyNow];
859 //No need to continue to store the NSNumber
860 [self setValue:nil forProperty:KEY_CHAT_ERROR notify:NotifyNever];
863 #pragma mark Room commands
864 - (NSMenu *)actionMenu
866 return [self.account actionMenuForChat:self];
868 - (void)setActionMenu:(NSMenu *)inMenu {};
870 #pragma mark Applescript
872 - (NSScriptObjectSpecifier *)objectSpecifier
874 //the chat may not be in a window! Just reference it from the application...
876 NSScriptClassDescription *containerClassDesc = (NSScriptClassDescription *)[NSScriptClassDescription classDescriptionForClass:[NSApp class]];
877 return [[[NSUniqueIDSpecifier allocWithZone:[self zone]]
878 initWithContainerClassDescription:containerClassDesc
879 containerSpecifier:nil key:@"chats" uniqueID:[self uniqueChatID]] autorelease];
882 - (unsigned int)index
884 //what we're going to do is find this tab in the tab view's hierarchy, so as to get its index
885 AIMessageWindowController *windowController = self.chatContainer.windowController;
887 NSArray *chats = [windowController containedChats];
888 for (unsigned int i=0;i<[chats count];i++) {
889 if ([chats objectAtIndex:i] == self)
890 return i+1; //one based
892 NSAssert(NO, @"This chat is weird.");
895 /*- (void)setIndex:(unsigned int)index
897 AIMessageWindowController *windowController = self.chatContainer.windowController;
898 NSArray *chats = [windowController containedChats];
899 NSAssert (index-1 < [chats count], @"Don't let index be bigger than the count!");
900 NSLog(@"Trying to move %@ in %@ to %u",messageTab,window,index-1);
901 [windowController moveTabViewItem:messageTab toIndex:index-1]; //This is bad bad bad. Why?
905 - (NSString *)scriptingName
907 NSString *aName = self.name;
909 aName = self.listObject.UID;
913 - (id <AIChatContainer>)chatContainer
915 return [self valueForProperty:@"MessageTabViewItem"];
918 - (id)handleCloseScriptCommand:(NSCloseCommand *)closeCommand
920 [adium.interfaceController closeChat:self];
924 - (void)setUniqueChatID:(NSString *)str
926 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
929 - (AIAccount *)scriptingAccount
934 - (void)setScriptingAccount:(AIAccount *)a
936 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
937 [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't set the account of a chat."];
940 - (NSString *)content
942 /*AITranscriptLogEnumerator *e = [[[AITranscriptLogReader alloc] initWithChat:self] autorelease];
944 NSMutableString *result = [[[NSMutableString alloc] init] autorelease];
945 while ((m = [e nextObject])) {
946 [result appendFormat:@"%@\n",[m messageString]];
949 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
950 [[NSScriptCommand currentCommand] setScriptErrorString:@"Still unsupported."];
955 * @brief Applescript command to send a message in this chat
957 - (id)sendScriptCommand:(NSScriptCommand *)command {
958 NSDictionary *evaluatedArguments = [command evaluatedArguments];
959 NSString *message = [evaluatedArguments objectForKey:@"message"];
960 NSURL *fileURL = [evaluatedArguments objectForKey:@"withFile"];
962 //Send any message we were told to send
963 if (message && [message length]) {
964 //Take the string and turn it into an attributed string (in case we were passed HTML)
965 NSAttributedString *attributedMessage = [AIHTMLDecoder decodeHTML:message];
966 AIContentMessage *messageContent;
967 messageContent = [AIContentMessage messageInChat:self
968 withSource:self.account
969 destination:self.listObject
971 message:attributedMessage
974 [adium.contentController sendContentObject:messageContent];
977 //Send any file we were told to send to every participating list object (anyone remember the AOL mass mailing zareW scene?)
978 if (fileURL && fileURL.path.length) {
980 for (AIListContact *listContact in self) {
981 AIListContact *targetFileTransferContact;
983 //Make sure we know where we are sending the file by finding the best contact for
984 //sending CONTENT_FILE_TRANSFER_TYPE.
985 if ((targetFileTransferContact = [adium.contactController preferredContactForContentType:CONTENT_FILE_TRANSFER_TYPE
986 forListContact:listContact])) {
987 [adium.fileTransferController sendFile:[fileURL path]
988 toListContact:targetFileTransferContact];
990 AILogWithSignature(@"No contact available to receive files to %@", listContact);
999 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
1001 return [self.containedObjects countByEnumeratingWithState:state objects:stackbuf count:len];
1004 - (BOOL) canContainObject:(id)obj
1006 return [obj isKindOfClass:[AIListContact class]];