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.
19 #import "AIContentController.h"
21 #import "AdiumTyping.h"
22 #import "AdiumFormatting.h"
23 #import "AdiumMessageEvents.h"
24 #import "AdiumContentFiltering.h"
26 #import <Adium/AIAccountControllerProtocol.h>
27 #import <Adium/AIChatControllerProtocol.h>
28 #import <Adium/AIContactControllerProtocol.h>
29 #import <Adium/AIInterfaceControllerProtocol.h>
30 #import <Adium/AIPreferenceControllerProtocol.h>
31 #import <Adium/AIContactAlertsControllerProtocol.h>
32 #import <Adium/AIFileTransferControllerProtocol.h>
33 #import <Adium/AIAccount.h>
34 #import <Adium/AIChat.h>
35 #import <Adium/AIContentMessage.h>
36 #import <Adium/AIContentObject.h>
37 #import <Adium/AIContentEvent.h>
38 #import <Adium/AIHTMLDecoder.h>
39 #import <Adium/AIListContact.h>
40 #import <Adium/AIListGroup.h>
41 #import <Adium/AIListObject.h>
42 #import <Adium/AIMetaContact.h>
43 #import <Adium/ESFileTransfer.h>
44 #import <Adium/AITextAttachmentExtension.h>
45 #import <AIUtilities/AIArrayAdditions.h>
46 #import <AIUtilities/AIAttributedStringAdditions.h>
47 #import <AIUtilities/AIColorAdditions.h>
48 #import <AIUtilities/AIDictionaryAdditions.h>
49 #import <AIUtilities/AIFontAdditions.h>
50 #import <AIUtilities/AIMenuAdditions.h>
51 #import <AIUtilities/AIStringAdditions.h>
52 #import <AIUtilities/AITextAttachmentAdditions.h>
53 #import <AIUtilities/AITextAttributes.h>
54 #import <AIUtilities/AIImageAdditions.h>
56 @interface AIContentController (PRIVATE)
57 - (void)finishReceiveContentObject:(AIContentObject *)inObject;
58 - (void)finishSendContentObject:(AIContentObject *)inObject;
59 - (void)finishDisplayContentObject:(AIContentObject *)inObject;
61 - (BOOL)processAndSendContentObject:(AIContentObject *)inContentObject;
65 * @class AIContentController
66 * @brief Controller to manage incoming and outgoing content and chats.
68 * This controller handles default formatting and text entry filters, which can respond as text is entered in a message
69 * window. It the center for content filtering, including registering/unregistering of content filters.
70 * It handles sending and receiving of content objects. It manages chat observers, which are objects notified as
71 * status objects are set and removed on AIChat objects. It manages chats themselves, tracking open ones, closing
72 * them when needed, etc. Finally, it provides Events related to sending and receiving content, such as Message Received.
74 @implementation AIContentController
77 * @brief Initialize the controller
81 if ((self = [super init])) {
82 adiumTyping = [[AdiumTyping alloc] init];
83 adiumFormatting = [[AdiumFormatting alloc] init];
84 adiumContentFiltering = [[AdiumContentFiltering alloc] init];
85 adiumMessageEvents = [[AdiumMessageEvents alloc] init];
87 objectsBeingReceived = [[NSMutableSet alloc] init];
93 - (void)controllerDidLoad
95 [adiumFormatting controllerDidLoad];
96 [adiumMessageEvents controllerDidLoad];
100 * @brief Close the controller
102 - (void)controllerWillClose
112 [objectsBeingReceived release]; objectsBeingReceived = nil;
113 [adiumTyping release]; adiumTyping = nil;
114 [adiumFormatting release]; adiumFormatting = nil;
115 [adiumContentFiltering release]; adiumContentFiltering = nil;
116 [adiumEncryptor release];
122 * @brief Set the encryptor
124 * NB: We must _always_ have an encryptor.
126 - (void)setEncryptor:(id<AdiumMessageEncryptor>)inEncryptor
128 NSParameterAssert([inEncryptor conformsToProtocol:@protocol(AdiumMessageEncryptor)]);
130 [adiumEncryptor release];
131 adiumEncryptor = [inEncryptor retain];
137 * @brief User is currently changing the content in a chat
139 * This should be called by a text entry control like an NSTextView.
141 * @param chat The chat
142 * @param hasEnteredText YES if there are one or more characters typed into the text entry area
144 - (void)userIsTypingContentForChat:(AIChat *)chat hasEnteredText:(BOOL)hasEnteredText {
145 [adiumTyping userIsTypingContentForChat:chat hasEnteredText:hasEnteredText];
148 #pragma mark Formatting
149 - (NSDictionary *)defaultFormattingAttributes {
150 return [adiumFormatting defaultFormattingAttributes];
153 #pragma mark Content Filtering
154 - (void)registerContentFilter:(id <AIContentFilter>)inFilter
155 ofType:(AIFilterType)type
156 direction:(AIFilterDirection)direction {
157 [adiumContentFiltering registerContentFilter:inFilter ofType:type direction:direction];
159 - (void)registerDelayedContentFilter:(id <AIDelayedContentFilter>)inFilter
160 ofType:(AIFilterType)type
161 direction:(AIFilterDirection)direction {
162 [adiumContentFiltering registerDelayedContentFilter:inFilter ofType:type direction:direction];
164 - (void)unregisterContentFilter:(id <AIContentFilter>)inFilter {
165 [adiumContentFiltering unregisterContentFilter:inFilter];
167 - (void)registerFilterStringWhichRequiresPolling:(NSString *)inPollString {
168 [adiumContentFiltering registerFilterStringWhichRequiresPolling:inPollString];
170 - (BOOL)shouldPollToUpdateString:(NSString *)inString {
171 return [adiumContentFiltering shouldPollToUpdateString:inString];
173 - (NSAttributedString *)filterAttributedString:(NSAttributedString *)attributedString
174 usingFilterType:(AIFilterType)type
175 direction:(AIFilterDirection)direction
176 context:(id)context {
177 return [adiumContentFiltering filterAttributedString:attributedString
182 - (void)filterAttributedString:(NSAttributedString *)attributedString
183 usingFilterType:(AIFilterType)type
184 direction:(AIFilterDirection)direction
185 filterContext:(id)filterContext
186 notifyingTarget:(id)target
187 selector:(SEL)selector
188 context:(id)context {
189 [adiumContentFiltering filterAttributedString:attributedString
192 filterContext:filterContext
193 notifyingTarget:target
197 - (void)delayedFilterDidFinish:(NSAttributedString *)attributedString uniqueID:(unsigned long long)uniqueID
199 [adiumContentFiltering delayedFilterDidFinish:attributedString
203 //Messaging ------------------------------------------------------------------------------------------------------------
204 #pragma mark Messaging
205 //Receiving step 1: Add an incoming content object - entry point
206 - (void)receiveContentObject:(AIContentObject *)inObject
209 AIChat *chat = [inObject chat];
211 //Only proceed if the contact is not ignored
212 if (![chat isListContactIgnored:[inObject source]]) {
213 //Notify: Will Receive Content
214 if ([inObject trackContent]) {
215 [[adium notificationCenter] postNotificationName:Content_WillReceiveContent
217 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:inObject,@"Object",nil]];
220 //Run the object through our incoming content filters
221 if ([inObject filterContent]) {
222 //Track that we are in the process of receiving this object
223 [objectsBeingReceived addObject:inObject];
225 [self filterAttributedString:[inObject message]
226 usingFilterType:AIFilterContent
227 direction:AIFilterIncoming
228 filterContext:inObject
230 selector:@selector(didFilterAttributedString:receivingContext:)
234 [self finishReceiveContentObject:inObject];
240 //Receiving step 2: filtering callback
241 - (void)didFilterAttributedString:(NSAttributedString *)filteredMessage receivingContext:(AIContentObject *)inObject
243 [inObject setMessage:filteredMessage];
245 [self finishReceiveContentObject:inObject];
248 //Receiving step 3: Display the content
249 - (void)finishReceiveContentObject:(AIContentObject *)inObject
251 //Display the content
252 [self displayContentObject:inObject];
255 //Sending step 1: Entry point for any method in Adium which sends content
257 * @brief Send a content object
259 * Sending step 1: Public method to send a content object.
261 * This method checks to be sure that messages are sent by accounts in the order they are sent by the user;
262 * this can only be problematic when a delayedFilter is involved, leading to the user sending more messages before
263 * the first finished sending.
265 - (BOOL)sendContentObject:(AIContentObject *)inObject
267 //Only proceed if the chat allows it; if it doesn't, it will handle calling this method again when it is ready
268 if ([[inObject chat] willBeginSendingContentObject:inObject]) {
270 //Run the object through our outgoing content filters
271 if ([inObject filterContent]) {
272 //Track that we are in the process of send this object
273 [objectsBeingReceived addObject:inObject];
275 [self filterAttributedString:[inObject message]
276 usingFilterType:AIFilterContent
277 direction:AIFilterOutgoing
278 filterContext:inObject
280 selector:@selector(didFilterAttributedString:contentSendingContext:)
284 [self finishSendContentObject:inObject];
292 //Sending step 2: Sending filter callback
293 -(void)didFilterAttributedString:(NSAttributedString *)filteredString contentSendingContext:(AIContentObject *)inObject
295 [inObject setMessage:filteredString];
297 //Special outgoing content filter for AIM away message bouncing. Used to filter %n,%t,...
298 if ([inObject isKindOfClass:[AIContentMessage class]] && [(AIContentMessage *)inObject isAutoreply]) {
299 [self filterAttributedString:[inObject message]
300 usingFilterType:AIFilterAutoReplyContent
301 direction:AIFilterOutgoing
302 filterContext:inObject
304 selector:@selector(didFilterAttributedString:autoreplySendingContext:)
307 [self finishSendContentObject:inObject];
311 //Sending step 3, applicable only when sending an autreply: Filter callback
312 -(void)didFilterAttributedString:(NSAttributedString *)filteredString autoreplySendingContext:(AIContentObject *)inObject
314 [inObject setMessage:filteredString];
316 [self finishSendContentObject:inObject];
319 //Sending step 4: Post notifications and ask the account to actually send the content.
320 - (void)finishSendContentObject:(AIContentObject *)inObject
322 AIChat *chat = [inObject chat];
324 //Notify: Will Send Content
325 if ([inObject trackContent]) {
326 [[adium notificationCenter] postNotificationName:Content_WillSendContent
328 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:inObject,@"Object",nil]];
332 if ([inObject sendContent]) {
333 if ([self processAndSendContentObject:inObject]) {
334 if ([inObject displayContent]) {
336 [self displayContentObject:inObject];
339 //We are no longer in the process of receiving this object
340 [objectsBeingReceived removeObject:inObject];
343 if ([inObject trackContent]) {
345 [[adium contactAlertsController] generateEvent:CONTENT_MESSAGE_SENT
346 forListObject:[chat listObject]
347 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:chat,@"AIChat",inObject,@"AIContentObject",nil]
348 previouslyPerformedActionIDs:nil];
350 [chat setHasSentOrReceivedContent:YES];
354 //We are no longer in the process of receiving this object
355 [objectsBeingReceived removeObject:inObject];
357 NSString *message = [NSString stringWithFormat:AILocalizedString(@"Could not send from %@ to %@",nil),
358 [[inObject source] formattedUID],[[inObject destination] formattedUID]];
360 [self displayEvent:message
366 //Let the chat know we finished sending
367 [chat finishedSendingContentObject:inObject];
370 //Display a content object
371 //Add content to the message view. Doesn't do any sending or receiving, just adds the content.
372 - (void)displayContentObject:(AIContentObject *)inObject usingContentFilters:(BOOL)useContentFilters
374 [self displayContentObject:inObject usingContentFilters:useContentFilters immediately:NO];
377 //Immediately YES means the main thread will halt until the content object is displayed;
378 //Immediately NO shuffles it off into the filtering thread, which will handle content sequentially but allows the main
379 //thread to continue operation.
380 //This facility primarily exists for message history, which needs to put its display in before the first message;
381 //without this, the use of threaded filtering means that message history shows up after the first message.
382 - (void)displayContentObject:(AIContentObject *)inObject usingContentFilters:(BOOL)useContentFilters immediately:(BOOL)immediately
384 if (useContentFilters) {
387 //Filter in the main thread, set the message, and continue
388 [inObject setMessage:[self filterAttributedString:[inObject message]
389 usingFilterType:AIFilterContent
390 direction:([inObject isOutgoing] ? AIFilterOutgoing : AIFilterIncoming)
392 [self displayContentObject:inObject immediately:YES];
396 //Filter in the filter thread
397 [self filterAttributedString:[inObject message]
398 usingFilterType:AIFilterContent
399 direction:([inObject isOutgoing] ? AIFilterOutgoing : AIFilterIncoming)
400 filterContext:inObject
402 selector:@selector(didFilterAttributedString:contentFilterDisplayContext:)
407 [self displayContentObject:inObject immediately:immediately];
411 - (void)didFilterAttributedString:(NSAttributedString *)filteredString contentFilterDisplayContext:(AIContentObject *)inObject
413 [inObject setMessage:filteredString];
416 [self displayContentObject:inObject immediately:NO];
419 //Display a content object
420 //Add content to the message view. Doesn't do any sending or receiving, just adds the content.
421 - (void)displayContentObject:(AIContentObject *)inObject
423 [self displayContentObject:inObject immediately:NO];
426 - (void)displayContentObject:(AIContentObject *)inObject immediately:(BOOL)immediately
428 //Filter the content object
429 if ([inObject filterContent]) {
430 BOOL message = ([inObject isKindOfClass:[AIContentMessage class]] && ![(AIContentMessage *)inObject isAutoreply]);
431 AIFilterType filterType = (message ? AIFilterMessageDisplay : AIFilterDisplay);
432 AIFilterDirection direction = ([inObject isOutgoing] ? AIFilterOutgoing : AIFilterIncoming);
436 //Set it after filtering in the main thread, then display it
437 [inObject setMessage:[self filterAttributedString:[inObject message]
438 usingFilterType:filterType
441 [self finishDisplayContentObject:inObject];
444 //Filter in the filtering thread
445 [self filterAttributedString:[inObject message]
446 usingFilterType:filterType
448 filterContext:inObject
450 selector:@selector(didFilterAttributedString:displayContext:)
455 [self finishDisplayContentObject:inObject];
460 - (void)didFilterAttributedString:(NSAttributedString *)filteredString displayContext:(AIContentObject *)inObject
462 [inObject setMessage:filteredString];
464 [self finishDisplayContentObject:inObject];
467 - (void)finishDisplayContentObject:(AIContentObject *)inObject
469 //Check if the object should display
470 if ([inObject displayContent] && ([[inObject message] length] > 0)) {
471 AIChat *chat = [inObject chat];
472 NSDictionary *userInfo;
473 BOOL contentReceived, shouldPostContentReceivedEvents;
475 //If the chat of the content object has been cleared, we can't do anything with it, so simply return
478 contentReceived = (([inObject isMemberOfClass:[AIContentMessage class]]) &&
479 (![inObject isOutgoing]));
480 shouldPostContentReceivedEvents = contentReceived && [inObject trackContent];
482 if (![chat isOpen]) {
483 /* Tell the interface to open the chat
484 * For incoming messages, we don't open the chat until we're sure that new content is being received.
486 [[adium interfaceController] openChat:chat];
489 userInfo = [NSDictionary dictionaryWithObjectsAndKeys:chat, @"AIChat", inObject, @"AIContentObject", nil];
491 //Notify: Content Object Added
492 [[adium notificationCenter] postNotificationName:Content_ContentObjectAdded
496 if (shouldPostContentReceivedEvents) {
497 NSSet *previouslyPerformedActionIDs = nil;
498 AIListObject *listObject = [chat listObject];
500 if (![chat hasSentOrReceivedContent]) {
501 //If the chat wasn't open before, generate CONTENT_MESSAGE_RECEIVED_FIRST
502 previouslyPerformedActionIDs = [[adium contactAlertsController] generateEvent:CONTENT_MESSAGE_RECEIVED_FIRST
503 forListObject:listObject
505 previouslyPerformedActionIDs:nil];
506 [chat setHasSentOrReceivedContent:YES];
509 if (chat != [[adium interfaceController] activeChat]) {
510 //If the chat is not currently active, generate CONTENT_MESSAGE_RECEIVED_BACKGROUND
511 previouslyPerformedActionIDs = [[adium contactAlertsController] generateEvent:CONTENT_MESSAGE_RECEIVED_BACKGROUND
512 forListObject:listObject
514 previouslyPerformedActionIDs:previouslyPerformedActionIDs];
517 [[adium contactAlertsController] generateEvent:CONTENT_MESSAGE_RECEIVED
518 forListObject:listObject
520 previouslyPerformedActionIDs:previouslyPerformedActionIDs];
524 //We are no longer in the process of receiving this object
525 [objectsBeingReceived removeObject:inObject];
531 * @brief Send any NSTextAttachments embedded in inContentMessage's message
533 * This method will remove such attachments after requesting their files being sent.
535 * If the account supports sending images on this message's chat and a file is an image it will be left in the
536 * attributed string for processing later by AIHTMLDecoder.
538 - (void)handleFileSendsForContentMessage:(AIContentMessage *)inContentMessage
540 if (![inContentMessage destination] ||
541 ![[inContentMessage destination] isKindOfClass:[AIListContact class]] ||
542 ![[[inContentMessage chat] account] availableForSendingContentType:CONTENT_FILE_TRANSFER_TYPE
543 toContact:(AIListContact *)[inContentMessage destination]]) {
544 //Simply return if we can't do anything about file sends for this message.
548 NSMutableAttributedString *newAttributedString = nil;
549 NSAttributedString *attributedMessage = [inContentMessage message];
550 unsigned length = [attributedMessage length];
553 NSRange searchRange = NSMakeRange(0,0);
554 NSAttributedString *currentAttributedString = attributedMessage;
556 while (searchRange.location < length) {
557 NSTextAttachment *textAttachment = [currentAttributedString attribute:NSAttachmentAttributeName
558 atIndex:searchRange.location
559 effectiveRange:&searchRange];
560 if (textAttachment) {
561 BOOL shouldSendAttachmentAsFile;
562 //Invariant within the loop, but most calls to handleFileSendsForContentMessage: don't get here at all
563 BOOL canSendImages = [(AIAccount *)[inContentMessage source] canSendImagesForChat:[inContentMessage chat]];
565 if ([textAttachment isKindOfClass:[AITextAttachmentExtension class]]) {
566 AITextAttachmentExtension *textAttachmentExtension = (AITextAttachmentExtension *)textAttachment;
569 * This attachment isn't just for display (i.e. isn't an emoticon) AND
570 * This chat can't send images, or it can but this attachment isn't an image
572 shouldSendAttachmentAsFile = (![textAttachmentExtension shouldAlwaysSendAsText] &&
573 (!canSendImages || ![textAttachmentExtension attachesAnImage]));
576 shouldSendAttachmentAsFile = (!canSendImages || ![textAttachment wrapsImage]);
579 if (shouldSendAttachmentAsFile) {
580 if (!newAttributedString) {
581 newAttributedString = [[attributedMessage mutableCopy] autorelease];
582 currentAttributedString = newAttributedString;
586 if ([textAttachment isKindOfClass:[AITextAttachmentExtension class]]) {
587 path = [(AITextAttachmentExtension *)textAttachment path];
588 AILog(@"Sending text attachment %@ which has path %@",textAttachment,path);
590 //Write out the file so we can send it if we have a standard NSTextAttachment to send
591 NSFileWrapper *fileWrapper = [textAttachment fileWrapper];
593 //Desired folder: /private/tmp/$UID/`uuidgen`
594 NSString *tmpDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
595 NSString *filename = [fileWrapper preferredFilename];
596 if (!filename) filename = [NSString randomStringOfLength:5];
598 path = [tmpDir stringByAppendingPathComponent:filename];
600 if ([fileWrapper writeToFile:tmpDir atomically:YES updateFilenames:YES]) {
601 AILog(@"Wrote out the file to %@ for sending",path);
603 NSLog(@"Failed to write out the file to %@ for sending", path);
604 AILog(@"Failed to write out the file to %@ for sending", path);
606 //The transfer is not going to happen so clear path
611 [[adium fileTransferController] sendFile:path
612 toListContact:(AIListContact *)[inContentMessage destination]];
614 NSLog(@"-[AIContentController handleFileSendsForContentMessage:]: Warning: Failed to have a path for sending an inline file!");
615 AILog(@"-[AIContentController handleFileSendsForContentMessage:]: Warning: Failed to have a path for sending an inline file for content message %@!",
619 //Now remove the attachment
620 [newAttributedString removeAttribute:NSAttachmentAttributeName range:NSMakeRange(searchRange.location,
621 searchRange.length)];
622 [newAttributedString replaceCharactersInRange:searchRange withString:@""];
623 //Decrease length by the number of characters we replaced
624 length -= searchRange.length;
626 //And don't increase our location in the searchRange.location += searchRange.length below
627 searchRange.length = 0;
632 searchRange.location += searchRange.length;
636 //If any changes were made, update the AIContentMessage
637 if (newAttributedString) {
638 [inContentMessage setMessage:newAttributedString];
643 * @brief Handle sending a content object
645 * This method must return YES for the content to be displayed
647 * For a typing content object, the account is informed.
648 * For a message content object, the account is told to send the message; any imbedded file transfers will also be sent.
649 * For a file transfer content object, YES is always returned, as this is actually just for display purposes.
651 - (BOOL)processAndSendContentObject:(AIContentObject *)inContentObject
653 AIAccount *sendingAccount = (AIAccount *)[inContentObject source];
656 if ([inContentObject isKindOfClass:[AIContentTyping class]]) {
658 [sendingAccount sendTypingObject:(AIContentTyping *)inContentObject];
660 } else if ([inContentObject isKindOfClass:[AIContentMessage class]]) {
661 /* Sending a message */
662 AIContentMessage *contentMessage = (AIContentMessage *)inContentObject;
663 NSString *encodedOutgoingMessage;
665 //Before we send the message on to the account, we need to look for embedded files which should be sent as file transfers
666 [self handleFileSendsForContentMessage:contentMessage];
668 /* Let the account encode it as appropriate for sending. Note that we succeeded in sending if we have no length
669 * as that means that somewhere we meant to stop the send -- a file send, an encryption message, etc.
671 if ([[contentMessage message] length]) {
672 encodedOutgoingMessage = [sendingAccount encodedAttributedStringForSendingContentMessage:contentMessage];
674 if (encodedOutgoingMessage && [encodedOutgoingMessage length]) {
675 [contentMessage setEncodedMessage:encodedOutgoingMessage];
676 [adiumEncryptor willSendContentMessage:contentMessage];
678 if ([contentMessage encodedMessage]) {
679 success = [sendingAccount sendMessageObject:contentMessage];
682 //If the account returns nil when encoding the attributed string, we shouldn't display it on-screen.
683 [contentMessage setDisplayContent:NO];
687 } else if ([inContentObject isKindOfClass:[ESFileTransfer class]]) {
691 /* Eating a tasty sandwich */
695 if (!success) AILog(@"Failed to send %@ (sendingAccount %@)",inContentObject,sendingAccount);
701 * @brief Send a message as-specified without going through any filters or notifications
703 - (void)sendRawMessage:(NSString *)inString toContact:(AIListContact *)inContact
705 AIAccount *account = [inContact account];
707 AIContentMessage *contentMessage;
709 if (!(chat = [[adium chatController] existingChatWithContact:inContact])) {
710 chat = [[adium chatController] chatWithContact:inContact];
713 contentMessage = [AIContentMessage messageInChat:chat
715 destination:inContact
719 [contentMessage setEncodedMessage:inString];
721 [account sendMessageObject:contentMessage];
725 * @brief Given an incoming message, decrypt it. It is likely not yet ready for display when returned, as it may still include HTML.
727 - (NSString *)decryptedIncomingMessage:(NSString *)inString fromContact:(AIListContact *)inListContact onAccount:(AIAccount *)inAccount
729 return [adiumEncryptor decryptIncomingMessage:inString fromContact:inListContact onAccount:inAccount];
733 * @brief Given an incoming message, decrypt it if necessary then convert it to an NSAttributedString, processing HTML if possible
735 - (NSAttributedString *)decodedIncomingMessage:(NSString *)inString fromContact:(AIListContact *)inListContact onAccount:(AIAccount *)inAccount
737 return [AIHTMLDecoder decodeHTML:[self decryptedIncomingMessage:inString
738 fromContact:inListContact
739 onAccount:inAccount]];
743 - (void)requestSecureOTRMessaging:(BOOL)inSecureMessaging inChat:(AIChat *)inChat
745 [adiumEncryptor requestSecureOTRMessaging:inSecureMessaging inChat:inChat];
748 - (void)promptToVerifyEncryptionIdentityInChat:(AIChat *)inChat
750 [adiumEncryptor promptToVerifyEncryptionIdentityInChat:inChat];
755 * @brief Is the passed chat currently receiving content?
757 * Note: This may be irrelevent if threaded filtering is removed.
759 - (BOOL)chatIsReceivingContent:(AIChat *)inChat
761 BOOL isReceivingContent = NO;
763 NSEnumerator *objectsBeingReceivedEnumerator = [objectsBeingReceived objectEnumerator];
764 AIContentObject *contentObject;
765 while ((contentObject = [objectsBeingReceivedEnumerator nextObject])) {
766 if ([contentObject chat] == inChat) {
767 isReceivingContent = YES;
772 return isReceivingContent;
775 - (void)displayEvent:(NSString *)message ofType:(NSString *)type inChat:(AIChat *)inChat
777 AIContentStatus *content;
778 NSAttributedString *attributedMessage;
780 //Create our content object
781 attributedMessage = [[AIHTMLDecoder decoder] decodeHTML:message withDefaultAttributes:[self defaultFormattingAttributes]];
783 content = [AIContentEvent statusInChat:inChat
784 withSource:[inChat listObject]
785 destination:[inChat account]
787 message:attributedMessage
791 [self receiveContentObject:content];
794 //Returns YES if the account/chat is available for sending content
795 - (BOOL)availableForSendingContentType:(NSString *)inType toContact:(AIListContact *)inContact onAccount:(AIAccount *)inAccount
797 return [inAccount availableForSendingContentType:inType toContact:inContact];
801 * @brief Generate a menu of encryption preference choices
803 - (NSMenu *)encryptionMenuNotifyingTarget:(id)target withDefault:(BOOL)withDefault
805 NSMenu *encryptionMenu = [[NSMenu allocWithZone:[NSMenu zone]] init];
806 NSMenuItem *menuItem;
808 [encryptionMenu setTitle:ENCRYPTION_MENU_TITLE];
810 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Disable chat encryption",nil)
812 action:@selector(selectedEncryptionPreference:)
815 [menuItem setTag:EncryptedChat_Never];
816 [encryptionMenu addItem:menuItem];
819 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Encrypt chats as requested",nil)
821 action:@selector(selectedEncryptionPreference:)
824 [menuItem setTag:EncryptedChat_Manually];
825 [encryptionMenu addItem:menuItem];
828 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Encrypt chats automatically",nil)
830 action:@selector(selectedEncryptionPreference:)
833 [menuItem setTag:EncryptedChat_Automatically];
834 [encryptionMenu addItem:menuItem];
837 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Force encryption and refuse plaintext",nil)
839 action:@selector(selectedEncryptionPreference:)
842 [menuItem setTag:EncryptedChat_RejectUnencryptedMessages];
843 [encryptionMenu addItem:menuItem];
847 [encryptionMenu addItem:[NSMenuItem separatorItem]];
849 NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Default",nil)
851 action:@selector(selectedEncryptionPreference:)
854 [menuItem setTag:EncryptedChat_Default];
855 [encryptionMenu addItem:menuItem];
859 return [encryptionMenu autorelease];