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 if (![chat isGroupChat]) {
503 previouslyPerformedActionIDs = [[adium contactAlertsController] generateEvent:CONTENT_MESSAGE_RECEIVED_FIRST
504 forListObject:listObject
506 previouslyPerformedActionIDs:nil];
508 [chat setHasSentOrReceivedContent:YES];
511 if (chat != [[adium interfaceController] activeChat]) {
512 //If the chat is not currently active, generate CONTENT_MESSAGE_RECEIVED_BACKGROUND
513 previouslyPerformedActionIDs = [[adium contactAlertsController] generateEvent:([chat isGroupChat] ? CONTENT_MESSAGE_RECEIVED_BACKGROUND_GROUP : CONTENT_MESSAGE_RECEIVED_BACKGROUND)
514 forListObject:listObject
516 previouslyPerformedActionIDs:previouslyPerformedActionIDs];
519 [[adium contactAlertsController] generateEvent:([chat isGroupChat] ? CONTENT_MESSAGE_RECEIVED_GROUP : CONTENT_MESSAGE_RECEIVED)
520 forListObject:listObject
522 previouslyPerformedActionIDs:previouslyPerformedActionIDs];
526 //We are no longer in the process of receiving this object
527 [objectsBeingReceived removeObject:inObject];
533 * @brief Send any NSTextAttachments embedded in inContentMessage's message
535 * This method will remove such attachments after requesting their files being sent.
537 * If the account supports sending images on this message's chat and a file is an image it will be left in the
538 * attributed string for processing later by AIHTMLDecoder.
540 - (void)handleFileSendsForContentMessage:(AIContentMessage *)inContentMessage
542 if (![inContentMessage destination] ||
543 ![[inContentMessage destination] isKindOfClass:[AIListContact class]] ||
544 ![[[inContentMessage chat] account] availableForSendingContentType:CONTENT_FILE_TRANSFER_TYPE
545 toContact:(AIListContact *)[inContentMessage destination]]) {
546 //Simply return if we can't do anything about file sends for this message.
550 NSMutableAttributedString *newAttributedString = nil;
551 NSAttributedString *attributedMessage = [inContentMessage message];
552 unsigned length = [attributedMessage length];
555 NSRange searchRange = NSMakeRange(0,0);
556 NSAttributedString *currentAttributedString = attributedMessage;
558 while (searchRange.location < length) {
559 NSTextAttachment *textAttachment = [currentAttributedString attribute:NSAttachmentAttributeName
560 atIndex:searchRange.location
561 effectiveRange:&searchRange];
562 if (textAttachment) {
563 BOOL shouldSendAttachmentAsFile;
564 //Invariant within the loop, but most calls to handleFileSendsForContentMessage: don't get here at all
565 BOOL canSendImages = [(AIAccount *)[inContentMessage source] canSendImagesForChat:[inContentMessage chat]];
567 if ([textAttachment isKindOfClass:[AITextAttachmentExtension class]]) {
568 AITextAttachmentExtension *textAttachmentExtension = (AITextAttachmentExtension *)textAttachment;
571 * This attachment isn't just for display (i.e. isn't an emoticon) AND
572 * This chat can't send images, or it can but this attachment isn't an image
574 shouldSendAttachmentAsFile = (![textAttachmentExtension shouldAlwaysSendAsText] &&
575 (!canSendImages || ![textAttachmentExtension attachesAnImage]));
578 shouldSendAttachmentAsFile = (!canSendImages || ![textAttachment wrapsImage]);
581 if (shouldSendAttachmentAsFile) {
582 if (!newAttributedString) {
583 newAttributedString = [[attributedMessage mutableCopy] autorelease];
584 currentAttributedString = newAttributedString;
588 if ([textAttachment isKindOfClass:[AITextAttachmentExtension class]]) {
589 path = [(AITextAttachmentExtension *)textAttachment path];
590 AILog(@"Sending text attachment %@ which has path %@",textAttachment,path);
592 //Write out the file so we can send it if we have a standard NSTextAttachment to send
593 NSFileWrapper *fileWrapper = [textAttachment fileWrapper];
595 //Desired folder: /private/tmp/$UID/`uuidgen`
596 NSString *tmpDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
597 NSString *filename = [fileWrapper preferredFilename];
598 if (!filename) filename = [NSString randomStringOfLength:5];
600 path = [tmpDir stringByAppendingPathComponent:filename];
602 if ([fileWrapper writeToFile:tmpDir atomically:YES updateFilenames:YES]) {
603 AILog(@"Wrote out the file to %@ for sending",path);
605 NSLog(@"Failed to write out the file to %@ for sending", path);
606 AILog(@"Failed to write out the file to %@ for sending", path);
608 //The transfer is not going to happen so clear path
613 [[adium fileTransferController] sendFile:path
614 toListContact:(AIListContact *)[inContentMessage destination]];
616 NSLog(@"-[AIContentController handleFileSendsForContentMessage:]: Warning: Failed to have a path for sending an inline file!");
617 AILog(@"-[AIContentController handleFileSendsForContentMessage:]: Warning: Failed to have a path for sending an inline file for content message %@!",
621 //Now remove the attachment
622 [newAttributedString removeAttribute:NSAttachmentAttributeName range:NSMakeRange(searchRange.location,
623 searchRange.length)];
624 [newAttributedString replaceCharactersInRange:searchRange withString:@""];
625 //Decrease length by the number of characters we replaced
626 length -= searchRange.length;
628 //And don't increase our location in the searchRange.location += searchRange.length below
629 searchRange.length = 0;
634 searchRange.location += searchRange.length;
638 //If any changes were made, update the AIContentMessage
639 if (newAttributedString) {
640 [inContentMessage setMessage:newAttributedString];
645 * @brief Handle sending a content object
647 * This method must return YES for the content to be displayed
649 * For a typing content object, the account is informed.
650 * For a message content object, the account is told to send the message; any imbedded file transfers will also be sent.
651 * For a file transfer content object, YES is always returned, as this is actually just for display purposes.
653 - (BOOL)processAndSendContentObject:(AIContentObject *)inContentObject
655 AIAccount *sendingAccount = (AIAccount *)[inContentObject source];
658 if ([inContentObject isKindOfClass:[AIContentTyping class]]) {
660 [sendingAccount sendTypingObject:(AIContentTyping *)inContentObject];
662 } else if ([inContentObject isKindOfClass:[AIContentMessage class]]) {
663 /* Sending a message */
664 AIContentMessage *contentMessage = (AIContentMessage *)inContentObject;
665 NSString *encodedOutgoingMessage;
667 //Before we send the message on to the account, we need to look for embedded files which should be sent as file transfers
668 [self handleFileSendsForContentMessage:contentMessage];
670 /* Let the account encode it as appropriate for sending. Note that we succeeded in sending if we have no length
671 * as that means that somewhere we meant to stop the send -- a file send, an encryption message, etc.
673 if ([[contentMessage message] length]) {
674 encodedOutgoingMessage = [sendingAccount encodedAttributedStringForSendingContentMessage:contentMessage];
676 if (encodedOutgoingMessage && [encodedOutgoingMessage length]) {
677 [contentMessage setEncodedMessage:encodedOutgoingMessage];
678 [adiumEncryptor willSendContentMessage:contentMessage];
680 if ([contentMessage encodedMessage]) {
681 success = [sendingAccount sendMessageObject:contentMessage];
684 //If the account returns nil when encoding the attributed string, we shouldn't display it on-screen.
685 [contentMessage setDisplayContent:NO];
689 } else if ([inContentObject isKindOfClass:[ESFileTransfer class]]) {
693 /* Eating a tasty sandwich */
697 if (!success) AILog(@"Failed to send %@ (sendingAccount %@)",inContentObject,sendingAccount);
703 * @brief Send a message as-specified without going through any filters or notifications
705 - (void)sendRawMessage:(NSString *)inString toContact:(AIListContact *)inContact
707 AIAccount *account = [inContact account];
709 AIContentMessage *contentMessage;
711 if (!(chat = [[adium chatController] existingChatWithContact:inContact])) {
712 chat = [[adium chatController] chatWithContact:inContact];
715 contentMessage = [AIContentMessage messageInChat:chat
717 destination:inContact
721 [contentMessage setEncodedMessage:inString];
723 [account sendMessageObject:contentMessage];
727 * @brief Given an incoming message, decrypt it. It is likely not yet ready for display when returned, as it may still include HTML.
729 - (NSString *)decryptedIncomingMessage:(NSString *)inString fromContact:(AIListContact *)inListContact onAccount:(AIAccount *)inAccount
731 return [adiumEncryptor decryptIncomingMessage:inString fromContact:inListContact onAccount:inAccount];
735 * @brief Given an incoming message, decrypt it if necessary then convert it to an NSAttributedString, processing HTML if possible
737 - (NSAttributedString *)decodedIncomingMessage:(NSString *)inString fromContact:(AIListContact *)inListContact onAccount:(AIAccount *)inAccount
739 return [AIHTMLDecoder decodeHTML:[self decryptedIncomingMessage:inString
740 fromContact:inListContact
741 onAccount:inAccount]];
745 - (void)requestSecureOTRMessaging:(BOOL)inSecureMessaging inChat:(AIChat *)inChat
747 [adiumEncryptor requestSecureOTRMessaging:inSecureMessaging inChat:inChat];
750 - (void)promptToVerifyEncryptionIdentityInChat:(AIChat *)inChat
752 [adiumEncryptor promptToVerifyEncryptionIdentityInChat:inChat];
757 * @brief Is the passed chat currently receiving content?
759 * Note: This may be irrelevent if threaded filtering is removed.
761 - (BOOL)chatIsReceivingContent:(AIChat *)inChat
763 BOOL isReceivingContent = NO;
765 NSEnumerator *objectsBeingReceivedEnumerator = [objectsBeingReceived objectEnumerator];
766 AIContentObject *contentObject;
767 while ((contentObject = [objectsBeingReceivedEnumerator nextObject])) {
768 if ([contentObject chat] == inChat) {
769 isReceivingContent = YES;
774 return isReceivingContent;
777 - (void)displayEvent:(NSString *)message ofType:(NSString *)type inChat:(AIChat *)inChat
779 AIContentStatus *content;
780 NSAttributedString *attributedMessage;
782 //Create our content object
783 attributedMessage = [[AIHTMLDecoder decoder] decodeHTML:message withDefaultAttributes:[self defaultFormattingAttributes]];
785 content = [AIContentEvent statusInChat:inChat
786 withSource:[inChat listObject]
787 destination:[inChat account]
789 message:attributedMessage
793 [self receiveContentObject:content];
796 //Returns YES if the account/chat is available for sending content
797 - (BOOL)availableForSendingContentType:(NSString *)inType toContact:(AIListContact *)inContact onAccount:(AIAccount *)inAccount
799 return [inAccount availableForSendingContentType:inType toContact:inContact];
803 * @brief Generate a menu of encryption preference choices
805 - (NSMenu *)encryptionMenuNotifyingTarget:(id)target withDefault:(BOOL)withDefault
807 NSMenu *encryptionMenu = [[NSMenu allocWithZone:[NSMenu zone]] init];
808 NSMenuItem *menuItem;
810 [encryptionMenu setTitle:ENCRYPTION_MENU_TITLE];
812 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Disable chat encryption",nil)
814 action:@selector(selectedEncryptionPreference:)
817 [menuItem setTag:EncryptedChat_Never];
818 [encryptionMenu addItem:menuItem];
821 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Encrypt chats as requested",nil)
823 action:@selector(selectedEncryptionPreference:)
826 [menuItem setTag:EncryptedChat_Manually];
827 [encryptionMenu addItem:menuItem];
830 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Encrypt chats automatically",nil)
832 action:@selector(selectedEncryptionPreference:)
835 [menuItem setTag:EncryptedChat_Automatically];
836 [encryptionMenu addItem:menuItem];
839 menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Force encryption and refuse plaintext",nil)
841 action:@selector(selectedEncryptionPreference:)
844 [menuItem setTag:EncryptedChat_RejectUnencryptedMessages];
845 [encryptionMenu addItem:menuItem];
849 [encryptionMenu addItem:[NSMenuItem separatorItem]];
851 NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Default",nil)
853 action:@selector(selectedEncryptionPreference:)
856 [menuItem setTag:EncryptedChat_Default];
857 [encryptionMenu addItem:menuItem];
861 return [encryptionMenu autorelease];