diff options
Diffstat (limited to 'Swift/Controllers/Chat')
19 files changed, 5716 insertions, 5186 deletions
diff --git a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h index e49e378..646612b 100644 --- a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h +++ b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h @@ -5,49 +5,50 @@ */ /* - * Copyright (c) 2014 Isode Limited. + * Copyright (c) 2014-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once -#include <Swiften/Roster/XMPPRoster.h> #include <Swiften/Elements/MUCInvitationPayload.h> -#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swiften/Roster/XMPPRoster.h> + #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> namespace Swift { - class AutoAcceptMUCInviteDecider { - public: - AutoAcceptMUCInviteDecider(const JID& domain, XMPPRoster* roster, SettingsProvider* settings) : domain_(domain), roster_(roster), settings_(settings) { - } - - bool isAutoAcceptedInvite(const JID& from, MUCInvitationPayload::ref invite) { - if (!invite->getIsImpromptu()) { - return false; /* always ask the user for normal MUC invites */ - } - - if (invite->getIsContinuation()) { - return true; - } - - std::string auto_accept_mode = settings_->getSetting(SettingConstants::INVITE_AUTO_ACCEPT_MODE); - if (auto_accept_mode == "no") { - return false; - } else if (auto_accept_mode == "presence") { - return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both; - } else if (auto_accept_mode == "domain") { - return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both || from.getDomain() == domain_; - } else { - assert(false); - return false; - } - } - - private: - JID domain_; - XMPPRoster* roster_; - SettingsProvider* settings_; - }; + class AutoAcceptMUCInviteDecider { + public: + AutoAcceptMUCInviteDecider(const JID& domain, XMPPRoster* roster, SettingsProvider* settings) : domain_(domain), roster_(roster), settings_(settings) { + } + + bool isAutoAcceptedInvite(const JID& from, MUCInvitationPayload::ref invite) { + if (!invite->getIsImpromptu()) { + return false; /* always ask the user for normal MUC invites */ + } + + if (invite->getIsContinuation()) { + return true; + } + + std::string auto_accept_mode = settings_->getSetting(SettingConstants::INVITE_AUTO_ACCEPT_MODE); + if (auto_accept_mode == "no") { + return false; + } else if (auto_accept_mode == "presence") { + return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both; + } else if (auto_accept_mode == "domain") { + return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both || from.getDomain() == domain_; + } else { + assert(false); + return false; + } + } + + private: + JID domain_; + XMPPRoster* roster_; + SettingsProvider* settings_; + }; } diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index a80eee5..cd8fa0c 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -6,14 +6,13 @@ #include <Swift/Controllers/Chat/ChatController.h> +#include <memory> + #include <boost/bind.hpp> -#include <boost/smart_ptr/make_shared.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Algorithm.h> -#include <Swiften/Base/DateTime.h> #include <Swiften/Base/Log.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> #include <Swiften/Chat/ChatStateNotifier.h> #include <Swiften/Chat/ChatStateTracker.h> @@ -33,6 +32,7 @@ #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/StatusUtil.h> +#include <Swift/Controllers/Translator.h> #include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> @@ -43,491 +43,509 @@ #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h> namespace Swift { - + /** * The controller does not gain ownership of the stanzaChannel, nor the factory. */ -ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { - isInMUC_ = isInMUC; - lastWasPresence_ = false; - chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); - chatStateTracker_ = new ChatStateTracker(); - nickResolver_ = nickResolver; - presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1)); - chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1)); - stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1)); - nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); - std::string nick = nickResolver_->jidToNick(toJID_); - chatWindow_->setName(nick); - std::string startMessage; - Presence::ref theirPresence; - if (isInMUC) { - startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString()); - theirPresence = presenceOracle->getLastPresence(contact); - } else { - startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString()); - theirPresence = contact.isBare() ? presenceOracle->getAccountPresence(contact) : presenceOracle->getLastPresence(contact); - } - Idle::ref idle; - if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { - startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); - } - startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); - if (theirPresence && !theirPresence->getStatus().empty()) { - startMessage += " (" + theirPresence->getStatus() + ")"; - } - lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None; - chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); - startMessage += "."; - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); - chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); - chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); - chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2)); - chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2)); - chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1)); - chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1)); - chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this)); - chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this)); - chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this)); - chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this)); - chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this)); - chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1)); - chatWindow_->onClosed.connect(boost::bind(&ChatController::handleWindowClosed, this)); - ChatController::handleBareJIDCapsChanged(toJID_); - - settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1)); - eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1)); +ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { + isInMUC_ = isInMUC; + lastWasPresence_ = false; + chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); + chatStateTracker_ = new ChatStateTracker(); + nickResolver_ = nickResolver; + presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1)); + chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1)); + stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1)); + nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); + std::string nick = nickResolver_->jidToNick(toJID_); + chatWindow_->setName(nick); + std::string startMessage; + Presence::ref theirPresence; + if (isInMUC) { + startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString()); + theirPresence = presenceOracle->getLastPresence(contact); + } else { + startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString()); + theirPresence = contact.isBare() ? presenceOracle->getAccountPresence(contact) : presenceOracle->getLastPresence(contact); + } + Idle::ref idle; + if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { + startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % Swift::Translator::getInstance()->ptimeToHumanReadableString(idle->getSince())); + } + startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); + if (theirPresence && !theirPresence->getStatus().empty()) { + startMessage += " (" + theirPresence->getStatus() + ")"; + } + lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None; + chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); + startMessage += "."; + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); + chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2)); + chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2)); + chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1)); + chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1)); + chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this)); + chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this)); + chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this)); + chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this)); + chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this)); + chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1)); + chatWindow_->onClosed.connect(boost::bind(&ChatController::handleWindowClosed, this)); + ChatController::handleBareJIDCapsChanged(toJID_); + + settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1)); + eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1)); } void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) { - if (jid.toBare() == toJID_.toBare()) { - chatWindow_->setName(nickResolver_->jidToNick(toJID_)); - } + if (jid.toBare() == toJID_.toBare()) { + chatWindow_->setName(nickResolver_->jidToNick(toJID_)); + } } ChatController::~ChatController() { - eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1)); - settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1)); - nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); - delete chatStateNotifier_; - delete chatStateTracker_; + eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1)); + settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1)); + nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); + delete chatStateNotifier_; + delete chatStateTracker_; } JID ChatController::getBaseJID() { - return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID(); + return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID(); } void ChatController::cancelReplaces() { - lastWasPresence_ = false; + lastWasPresence_ = false; } void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) { - FeatureOracle featureOracle(entityCapsProvider_, presenceOracle_); + FeatureOracle featureOracle(entityCapsProvider_, presenceOracle_); - chatWindow_->setCorrectionEnabled(featureOracle.isMessageCorrectionSupported(toJID_)); - chatWindow_->setFileTransferEnabled(isInMUC_ ? No : featureOracle.isFileTransferSupported(toJID_)); - contactSupportsReceipts_ = featureOracle.isMessageReceiptsSupported(toJID_); + chatWindow_->setCorrectionEnabled(featureOracle.isMessageCorrectionSupported(toJID_)); + chatWindow_->setFileTransferEnabled(isInMUC_ ? No : featureOracle.isFileTransferSupported(toJID_)); + contactSupportsReceipts_ = featureOracle.isMessageReceiptsSupported(toJID_); - checkForDisplayingDisplayReceiptsAlert(); + checkForDisplayingDisplayReceiptsAlert(); } void ChatController::setToJID(const JID& jid) { - chatStateNotifier_->setContact(jid); - ChatControllerBase::setToJID(jid); - Presence::ref presence; - if (isInMUC_) { - presence = presenceOracle_->getLastPresence(jid); - } else { - presence = jid.isBare() ? presenceOracle_->getAccountPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid); - } - chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available); - handleBareJIDCapsChanged(toJID_); -} - -void ChatController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { - ChatControllerBase::setAvailableServerFeatures(info); - if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { - boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); - - blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); - blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); - blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); - - handleBlockingStateChanged(); - } -} - -bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { - return false; -} - -void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { - if (messageEvent->isReadable()) { - chatWindow_->flash(); - lastWasPresence_ = false; - } - boost::shared_ptr<Message> message = messageEvent->getStanza(); - JID from = message->getFrom(); - if (!from.equals(toJID_, JID::WithResource)) { - if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){ - // Bind controller to a full JID if message contains body text or is a typing chat state. - ChatState::ref chatState = message->getPayload<ChatState>(); - if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) { - setToJID(from); - } - } - } - chatStateTracker_->handleMessageReceived(message); - chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>()); - - // handle XEP-0184 Message Receipts - // incomming receipts - if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) { - SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl; - if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) { - chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived); - requestedReceipts_.erase(receipt->getReceivedID()); - } - // incomming errors in response to send out receipts - } else if (message->getPayload<DeliveryReceiptRequest>() && (message->getType() == Message::Error)) { - if (requestedReceipts_.find(message->getID()) != requestedReceipts_.end()) { - chatWindow_->setMessageReceiptState(requestedReceipts_[message->getID()], ChatWindow::ReceiptFailed); - requestedReceipts_.erase(message->getID()); - } - // incoming receipt requests - } else if (message->getPayload<DeliveryReceiptRequest>()) { - if (receivingPresenceFromUs_) { - boost::shared_ptr<Message> receiptMessage = boost::make_shared<Message>(); - receiptMessage->setTo(toJID_); - receiptMessage->addPayload(boost::make_shared<DeliveryReceipt>(message->getID())); - stanzaChannel_->sendMessage(receiptMessage); - } - } -} - -void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { - eventController_->handleIncomingEvent(messageEvent); - if (!messageEvent->getConcluded()) { - highlighter_->handleHighlightAction(highlight); - } -} - - -void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) { - chatStateNotifier_->addChatStateRequest(message); - if (userWantsReceipts_ && (contactSupportsReceipts_ != No) && message) { - message->addPayload(boost::make_shared<DeliveryReceiptRequest>()); - } + chatStateNotifier_->setContact(jid); + ChatControllerBase::setToJID(jid); + Presence::ref presence; + if (isInMUC_) { + presence = presenceOracle_->getLastPresence(jid); + } else { + presence = jid.isBare() ? presenceOracle_->getAccountPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid); + } + chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available); + handleBareJIDCapsChanged(toJID_); +} + +void ChatController::setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) { + ChatControllerBase::setAvailableServerFeatures(info); + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + + handleBlockingStateChanged(); + } +} + +bool ChatController::isIncomingMessageFromMe(std::shared_ptr<Message>) { + return false; +} + +void ChatController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) { + if (messageEvent->isReadable()) { + chatWindow_->flash(); + lastWasPresence_ = false; + } + std::shared_ptr<Message> message = messageEvent->getStanza(); + JID from = message->getFrom(); + if (!from.equals(toJID_, JID::WithResource)) { + if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){ + // Bind controller to a full JID if message contains body text or is a typing chat state. + ChatState::ref chatState = message->getPayload<ChatState>(); + if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) { + setToJID(from); + } + } + } + chatStateTracker_->handleMessageReceived(message); + chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>()); + + // handle XEP-0184 Message Receipts + // incomming receipts + if (std::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) { + SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl; + if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) { + chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived); + requestedReceipts_.erase(receipt->getReceivedID()); + } + // incomming errors in response to send out receipts + } else if (message->getPayload<DeliveryReceiptRequest>() && (message->getType() == Message::Error)) { + if (requestedReceipts_.find(message->getID()) != requestedReceipts_.end()) { + chatWindow_->setMessageReceiptState(requestedReceipts_[message->getID()], ChatWindow::ReceiptFailed); + requestedReceipts_.erase(message->getID()); + } + // incoming receipt requests + } else if (message->getPayload<DeliveryReceiptRequest>()) { + if (receivingPresenceFromUs_) { + std::shared_ptr<Message> receiptMessage = std::make_shared<Message>(); + receiptMessage->setTo(toJID_); + receiptMessage->addPayload(std::make_shared<DeliveryReceipt>(message->getID())); + stanzaChannel_->sendMessage(receiptMessage); + } + } +} + +void ChatController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) { + eventController_->handleIncomingEvent(messageEvent); + if (!messageEvent->getConcluded()) { + handleHighlightActions(chatMessage); + } +} + + +void ChatController::preSendMessageRequest(std::shared_ptr<Message> message) { + chatStateNotifier_->addChatStateRequest(message); + if (userWantsReceipts_ && (contactSupportsReceipts_ != No) && message) { + message->addPayload(std::make_shared<DeliveryReceiptRequest>()); + } } void ChatController::setContactIsReceivingPresence(bool isReceivingPresence) { - receivingPresenceFromUs_ = isReceivingPresence; + receivingPresenceFromUs_ = isReceivingPresence; } void ChatController::handleSettingChanged(const std::string& settingPath) { - if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) { - userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); - checkForDisplayingDisplayReceiptsAlert(); - } + if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) { + userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); + checkForDisplayingDisplayReceiptsAlert(); + } } void ChatController::checkForDisplayingDisplayReceiptsAlert() { - boost::optional<ChatWindow::AlertID> newDeliverReceiptAlert; - if (userWantsReceipts_ && (contactSupportsReceipts_ == No)) { - newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts.")); - } else if (userWantsReceipts_ && (contactSupportsReceipts_ == Maybe)) { - newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you send.")); - } else { - if (deliveryReceiptAlert_) { - chatWindow_->removeAlert(*deliveryReceiptAlert_); - deliveryReceiptAlert_.reset(); - } - } - if (newDeliverReceiptAlert) { - if (deliveryReceiptAlert_) { - chatWindow_->removeAlert(*deliveryReceiptAlert_); - } - deliveryReceiptAlert_ = newDeliverReceiptAlert; - } + boost::optional<ChatWindow::AlertID> newDeliverReceiptAlert; + if (userWantsReceipts_ && (contactSupportsReceipts_ == No)) { + newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts.")); + } else if (userWantsReceipts_ && (contactSupportsReceipts_ == Maybe)) { + newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you send.")); + } else { + if (deliveryReceiptAlert_) { + chatWindow_->removeAlert(*deliveryReceiptAlert_); + deliveryReceiptAlert_.reset(); + } + } + if (newDeliverReceiptAlert) { + if (deliveryReceiptAlert_) { + chatWindow_->removeAlert(*deliveryReceiptAlert_); + } + deliveryReceiptAlert_ = newDeliverReceiptAlert; + } } void ChatController::handleBlockingStateChanged() { - boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); - if (blockList->getState() == BlockList::Available) { - if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) { - if (!blockedContactAlert_) { - blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); - } - chatWindow_->setBlockingState(ChatWindow::IsBlocked); - - // disconnect typing events to prevent chat state notifciations to blocked contacts - chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); - chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); - } else { - if (blockedContactAlert_) { - chatWindow_->removeAlert(*blockedContactAlert_); - blockedContactAlert_.reset(); - } - chatWindow_->setBlockingState(ChatWindow::IsUnblocked); - - chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); - chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); - } - } + std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + if (blockList->getState() == BlockList::Available) { + if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) { + if (!blockedContactAlert_) { + blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); + } + chatWindow_->setBlockingState(ChatWindow::IsBlocked); + + // disconnect typing events to prevent chat state notifciations to blocked contacts + chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + } else { + if (blockedContactAlert_) { + chatWindow_->removeAlert(*blockedContactAlert_); + blockedContactAlert_.reset(); + } + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + + chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + } + } } void ChatController::handleBlockUserRequest() { - if (isInMUC_) { - eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_)); - } else { - eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare())); - } + if (isInMUC_) { + eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_)); + } else { + eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare())); + } } void ChatController::handleUnblockUserRequest() { - if (isInMUC_) { - eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_)); - } else { - eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare())); - } + if (isInMUC_) { + eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_)); + } else { + eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare())); + } } void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) { - boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs, RequestInviteToMUCUIEvent::Impromptu)); - eventStream_->send(event); + std::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(getToJID(), droppedJIDs, RequestInviteToMUCUIEvent::Impromptu)); + eventStream_->send(event); } void ChatController::handleWindowClosed() { - onWindowClosed(); + onWindowClosed(); } -void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) { - boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); - if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) { - onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason()); - } +void ChatController::handleUIEvent(std::shared_ptr<UIEvent> event) { + std::shared_ptr<InviteToMUCUIEvent> inviteEvent = std::dynamic_pointer_cast<InviteToMUCUIEvent>(event); + if (inviteEvent && inviteEvent->getOriginator() == getToJID()) { + onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason()); + } } +void ChatController::handleIncomingOwnMessage(std::shared_ptr<Message> message) { + if (!message->getBody().get_value_or("").empty()) { + postSendMessage(message->getBody().get_value_or(""), message); + handleStanzaAcked(message); + } +} -void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) { - boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); - if (replace) { - eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); - replaceMessage(body, myLastMessageUIID_, true, boost::posix_time::microsec_clock::universal_time(), HighlightAction()); - } else { - myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction()); - } +void ChatController::postSendMessage(const std::string& body, std::shared_ptr<Stanza> sentStanza) { + std::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); + if (replace) { + eraseIf(unackedStanzas_, PairSecondEquals<std::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); + replaceMessage(chatMessageParser_->parseMessageBody(body, "", true), myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time()); + } else { + myLastMessageUIID_ = addMessage(chatMessageParser_->parseMessageBody(body, "", true), QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : std::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time()); + } - if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) { - chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending); - unackedStanzas_[sentStanza] = myLastMessageUIID_; - } + if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) { + chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending); + unackedStanzas_[sentStanza] = myLastMessageUIID_; + } - if (sentStanza->getPayload<DeliveryReceiptRequest>()) { - requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_; - chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested); - } + if (sentStanza->getPayload<DeliveryReceiptRequest>()) { + requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_; + chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested); + } - lastWasPresence_ = false; - chatStateNotifier_->userSentMessage(); + lastWasPresence_ = false; + chatStateNotifier_->userSentMessage(); } -void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) { - std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza); - if (unackedStanza != unackedStanzas_.end()) { - chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received); - unackedStanzas_.erase(unackedStanza); - } +void ChatController::handleStanzaAcked(std::shared_ptr<Stanza> stanza) { + std::map<std::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza); + if (unackedStanza != unackedStanzas_.end()) { + chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received); + unackedStanzas_.erase(unackedStanza); + } } void ChatController::setOnline(bool online) { - if (!online) { - std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin(); - for ( ; it != unackedStanzas_.end(); ++it) { - chatWindow_->setAckState(it->second, ChatWindow::Failed); - } - unackedStanzas_.clear(); + if (!online) { + for (auto& stanzaIdPair : unackedStanzas_) { + chatWindow_->setAckState(stanzaIdPair.second, ChatWindow::Failed); + } + unackedStanzas_.clear(); - Presence::ref fakeOffline(new Presence()); - fakeOffline->setFrom(toJID_); - fakeOffline->setType(Presence::Unavailable); - chatStateTracker_->handlePresenceChange(fakeOffline); - } - ChatControllerBase::setOnline(online); + Presence::ref fakeOffline(new Presence()); + fakeOffline->setFrom(toJID_); + fakeOffline->setType(Presence::Unavailable); + chatStateTracker_->handlePresenceChange(fakeOffline); + } + ChatControllerBase::setOnline(online); } void ChatController::handleNewFileTransferController(FileTransferController* ftc) { - std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty()); - std::string ftID = ftc->setChatWindow(chatWindow_, nick); - ftControllers[ftID] = ftc; - lastWasPresence_ = false; + std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty()); + std::string ftID = ftc->setChatWindow(chatWindow_, nick); + ftControllers[ftID] = ftc; + lastWasPresence_ = false; + + if (ftc->isIncoming()) { + auto incomingFileTransferEvent = std::make_shared<IncomingFileTransferEvent>(ftc->getOtherParty()); + if (hasOpenWindow()) { + incomingFileTransferEvent->conclude(); + } + else { + unreadMessages_.push_back(incomingFileTransferEvent); + updateMessageCount(); + } + eventController_->handleIncomingEvent(incomingFileTransferEvent); + } } void ChatController::handleWhiteboardSessionRequest(bool senderIsSelf) { - lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf); + lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf); } void ChatController::handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state) { - chatWindow_->setWhiteboardSessionStatus(lastWbID_, state); + chatWindow_->setWhiteboardSessionStatus(lastWbID_, state); } void ChatController::handleFileTransferCancel(std::string id) { - SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl; - if (ftControllers.find(id) != ftControllers.end()) { - ftControllers[id]->cancel(); - } else { - std::cerr << "unknown file transfer UI id" << std::endl; - } + SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->cancel(); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } } void ChatController::handleFileTransferStart(std::string id, std::string description) { - SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl; - if (ftControllers.find(id) != ftControllers.end()) { - ftControllers[id]->start(description); - } else { - std::cerr << "unknown file transfer UI id" << std::endl; - } + SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->start(description); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } } void ChatController::handleFileTransferAccept(std::string id, std::string filename) { - SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; - if (ftControllers.find(id) != ftControllers.end()) { - ftControllers[id]->accept(filename); - } else { - std::cerr << "unknown file transfer UI id" << std::endl; - } + SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->accept(filename); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } } void ChatController::handleSendFileRequest(std::string filename) { - SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl; - eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename)); + SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl; + eventStream_->send(std::make_shared<SendFileUIEvent>(getToJID(), filename)); } void ChatController::handleWhiteboardSessionAccept() { - eventStream_->send(boost::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_)); + eventStream_->send(std::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_)); } void ChatController::handleWhiteboardSessionCancel() { - eventStream_->send(boost::make_shared<CancelWhiteboardSessionUIEvent>(toJID_)); + eventStream_->send(std::make_shared<CancelWhiteboardSessionUIEvent>(toJID_)); } void ChatController::handleWhiteboardWindowShow() { - eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(toJID_)); + eventStream_->send(std::make_shared<ShowWhiteboardUIEvent>(toJID_)); } std::string ChatController::senderHighlightNameFromMessage(const JID& from) { - if (isInMUC_) { - return nickResolver_->jidToNick(from); - } - else { - return from.toBare().toString(); - } + if (isInMUC_) { + return nickResolver_->jidToNick(from); + } + else { + return from.toBare().toString(); + } } std::string ChatController::senderDisplayNameFromMessage(const JID& from) { - return nickResolver_->jidToNick(from); -} - -std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> presence) { - std::string nick = senderDisplayNameFromMessage(presence->getFrom()); - std::string response; - if (!presence || presence->getType() == Presence::Unavailable || presence->getType() == Presence::Error) { - response = QT_TRANSLATE_NOOP("", "%1% has gone offline"); - } else if (presence->getType() == Presence::Available) { - StatusShow::Type show = presence->getShow(); - if (show == StatusShow::Online || show == StatusShow::FFC) { - response = QT_TRANSLATE_NOOP("", "%1% has become available"); - } else if (show == StatusShow::Away || show == StatusShow::XA) { - response = QT_TRANSLATE_NOOP("", "%1% has gone away"); - } else if (show == StatusShow::DND) { - response = QT_TRANSLATE_NOOP("", "%1% is now busy"); - } - } - Idle::ref idle; - if ((idle = presence->getPayload<Idle>())) { - response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); - } - - if (!response.empty()) { - response = str(format(response) % nick); - } - - if (!presence->getStatus().empty()) { - response += " (" + presence->getStatus() + ")"; - } - return response + "."; -} - -void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresence) { - bool relevantPresence = false; - - if (isInMUC_) { - // For MUC participants we only have a single presence to choose one and - // even for multi-session nicknames multiple resources are not distinguishable - // to other participants. - if (toJID_.equals(newPresence->getFrom(), JID::WithResource)) { - relevantPresence = true; - } - } - else { - // For standard chats we retrieve the account presence from the PresenceOracle, - // as there can be multiple presences to choose from. - if (toJID_.equals(newPresence->getFrom(), JID::WithoutResource)) { - // Presence matches ChatController JID. - newPresence = presenceOracle_->getAccountPresence(toJID_); - relevantPresence = true; - } - } - - if (!relevantPresence) { - return; - } - - if (!newPresence) { - newPresence = boost::make_shared<Presence>(); - newPresence->setType(Presence::Unavailable); - } - lastShownStatus_ = newPresence->getShow(); - - chatStateTracker_->handlePresenceChange(newPresence); - chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available); - std::string newStatusChangeString = getStatusChangeString(newPresence); - if (newStatusChangeString != lastStatusChangeString_) { - if (lastWasPresence_) { - chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp); - } else { - chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); - } - lastStatusChangeString_ = newStatusChangeString; - lastWasPresence_ = true; - } -} - -boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(boost::shared_ptr<Message> message) const { - return message->getTimestamp(); + return nickResolver_->jidToNick(from); +} + +std::string ChatController::getStatusChangeString(std::shared_ptr<Presence> presence) { + std::string nick = senderDisplayNameFromMessage(presence->getFrom()); + std::string response; + if (!presence || presence->getType() == Presence::Unavailable || presence->getType() == Presence::Error) { + response = QT_TRANSLATE_NOOP("", "%1% has gone offline"); + } else if (presence->getType() == Presence::Available) { + StatusShow::Type show = presence->getShow(); + if (show == StatusShow::Online || show == StatusShow::FFC) { + response = QT_TRANSLATE_NOOP("", "%1% has become available"); + } else if (show == StatusShow::Away || show == StatusShow::XA) { + response = QT_TRANSLATE_NOOP("", "%1% has gone away"); + } else if (show == StatusShow::DND) { + response = QT_TRANSLATE_NOOP("", "%1% is now busy"); + } + } + Idle::ref idle; + if ((idle = presence->getPayload<Idle>())) { + response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % Swift::Translator::getInstance()->ptimeToHumanReadableString(idle->getSince())); + } + + if (!response.empty()) { + response = str(format(response) % nick); + } + + if (!presence->getStatus().empty()) { + response += " (" + presence->getStatus() + ")"; + } + return response + "."; +} + +void ChatController::handlePresenceChange(std::shared_ptr<Presence> newPresence) { + bool relevantPresence = false; + + if (isInMUC_) { + // For MUC participants we only have a single presence to choose one and + // even for multi-session nicknames multiple resources are not distinguishable + // to other participants. + if (toJID_.equals(newPresence->getFrom(), JID::WithResource)) { + relevantPresence = true; + } + } + else { + // For standard chats we retrieve the account presence from the PresenceOracle, + // as there can be multiple presences to choose from. + if (toJID_.equals(newPresence->getFrom(), JID::WithoutResource)) { + // Presence matches ChatController JID. + newPresence = presenceOracle_->getAccountPresence(toJID_); + relevantPresence = true; + } + } + + if (!relevantPresence) { + return; + } + + if (!newPresence) { + newPresence = std::make_shared<Presence>(); + newPresence->setType(Presence::Unavailable); + } + lastShownStatus_ = newPresence->getShow(); + + chatStateTracker_->handlePresenceChange(newPresence); + chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available); + std::string newStatusChangeString = getStatusChangeString(newPresence); + if (newStatusChangeString != lastStatusChangeString_) { + if (lastWasPresence_) { + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp); + } else { + chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); + } + lastStatusChangeString_ = newStatusChangeString; + lastWasPresence_ = true; + } +} + +boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(std::shared_ptr<Message> message) const { + return message->getTimestamp(); } void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) { - HistoryMessage::Type type; - if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) { - type = HistoryMessage::PrivateMessage; - } - else { - type = HistoryMessage::Chat; - } + HistoryMessage::Type type; + if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) { + type = HistoryMessage::PrivateMessage; + } + else { + type = HistoryMessage::Chat; + } - if (historyController_) { - historyController_->addMessage(message, fromJID, toJID, type, timeStamp); - } + if (historyController_) { + historyController_->addMessage(message, fromJID, toJID, type, timeStamp); + } } ChatWindow* ChatController::detachChatWindow() { - chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); - chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); - return ChatControllerBase::detachChatWindow(); + chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + return ChatControllerBase::detachChatWindow(); } } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 9ee82eb..7bd6107 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -16,101 +16,101 @@ #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { - class AvatarManager; - class ChatStateNotifier; - class ChatStateTracker; - class NickResolver; - class EntityCapsProvider; - class FileTransferController; - class SettingsProvider; - class HistoryController; - class HighlightManager; - class ClientBlockListManager; - class UIEvent; - - class ChatController : public ChatControllerBase { - public: - ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); - virtual ~ChatController(); - virtual void setToJID(const JID& jid) SWIFTEN_OVERRIDE; - virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; - virtual void setOnline(bool online) SWIFTEN_OVERRIDE; - virtual void handleNewFileTransferController(FileTransferController* ftc); - virtual void handleWhiteboardSessionRequest(bool senderIsSelf); - virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state); - virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) SWIFTEN_OVERRIDE; - virtual ChatWindow* detachChatWindow() SWIFTEN_OVERRIDE; - - protected: - virtual void cancelReplaces() SWIFTEN_OVERRIDE; - virtual JID getBaseJID() SWIFTEN_OVERRIDE; - virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; - - private: - void handlePresenceChange(boost::shared_ptr<Presence> newPresence); - std::string getStatusChangeString(boost::shared_ptr<Presence> presence); - virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message> message) SWIFTEN_OVERRIDE; - virtual void postSendMessage(const std::string &body, boost::shared_ptr<Stanza> sentStanza) SWIFTEN_OVERRIDE; - virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) SWIFTEN_OVERRIDE; - virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction&) SWIFTEN_OVERRIDE; - virtual void preSendMessageRequest(boost::shared_ptr<Message>) SWIFTEN_OVERRIDE; - virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const SWIFTEN_OVERRIDE; - void handleStanzaAcked(boost::shared_ptr<Stanza> stanza); - virtual void dayTicked() SWIFTEN_OVERRIDE { lastWasPresence_ = false; } - void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); - virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; - - void handleFileTransferCancel(std::string /* id */); - void handleFileTransferStart(std::string /* id */, std::string /* description */); - void handleFileTransferAccept(std::string /* id */, std::string /* filename */); - void handleSendFileRequest(std::string filename); - - void handleWhiteboardSessionAccept(); - void handleWhiteboardSessionCancel(); - void handleWhiteboardWindowShow(); - - void handleSettingChanged(const std::string& settingPath); - void checkForDisplayingDisplayReceiptsAlert(); - - void handleBlockingStateChanged(); - void handleBlockUserRequest(); - void handleUnblockUserRequest(); - - void handleInviteToChat(const std::vector<JID>& droppedJIDs); - - void handleWindowClosed(); - - void handleUIEvent(boost::shared_ptr<UIEvent> event); - - private: - NickResolver* nickResolver_; - ChatStateNotifier* chatStateNotifier_; - ChatStateTracker* chatStateTracker_; - std::string myLastMessageUIID_; - bool isInMUC_; - bool lastWasPresence_; - std::string lastStatusChangeString_; - std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_; - std::map<std::string, std::string> requestedReceipts_; - StatusShow::Type lastShownStatus_; - UIEventStream* eventStream_; - - Tristate contactSupportsReceipts_; - bool receivingPresenceFromUs_; - bool userWantsReceipts_; - std::map<std::string, FileTransferController*> ftControllers; - SettingsProvider* settings_; - std::string lastWbID_; - - ClientBlockListManager* clientBlockListManager_; - boost::bsignals::scoped_connection blockingOnStateChangedConnection_; - boost::bsignals::scoped_connection blockingOnItemAddedConnection_; - boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; - - boost::optional<ChatWindow::AlertID> deliveryReceiptAlert_; - boost::optional<ChatWindow::AlertID> blockedContactAlert_; - }; + class AvatarManager; + class ChatStateNotifier; + class ChatStateTracker; + class NickResolver; + class EntityCapsProvider; + class FileTransferController; + class SettingsProvider; + class HistoryController; + class HighlightManager; + class ClientBlockListManager; + class UIEvent; + + class ChatController : public ChatControllerBase { + public: + ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); + virtual ~ChatController(); + virtual void setToJID(const JID& jid) SWIFTEN_OVERRIDE; + virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; + virtual void setOnline(bool online) SWIFTEN_OVERRIDE; + virtual void handleNewFileTransferController(FileTransferController* ftc); + virtual void handleWhiteboardSessionRequest(bool senderIsSelf); + virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state); + virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) SWIFTEN_OVERRIDE; + virtual ChatWindow* detachChatWindow() SWIFTEN_OVERRIDE; + virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; + + protected: + virtual void cancelReplaces() SWIFTEN_OVERRIDE; + virtual JID getBaseJID() SWIFTEN_OVERRIDE; + virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; + + private: + void handlePresenceChange(std::shared_ptr<Presence> newPresence); + std::string getStatusChangeString(std::shared_ptr<Presence> presence); + virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; + virtual void postSendMessage(const std::string &body, std::shared_ptr<Stanza> sentStanza) SWIFTEN_OVERRIDE; + virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) SWIFTEN_OVERRIDE; + virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; + virtual void preSendMessageRequest(std::shared_ptr<Message>) SWIFTEN_OVERRIDE; + virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; + virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; + virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const SWIFTEN_OVERRIDE; + void handleStanzaAcked(std::shared_ptr<Stanza> stanza); + virtual void dayTicked() SWIFTEN_OVERRIDE { lastWasPresence_ = false; } + void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); + virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; + + void handleFileTransferCancel(std::string /* id */); + void handleFileTransferStart(std::string /* id */, std::string /* description */); + void handleFileTransferAccept(std::string /* id */, std::string /* filename */); + void handleSendFileRequest(std::string filename); + + void handleWhiteboardSessionAccept(); + void handleWhiteboardSessionCancel(); + void handleWhiteboardWindowShow(); + + void handleSettingChanged(const std::string& settingPath); + void checkForDisplayingDisplayReceiptsAlert(); + + void handleBlockingStateChanged(); + void handleBlockUserRequest(); + void handleUnblockUserRequest(); + + void handleInviteToChat(const std::vector<JID>& droppedJIDs); + + void handleWindowClosed(); + + void handleUIEvent(std::shared_ptr<UIEvent> event); + + private: + NickResolver* nickResolver_; + ChatStateNotifier* chatStateNotifier_; + ChatStateTracker* chatStateTracker_; + std::string myLastMessageUIID_; + bool isInMUC_; + std::string lastStatusChangeString_; + std::map<std::shared_ptr<Stanza>, std::string> unackedStanzas_; + std::map<std::string, std::string> requestedReceipts_; + StatusShow::Type lastShownStatus_; + UIEventStream* eventStream_; + + Tristate contactSupportsReceipts_; + bool receivingPresenceFromUs_ = false; + bool userWantsReceipts_; + std::map<std::string, FileTransferController*> ftControllers; + SettingsProvider* settings_; + std::string lastWbID_; + + ClientBlockListManager* clientBlockListManager_; + boost::signals2::scoped_connection blockingOnStateChangedConnection_; + boost::signals2::scoped_connection blockingOnItemAddedConnection_; + boost::signals2::scoped_connection blockingOnItemRemovedConnection_; + + boost::optional<ChatWindow::AlertID> deliveryReceiptAlert_; + boost::optional<ChatWindow::AlertID> blockedContactAlert_; + }; } diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index fef3e7a..da9064e 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -7,19 +7,15 @@ #include <Swift/Controllers/Chat/ChatControllerBase.h> #include <map> +#include <memory> #include <sstream> #include <boost/algorithm/string.hpp> #include <boost/bind.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/numeric/conversion/cast.hpp> -#include <boost/shared_ptr.hpp> -#include <boost/smart_ptr/make_shared.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Path.h> -#include <Swiften/Base/String.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/EntityCapsProvider.h> @@ -42,343 +38,384 @@ namespace Swift { -ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { - chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); - chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); - chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); - chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this)); - entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); - highlighter_ = highlightManager->createHighlighter(); - ChatControllerBase::setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); - createDayChangeTimer(); +ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { + chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); + chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); + chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); + chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this)); + entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); + highlighter_ = highlightManager->createHighlighter(); + ChatControllerBase::setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); + createDayChangeTimer(); } ChatControllerBase::~ChatControllerBase() { - if (dateChangeTimer_) { - dateChangeTimer_->onTick.disconnect(boost::bind(&ChatControllerBase::handleDayChangeTick, this)); - dateChangeTimer_->stop(); - } + if (dateChangeTimer_) { + dateChangeTimer_->onTick.disconnect(boost::bind(&ChatControllerBase::handleDayChangeTick, this)); + dateChangeTimer_->stop(); + } - delete highlighter_; - delete chatWindow_; + delete highlighter_; + delete chatWindow_; } void ChatControllerBase::handleLogCleared() { - cancelReplaces(); + cancelReplaces(); } ChatWindow* ChatControllerBase::detachChatWindow() { - ChatWindow* chatWindow = chatWindow_; - chatWindow_ = NULL; - return chatWindow; + ChatWindow* chatWindow = chatWindow_; + chatWindow_ = nullptr; + return chatWindow; } void ChatControllerBase::handleCapsChanged(const JID& jid) { - if (jid.compare(toJID_, JID::WithoutResource) == 0) { - handleBareJIDCapsChanged(jid); - } + if (jid.compare(toJID_, JID::WithoutResource) == 0) { + handleBareJIDCapsChanged(jid); + } } void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) { - if (chatWindow_) { - chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu); - } + if (chatWindow_) { + chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu); + } } void ChatControllerBase::createDayChangeTimer() { - if (timerFactory_) { - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1)); - int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds()); - dateChangeTimer_ = timerFactory_->createTimer(millisecondsUntilMidnight); - dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this)); - dateChangeTimer_->start(); - } + if (timerFactory_) { + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1)); + int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds()); + dateChangeTimer_ = timerFactory_->createTimer(millisecondsUntilMidnight); + dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this)); + dateChangeTimer_->start(); + } } void ChatControllerBase::handleDayChangeTick() { - dateChangeTimer_->stop(); - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection); - dayTicked(); - createDayChangeTimer(); + dateChangeTimer_->stop(); + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection); + lastWasPresence_ = false; + dayTicked(); + createDayChangeTimer(); } void ChatControllerBase::setEnabled(bool enabled) { - chatWindow_->setOnline(enabled); - chatWindow_->setCanInitiateImpromptuChats(false); + chatWindow_->setOnline(enabled); + chatWindow_->setCanInitiateImpromptuChats(false); } void ChatControllerBase::setOnline(bool online) { - setEnabled(online); + setEnabled(online); } JID ChatControllerBase::getBaseJID() { - return JID(toJID_.toBare()); + return JID(toJID_.toBare()); } -void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { - if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) { - GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_); - request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2)); - request->send(); - } else { - chatWindow_->setSecurityLabelsEnabled(false); - labelsEnabled_ = false; - } +void ChatControllerBase::setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) { + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) { + GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_); + request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2)); + request->send(); + } else { + chatWindow_->setSecurityLabelsEnabled(false); + labelsEnabled_ = false; + } } void ChatControllerBase::handleAllMessagesRead() { - if (!unreadMessages_.empty()) { - targetedUnreadMessages_.clear(); - foreach (boost::shared_ptr<StanzaEvent> stanzaEvent, unreadMessages_) { - stanzaEvent->conclude(); - } - unreadMessages_.clear(); - chatWindow_->setUnreadMessageCount(0); - onUnreadCountChanged(); - } + if (!unreadMessages_.empty()) { + targetedUnreadMessages_.clear(); + for (std::shared_ptr<StanzaEvent> stanzaEvent : unreadMessages_) { + stanzaEvent->conclude(); + } + unreadMessages_.clear(); + updateMessageCount(); + } } int ChatControllerBase::getUnreadCount() { - return boost::numeric_cast<int>(targetedUnreadMessages_.size()); + return boost::numeric_cast<int>(targetedUnreadMessages_.size()); } void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) { - if (!stanzaChannel_->isAvailable() || body.empty()) { - return; - } - boost::shared_ptr<Message> message(new Message()); - message->setTo(toJID_); - message->setType(Swift::Message::Chat); - message->setBody(body); - if (labelsEnabled_) { - if (!isCorrectionMessage) { - lastLabel_ = chatWindow_->getSelectedSecurityLabel(); - } - SecurityLabelsCatalog::Item labelItem = lastLabel_; - if (labelItem.getLabel()) { - message->addPayload(labelItem.getLabel()); - } - } - preSendMessageRequest(message); - - boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); - if (useDelayForLatency_) { - message->addPayload(boost::make_shared<Delay>(now, selfJID_)); - } - if (isCorrectionMessage) { - message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_))); - } - message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID()); - stanzaChannel_->sendMessage(message); - postSendMessage(message->getBody().get(), boost::dynamic_pointer_cast<Stanza>(message)); - onActivity(message->getBody().get()); + if (!stanzaChannel_->isAvailable() || body.empty()) { + return; + } + std::shared_ptr<Message> message(new Message()); + message->setTo(toJID_); + message->setType(Swift::Message::Chat); + message->setBody(body); + if (labelsEnabled_) { + if (!isCorrectionMessage) { + lastLabel_ = chatWindow_->getSelectedSecurityLabel(); + } + SecurityLabelsCatalog::Item labelItem = lastLabel_; + if (labelItem.getLabel()) { + message->addPayload(labelItem.getLabel()); + } + } + preSendMessageRequest(message); + + boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + if (useDelayForLatency_) { + message->addPayload(std::make_shared<Delay>(now, selfJID_)); + } + if (isCorrectionMessage) { + message->addPayload(std::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_))); + } + message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID()); + stanzaChannel_->sendMessage(message); + postSendMessage(message->getBody().get(), std::dynamic_pointer_cast<Stanza>(message)); + onActivity(message->getBody().get()); #ifdef SWIFT_EXPERIMENTAL_HISTORY - logMessage(body, selfJID_, toJID_, now, false); + logMessage(body, selfJID_, toJID_, now, false); #endif } -void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) { - if (catalog && !error) { - if (catalog->getItems().size() == 0) { - chatWindow_->setSecurityLabelsEnabled(false); - labelsEnabled_ = false; - } else { - labelsEnabled_ = true; - chatWindow_->setAvailableSecurityLabels(catalog->getItems()); - chatWindow_->setSecurityLabelsEnabled(true); - } - } else { - labelsEnabled_ = false; - chatWindow_->setSecurityLabelsError(); - } +void ChatControllerBase::handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) { + if (catalog && !error) { + if (catalog->getItems().size() == 0) { + chatWindow_->setSecurityLabelsEnabled(false); + labelsEnabled_ = false; + } else { + labelsEnabled_ = true; + chatWindow_->setAvailableSecurityLabels(catalog->getItems()); + chatWindow_->setSecurityLabelsEnabled(true); + } + } else { + labelsEnabled_ = false; + chatWindow_->setSecurityLabelsError(); + } } void ChatControllerBase::showChatWindow() { - chatWindow_->show(); + chatWindow_->show(); } void ChatControllerBase::activateChatWindow() { - chatWindow_->activate(); + chatWindow_->activate(); } bool ChatControllerBase::hasOpenWindow() const { - return chatWindow_ && chatWindow_->isVisible(); + return chatWindow_ && chatWindow_->isVisible(); } -std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { - if (boost::starts_with(message, "/me ")) { - return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); - } else { - return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); - } +ChatWindow::ChatMessage ChatControllerBase::buildChatWindowChatMessage(const std::string& message, bool senderIsSelf, const HighlightAction& fullMessageHighlightAction) { + ChatWindow::ChatMessage chatMessage; + chatMessage = chatMessageParser_->parseMessageBody(message, highlighter_->getNick(), senderIsSelf); + chatMessage.setFullMessageHighlightAction(fullMessageHighlightAction); + return chatMessage; } -void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight) { - if (boost::starts_with(message, "/me ")) { - chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight); - } else { - chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), id, time, highlight); - } +void ChatControllerBase::handleHighlightActions(const ChatWindow::ChatMessage& chatMessage) { + std::set<std::string> playedSounds; + if (chatMessage.getFullMessageHighlightAction().playSound()) { + highlighter_->handleHighlightAction(chatMessage.getFullMessageHighlightAction()); + playedSounds.insert(chatMessage.getFullMessageHighlightAction().getSoundFile()); + } + for (std::shared_ptr<ChatWindow::ChatMessagePart> part : chatMessage.getParts()) { + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightMessage = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part); + if (highlightMessage && highlightMessage->action.playSound()) { + if (playedSounds.find(highlightMessage->action.getSoundFile()) == playedSounds.end()) { + highlighter_->handleHighlightAction(highlightMessage->action); + playedSounds.insert(highlightMessage->action.getSoundFile()); + } + } + } +} + +void ChatControllerBase::updateMessageCount() { + chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); + onUnreadCountChanged(); +} + +std::string ChatControllerBase::addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, const std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time) { + if (chatMessage.isMeCommand()) { + return chatWindow_->addAction(chatMessage, senderName, senderIsSelf, label, pathToString(avatarPath), time); + } + else { + return chatWindow_->addMessage(chatMessage, senderName, senderIsSelf, label, pathToString(avatarPath), time); + } +} + +void ChatControllerBase::replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time) { + if (chatMessage.isMeCommand()) { + chatWindow_->replaceWithAction(chatMessage, id, time); + } + else { + chatWindow_->replaceMessage(chatMessage, id, time); + } } bool ChatControllerBase::isFromContact(const JID& from) { - return from.toBare() == toJID_.toBare(); -} - -void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { - preHandleIncomingMessage(messageEvent); - if (messageEvent->isReadable() && !messageEvent->getConcluded()) { - unreadMessages_.push_back(messageEvent); - if (messageEvent->targetsMe()) { - targetedUnreadMessages_.push_back(messageEvent); - } - } - boost::shared_ptr<Message> message = messageEvent->getStanza(); - std::string body = message->getBody().get_value_or(""); - HighlightAction highlight; - if (message->isError()) { - if (!message->getTo().getResource().empty()) { - std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); - chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); - } - } - else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { - handleMUCInvitation(messageEvent->getStanza()); - return; - } - else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) { - handleMediatedMUCInvitation(messageEvent->getStanza()); - return; - } - else { - if (!messageEvent->isReadable()) { - return; - } - showChatWindow(); - JID from = message->getFrom(); - std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>(); - for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) { - if (!delayPayloads[i]->getFrom()) { - continue; - } - boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); - std::ostringstream s; - s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << "."; - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); - } - boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); - - // Determine the timestamp - boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time(); - boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message); - if (messageTimeStamp) { - timeStamp = *messageTimeStamp; - } - onActivity(body); - - // Highlight - if (!isIncomingMessageFromMe(message)) { - highlight = highlighter_->findAction(body, senderHighlightNameFromMessage(from)); - } - - boost::shared_ptr<Replace> replace = message->getPayload<Replace>(); - if (replace) { - std::string body = message->getBody().get_value_or(""); - // Should check if the user has a previous message - std::map<JID, std::string>::iterator lastMessage; - lastMessage = lastMessagesUIID_.find(from); - if (lastMessage != lastMessagesUIID_.end()) { - replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), timeStamp, highlight); - } - } - else { - addMessageHandleIncomingMessage(from, body, isIncomingMessageFromMe(message), label, timeStamp, highlight); - } - - logMessage(body, from, selfJID_, timeStamp, true); - } - chatWindow_->show(); - chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); - onUnreadCountChanged(); - postHandleIncomingMessage(messageEvent, highlight); -} - -void ChatControllerBase::addMessageHandleIncomingMessage(const JID& from, const std::string& message, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp, const HighlightAction& highlight) { - lastMessagesUIID_[from] = addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp, highlight); -} - -std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) { - std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message"); - if (!error->getText().empty()) { - return error->getText(); - } - else { - switch (error->getCondition()) { - case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); - case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); - case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); - case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); - case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); - case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); - case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); - case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); - case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); - case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); - case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); - case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); - case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); - case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); - case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); - case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); - case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); - case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); - case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); - case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); - case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); - case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); - } - } - assert(false); - return defaultMessage; + return from.toBare() == toJID_.toBare(); +} + +void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) { + preHandleIncomingMessage(messageEvent); + if (messageEvent->isReadable() && !messageEvent->getConcluded()) { + unreadMessages_.push_back(messageEvent); + if (messageEvent->targetsMe()) { + targetedUnreadMessages_.push_back(messageEvent); + } + } + + std::shared_ptr<Message> message = messageEvent->getStanza(); + ChatWindow::ChatMessage chatMessage; + boost::optional<std::string> optionalBody = message->getBody(); + std::string body = optionalBody.get_value_or(""); + if (message->isError()) { + if (!message->getTo().getResource().empty()) { + std::string errorMessage; + if (message->getPayload<Swift::ErrorPayload>()->getCondition() == ErrorPayload::ItemNotFound) { + errorMessage = QT_TRANSLATE_NOOP("", "This user could not be found in the room."); + } + else { + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); + } + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); + } + } + else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { + handleMUCInvitation(messageEvent->getStanza()); + return; + } + else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) { + handleMediatedMUCInvitation(messageEvent->getStanza()); + return; + } + else { + if (!messageEvent->isReadable()) { + return; + } + showChatWindow(); + JID from = message->getFrom(); + std::vector<std::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>(); + for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) { + if (!delayPayloads[i]->getFrom()) { + continue; + } + boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + std::ostringstream s; + s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << "."; + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); + } + std::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); + + // Determine the timestamp + boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time(); + boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message); + if (messageTimeStamp) { + timeStamp = *messageTimeStamp; + } + onActivity(body); + + // Highlight + HighlightAction fullMessageHighlight; + if (!isIncomingMessageFromMe(message)) { + fullMessageHighlight = highlighter_->findFirstFullMessageMatchAction(body, senderHighlightNameFromMessage(from)); + } + + std::shared_ptr<Replace> replace = message->getPayload<Replace>(); + bool senderIsSelf = isIncomingMessageFromMe(message); + if (replace) { + // Should check if the user has a previous message + std::map<JID, std::string>::iterator lastMessage; + lastMessage = lastMessagesUIID_.find(from); + if (lastMessage != lastMessagesUIID_.end()) { + chatMessage = buildChatWindowChatMessage(body, senderIsSelf, fullMessageHighlight); + replaceMessage(chatMessage, lastMessagesUIID_[from], timeStamp); + } + } + else { + chatMessage = buildChatWindowChatMessage(body, senderIsSelf, fullMessageHighlight); + addMessageHandleIncomingMessage(from, chatMessage, senderIsSelf, label, timeStamp); + } + + logMessage(body, from, selfJID_, timeStamp, true); + } + chatWindow_->show(); + updateMessageCount(); + postHandleIncomingMessage(messageEvent, chatMessage); +} + +void ChatControllerBase::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { + lastMessagesUIID_[from] = addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp); +} + +std::string ChatControllerBase::getErrorMessage(std::shared_ptr<ErrorPayload> error) { + std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message"); + if (!error->getText().empty()) { + return error->getText(); + } + else { + switch (error->getCondition()) { + case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); + case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); + case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); + case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); + case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); + case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); + case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); + case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); + case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); + case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); + case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); + case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); + case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); + case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); + case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); + case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); + case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); + case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); + case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); + case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); + case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); + case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); + } + } + assert(false); + return defaultMessage; } void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) { - unreadMessages_.push_back(event); - chatWindow_->show(); - chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); - onUnreadCountChanged(); - chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu()); - eventController_->handleIncomingEvent(event); + unreadMessages_.push_back(event); + chatWindow_->show(); + updateMessageCount(); + chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu()); + eventController_->handleIncomingEvent(event); + lastWasPresence_ = false; } void ChatControllerBase::handleMUCInvitation(Message::ref message) { - MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); - - if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { - eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true)); - } else { - MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu()); - handleGeneralMUCInvitation(inviteEvent); - } + MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); + + if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { + eventStream_->send(std::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true)); + } else { + MUCInviteEvent::ref inviteEvent = std::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu()); + handleGeneralMUCInvitation(inviteEvent); + } } void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { - MUCUserPayload::Invite invite = *message->getPayload<MUCUserPayload>()->getInvite(); - JID from = message->getFrom(); - std::string reason; - if (!invite.reason.empty()) { - reason = invite.reason; - } - std::string password; - if (message->getPayload<MUCUserPayload>()->getPassword()) { - password = *message->getPayload<MUCUserPayload>()->getPassword(); - } - - MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false); - handleGeneralMUCInvitation(inviteEvent); + MUCUserPayload::Invite invite = *message->getPayload<MUCUserPayload>()->getInvite(); + JID from = message->getFrom(); + std::string reason; + if (!invite.reason.empty()) { + reason = invite.reason; + } + std::string password; + if (message->getPayload<MUCUserPayload>()->getPassword()) { + password = *message->getPayload<MUCUserPayload>()->getPassword(); + } + + MUCInviteEvent::ref inviteEvent = std::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false); + handleGeneralMUCInvitation(inviteEvent); } } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 4e68a2b..4255c19 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -7,16 +7,16 @@ #pragma once #include <map> +#include <memory> #include <string> #include <vector> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/filesystem/path.hpp> #include <boost/optional.hpp> -#include <boost/shared_ptr.hpp> +#include <boost/signals2.hpp> #include <Swiften/Base/IDGenerator.h> -#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/ErrorPayload.h> #include <Swiften/Elements/SecurityLabelsCatalog.h> @@ -35,104 +35,110 @@ #include <Swift/Controllers/XMPPEvents/MessageEvent.h> namespace Swift { - class IQRouter; - class StanzaChannel; - class ChatWindowFactory; - class AvatarManager; - class UIEventStream; - class EventController; - class EntityCapsProvider; - class HighlightManager; - class Highlighter; - class ChatMessageParser; - class AutoAcceptMUCInviteDecider; + class IQRouter; + class StanzaChannel; + class ChatWindowFactory; + class AvatarManager; + class UIEventStream; + class EventController; + class EntityCapsProvider; + class HighlightManager; + class Highlighter; + class ChatMessageParser; + class AutoAcceptMUCInviteDecider; - class ChatControllerBase : public boost::bsignals::trackable { - public: - virtual ~ChatControllerBase(); - void showChatWindow(); - void activateChatWindow(); - bool hasOpenWindow() const; - virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); - void handleIncomingMessage(boost::shared_ptr<MessageEvent> message); - std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); - void replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight); - virtual void setOnline(bool online); - void setEnabled(bool enabled); - virtual void setToJID(const JID& jid) {toJID_ = jid;} - /** Used for determining when something is recent.*/ - boost::signal<void (const std::string& /*activity*/)> onActivity; - boost::signal<void ()> onUnreadCountChanged; - boost::signal<void ()> onWindowClosed; - int getUnreadCount(); - const JID& getToJID() {return toJID_;} - void handleCapsChanged(const JID& jid); - void setCanStartImpromptuChats(bool supportsImpromptu); - virtual ChatWindow* detachChatWindow(); - boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC; + class ChatControllerBase : public boost::signals2::trackable { + public: + virtual ~ChatControllerBase(); + void showChatWindow(); + void activateChatWindow(); + bool hasOpenWindow() const; + virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info); + virtual void handleIncomingOwnMessage(std::shared_ptr<Message> /*message*/) {} + void handleIncomingMessage(std::shared_ptr<MessageEvent> message); + std::string addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time); + void replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time); + virtual void setOnline(bool online); + void setEnabled(bool enabled); + virtual void setToJID(const JID& jid) {toJID_ = jid;} + /** Used for determining when something is recent.*/ + boost::signals2::signal<void (const std::string& /*activity*/)> onActivity; + boost::signals2::signal<void ()> onUnreadCountChanged; + boost::signals2::signal<void ()> onWindowClosed; + int getUnreadCount(); + const JID& getToJID() {return toJID_;} + void handleCapsChanged(const JID& jid); + void setCanStartImpromptuChats(bool supportsImpromptu); + virtual ChatWindow* detachChatWindow(); + boost::signals2::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC; - protected: - ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); + protected: + ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); - /** - * Pass the Message appended, and the stanza used to send it. - */ - virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {} - virtual std::string senderDisplayNameFromMessage(const JID& from) = 0; - virtual std::string senderHighlightNameFromMessage(const JID& from) = 0; - virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0; - virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {} - virtual void addMessageHandleIncomingMessage(const JID& from, const std::string& message, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time, const HighlightAction& highlight); - virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&) {} - virtual void preSendMessageRequest(boost::shared_ptr<Message>) {} - virtual bool isFromContact(const JID& from); - virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0; - virtual void dayTicked() {} - virtual void handleBareJIDCapsChanged(const JID& jid) = 0; - std::string getErrorMessage(boost::shared_ptr<ErrorPayload>); - virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {} - virtual void cancelReplaces() = 0; - /** JID any iq for account should go to - bare except for PMs */ - virtual JID getBaseJID(); - virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0; + /** + * Pass the Message appended, and the stanza used to send it. + */ + virtual void postSendMessage(const std::string&, std::shared_ptr<Stanza>) {} + virtual std::string senderDisplayNameFromMessage(const JID& from) = 0; + virtual std::string senderHighlightNameFromMessage(const JID& from) = 0; + virtual bool isIncomingMessageFromMe(std::shared_ptr<Message>) = 0; + virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) {} + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time); + virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage&) {} + virtual void preSendMessageRequest(std::shared_ptr<Message>) {} + virtual bool isFromContact(const JID& from); + virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const = 0; + virtual void dayTicked() {} + virtual void handleBareJIDCapsChanged(const JID& jid) = 0; + std::string getErrorMessage(std::shared_ptr<ErrorPayload>); + virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {} + virtual void cancelReplaces() = 0; + /** JID any iq for account should go to - bare except for PMs */ + virtual JID getBaseJID(); + virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0; + ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, bool senderIsSelf, const HighlightAction& fullMessageHighlightAction); + void handleHighlightActions(const ChatWindow::ChatMessage& chatMessage); + void updateMessageCount(); - private: - IDGenerator idGenerator_; - std::string lastSentMessageStanzaID_; - void createDayChangeTimer(); - void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage); - void handleAllMessagesRead(); - void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error); - void handleDayChangeTick(); - void handleMUCInvitation(Message::ref message); - void handleMediatedMUCInvitation(Message::ref message); - void handleGeneralMUCInvitation(MUCInviteEvent::ref event); - void handleLogCleared(); + private: + IDGenerator idGenerator_; + std::string lastSentMessageStanzaID_; + void createDayChangeTimer(); - protected: - JID selfJID_; - std::vector<boost::shared_ptr<StanzaEvent> > unreadMessages_; - std::vector<boost::shared_ptr<StanzaEvent> > targetedUnreadMessages_; - StanzaChannel* stanzaChannel_; - IQRouter* iqRouter_; - ChatWindowFactory* chatWindowFactory_; - ChatWindow* chatWindow_; - JID toJID_; - bool labelsEnabled_; - std::map<JID, std::string> lastMessagesUIID_; - PresenceOracle* presenceOracle_; - AvatarManager* avatarManager_; - bool useDelayForLatency_; - EventController* eventController_; - boost::shared_ptr<Timer> dateChangeTimer_; - TimerFactory* timerFactory_; - EntityCapsProvider* entityCapsProvider_; - SecurityLabelsCatalog::Item lastLabel_; - HistoryController* historyController_; - MUCRegistry* mucRegistry_; - Highlighter* highlighter_; - boost::shared_ptr<ChatMessageParser> chatMessageParser_; - AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; - UIEventStream* eventStream_; - }; + void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage); + void handleAllMessagesRead(); + void handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error); + void handleDayChangeTick(); + void handleMUCInvitation(Message::ref message); + void handleMediatedMUCInvitation(Message::ref message); + void handleGeneralMUCInvitation(MUCInviteEvent::ref event); + void handleLogCleared(); + + protected: + JID selfJID_; + std::vector<std::shared_ptr<StanzaEvent> > unreadMessages_; + std::vector<std::shared_ptr<StanzaEvent> > targetedUnreadMessages_; + StanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + ChatWindowFactory* chatWindowFactory_; + ChatWindow* chatWindow_; + JID toJID_; + bool labelsEnabled_; + std::map<JID, std::string> lastMessagesUIID_; + PresenceOracle* presenceOracle_; + AvatarManager* avatarManager_; + bool useDelayForLatency_; + EventController* eventController_; + std::shared_ptr<Timer> dateChangeTimer_; + TimerFactory* timerFactory_; + EntityCapsProvider* entityCapsProvider_; + SecurityLabelsCatalog::Item lastLabel_; + HistoryController* historyController_; + MUCRegistry* mucRegistry_; + Highlighter* highlighter_; + std::shared_ptr<ChatMessageParser> chatMessageParser_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + UIEventStream* eventStream_; + bool lastWasPresence_ = false; + }; } diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp index 666ec2f..ec7df6c 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.cpp +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -1,197 +1,197 @@ /* - * Copyright (c) 2013-2015 Isode Limited. + * Copyright (c) 2013-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swift/Controllers/Chat/ChatMessageParser.h> -#include <vector> +#include <memory> #include <utility> +#include <vector> -#include <boost/smart_ptr/make_shared.hpp> #include <boost/algorithm/string.hpp> #include <Swiften/Base/Regex.h> -#include <Swiften/Base/foreach.h> +#include <Swiften/Base/String.h> #include <SwifTools/Linkify.h> - namespace Swift { - ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) - : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { - } - - typedef std::pair<std::string, std::string> StringPair; - - ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) { - ChatWindow::ChatMessage parsedMessage; - std::string remaining = body; - /* Parse one, URLs */ - while (!remaining.empty()) { - bool found = false; - std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining); - remaining = ""; - for (size_t i = 0; i < links.first.size(); i++) { - const std::string& part = links.first[i]; - if (found) { - // Must be on the last part, then - remaining = part; - } - else { - if (i == links.second) { - found = true; - parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part)); - } - else { - parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part)); - } - } - } - } - - /* do emoticon substitution */ - parsedMessage = emoticonHighlight(parsedMessage); - - if (!senderIsSelf) { /* do not highlight our own messsages */ - /* do word-based color highlighting */ - parsedMessage = splitHighlight(parsedMessage, nick); - } - - return parsedMessage; - } - - ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) - { - ChatWindow::ChatMessage parsedMessage = message; - - std::string regexString; - /* Parse two, emoticons */ - foreach (StringPair emoticon, emoticons_) { - /* Construct a regexp that finds an instance of any of the emoticons inside a group - * at the start or end of the line, or beside whitespace. - */ - regexString += regexString.empty() ? "" : "|"; - std::string escaped = "(" + Regex::escape(emoticon.first) + ")"; - regexString += "^" + escaped + "|"; - regexString += escaped + "$|"; - regexString += "\\s" + escaped + "|"; - regexString += escaped + "\\s"; - - } - if (!regexString.empty()) { - regexString += ""; - boost::regex emoticonRegex(regexString); - - ChatWindow::ChatMessage newMessage; - foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { - boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; - if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { - try { - boost::match_results<std::string::const_iterator> match; - const std::string& text = textPart->text; - std::string::const_iterator start = text.begin(); - while (regex_search(start, text.end(), match, emoticonRegex)) { - int matchIndex = 0; - for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) { - if (match[matchIndex].length() > 0) { - //This is the matching subgroup - break; - } - } - std::string::const_iterator matchStart = match[matchIndex].first; - std::string::const_iterator matchEnd = match[matchIndex].second; - if (start != matchStart) { - /* If we're skipping over plain text since the previous emoticon, record it as plain text */ - newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); - } - boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>(); - std::string matchString = match[matchIndex].str(); - std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString); - assert (emoticonIterator != emoticons_.end()); - const StringPair& emoticon = *emoticonIterator; - emoticonPart->imagePath = emoticon.second; - emoticonPart->alternativeText = emoticon.first; - newMessage.append(emoticonPart); - start = matchEnd; - } - if (start != text.end()) { - /* If there's plain text after the last emoticon, record it */ - newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); - } - - } - catch (std::runtime_error) { - /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ - newMessage.append(part); - } - } - else { - newMessage.append(part); - } - } - parsedMessage = newMessage; - - } - return parsedMessage; - } - - ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick) - { - ChatWindow::ChatMessage parsedMessage = message; - - for (size_t i = 0; i < highlightRules_->getSize(); ++i) { - const HighlightRule& rule = highlightRules_->getRule(i); - if (rule.getMatchMUC() && !mucMode_) { - continue; /* this rule only applies to MUC's, and this is a CHAT */ - } else if (rule.getMatchChat() && mucMode_) { - continue; /* this rule only applies to CHAT's, and this is a MUC */ - } else if (rule.getAction().getTextBackground().empty() && rule.getAction().getTextColor().empty()) { - continue; /* do not try to highlight text, if no highlight color is specified */ - } - const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick); - foreach(const boost::regex& regex, keywordRegex) { - ChatWindow::ChatMessage newMessage; - foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { - boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; - if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { - try { - boost::match_results<std::string::const_iterator> match; - const std::string& text = textPart->text; - std::string::const_iterator start = text.begin(); - while (regex_search(start, text.end(), match, regex)) { - std::string::const_iterator matchStart = match[0].first; - std::string::const_iterator matchEnd = match[0].second; - if (start != matchStart) { - /* If we're skipping over plain text since the previous emoticon, record it as plain text */ - newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); - } - boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>(); - highlightPart->text = match.str(); - highlightPart->foregroundColor = rule.getAction().getTextColor(); - highlightPart->backgroundColor = rule.getAction().getTextBackground(); - newMessage.append(highlightPart); - start = matchEnd; - } - if (start != text.end()) { - /* If there's plain text after the last emoticon, record it */ - newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); - } - } - catch (std::runtime_error) { - /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ - newMessage.append(part); - } - } else { - newMessage.append(part); - } - } - parsedMessage = newMessage; - } - } - - return parsedMessage; - } + ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) + : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { + } + + typedef std::pair<std::string, std::string> StringPair; + + ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) { + ChatWindow::ChatMessage parsedMessage; + std::string remaining = body; + if (boost::starts_with(body, "/me ")) { + remaining = String::getSplittedAtFirst(body, ' ').second; + parsedMessage.setIsMeCommand(true); + } + + /* Parse one, URLs */ + while (!remaining.empty()) { + bool found = false; + std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining); + remaining = ""; + for (size_t i = 0; i < links.first.size(); i++) { + const std::string& part = links.first[i]; + if (found) { + // Must be on the last part, then + remaining = part; + } + else { + if (i == links.second) { + found = true; + parsedMessage.append(std::make_shared<ChatWindow::ChatURIMessagePart>(part)); + } + else { + parsedMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(part)); + } + } + } + } + + /* do emoticon substitution */ + parsedMessage = emoticonHighlight(parsedMessage); + + if (!senderIsSelf) { /* do not highlight our own messsages */ + /* do word-based color highlighting */ + parsedMessage = splitHighlight(parsedMessage, nick); + } + + return parsedMessage; + } + + ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) { + ChatWindow::ChatMessage parsedMessage = message; + + std::string regexString; + /* Parse two, emoticons */ + for (StringPair emoticon : emoticons_) { + /* Construct a regexp that finds an instance of any of the emoticons inside a group + * at the start or end of the line, or beside whitespace. + */ + regexString += regexString.empty() ? "" : "|"; + std::string escaped = "(" + Regex::escape(emoticon.first) + ")"; + regexString += "^" + escaped + "|"; + regexString += escaped + "$|"; + regexString += "\\s" + escaped + "|"; + regexString += escaped + "\\s"; + + } + if (!regexString.empty()) { + regexString += ""; + boost::regex emoticonRegex(regexString); + + ChatWindow::ChatMessage newMessage; + for (std::shared_ptr<ChatWindow::ChatMessagePart> part : parsedMessage.getParts()) { + std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + try { + boost::match_results<std::string::const_iterator> match; + const std::string& text = textPart->text; + std::string::const_iterator start = text.begin(); + while (regex_search(start, text.end(), match, emoticonRegex)) { + int matchIndex = 0; + for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) { + if (match[matchIndex].length() > 0) { + //This is the matching subgroup + break; + } + } + std::string::const_iterator matchStart = match[matchIndex].first; + std::string::const_iterator matchEnd = match[matchIndex].second; + if (start != matchStart) { + /* If we're skipping over plain text since the previous emoticon, record it as plain text */ + newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); + } + std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = std::make_shared<ChatWindow::ChatEmoticonMessagePart>(); + std::string matchString = match[matchIndex].str(); + std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString); + assert (emoticonIterator != emoticons_.end()); + const StringPair& emoticon = *emoticonIterator; + emoticonPart->imagePath = emoticon.second; + emoticonPart->alternativeText = emoticon.first; + newMessage.append(emoticonPart); + start = matchEnd; + } + if (start != text.end()) { + /* If there's plain text after the last emoticon, record it */ + newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); + } + + } + catch (std::runtime_error) { + /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ + newMessage.append(part); + } + } + else { + newMessage.append(part); + } + } + parsedMessage.setParts(newMessage.getParts()); + } + return parsedMessage; + } + + ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick) { + ChatWindow::ChatMessage parsedMessage = message; + + for (size_t i = 0; i < highlightRules_->getSize(); ++i) { + const HighlightRule& rule = highlightRules_->getRule(i); + if (rule.getMatchMUC() && !mucMode_) { + continue; /* this rule only applies to MUC's, and this is a CHAT */ + } else if (rule.getMatchChat() && mucMode_) { + continue; /* this rule only applies to CHAT's, and this is a MUC */ + } else if (rule.getAction().getTextBackground().empty() && rule.getAction().getTextColor().empty()) { + continue; /* do not try to highlight text, if no highlight color is specified */ + } + const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick); + for (const boost::regex& regex : keywordRegex) { + ChatWindow::ChatMessage newMessage; + for (std::shared_ptr<ChatWindow::ChatMessagePart> part : parsedMessage.getParts()) { + std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + try { + boost::match_results<std::string::const_iterator> match; + const std::string& text = textPart->text; + std::string::const_iterator start = text.begin(); + while (regex_search(start, text.end(), match, regex)) { + std::string::const_iterator matchStart = match[0].first; + std::string::const_iterator matchEnd = match[0].second; + if (start != matchStart) { + /* If we're skipping over plain text since the previous emoticon, record it as plain text */ + newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); + } + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = std::make_shared<ChatWindow::ChatHighlightingMessagePart>(); + highlightPart->text = match.str(); + highlightPart->action = rule.getAction(); + newMessage.append(highlightPart); + start = matchEnd; + } + if (start != text.end()) { + /* If there's plain text after the last emoticon, record it */ + newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); + } + } + catch (std::runtime_error) { + /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ + newMessage.append(part); + } + } else { + newMessage.append(part); + } + } + parsedMessage.setParts(newMessage.getParts()); + } + } + + return parsedMessage; + } } diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h index e56d21b..4bed669 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.h +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -12,15 +12,15 @@ namespace Swift { - class ChatMessageParser { - public: - ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false); - ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false); - private: - ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); - ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick); - std::map<std::string, std::string> emoticons_; - HighlightRulesListPtr highlightRules_; - bool mucMode_; - }; + class ChatMessageParser { + public: + ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false); + ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false); + private: + ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); + ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick); + std::map<std::string, std::string> emoticons_; + HighlightRulesListPtr highlightRules_; + bool mucMode_; + }; } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 49caee4..f55df1e 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -6,6 +6,8 @@ #include <Swift/Controllers/Chat/ChatsManager.h> +#include <memory> + #include <boost/algorithm/string.hpp> #include <boost/archive/text_iarchive.hpp> #include <boost/archive/text_oarchive.hpp> @@ -15,18 +17,20 @@ #include <boost/serialization/split_free.hpp> #include <boost/serialization/string.hpp> #include <boost/serialization/vector.hpp> -#include <boost/smart_ptr/make_shared.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Log.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/DiscoServiceWalker.h> +#include <Swiften/Disco/FeatureOracle.h> +#include <Swiften/Elements/CarbonsReceived.h> +#include <Swiften/Elements/CarbonsSent.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Forwarded.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/MUC/MUCBookmarkManager.h> @@ -56,43 +60,43 @@ #include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> #include <Swift/Controllers/WhiteboardManager.h> #include <Swift/Controllers/XMPPEvents/EventController.h> -#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h> BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 1) namespace boost { namespace serialization { - template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) { - std::string jidStr = jid.toString(); - ar << jidStr; - } - - template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) { - std::string stringJID; - ar >> stringJID; - jid = Swift::JID(stringJID); - } - - template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){ - split_free(ar, t, file_version); - } - - template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) { - ar & chat.jid; - ar & chat.chatName; - ar & chat.activity; - ar & chat.isMUC; - ar & chat.nick; - ar & chat.impromptuJIDs; - if (version > 0) { - ar & chat.password; - } - } + template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) { + std::string jidStr = jid.toString(); + ar << jidStr; + } + + template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) { + std::string stringJID; + ar >> stringJID; + jid = Swift::JID(stringJID); + } + + template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){ + split_free(ar, t, file_version); + } + + template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) { + ar & chat.jid; + ar & chat.chatName; + ar & chat.activity; + ar & chat.isMUC; + ar & chat.nick; + ar & chat.impromptuJIDs; + if (version > 0) { + ar & chat.password; + } + } } } @@ -104,950 +108,1008 @@ typedef std::pair<JID, MUCController*> JIDMUCControllerPair; #define RECENT_CHATS "recent_chats" ChatsManager::ChatsManager( - JID jid, StanzaChannel* stanzaChannel, - IQRouter* iqRouter, - EventController* eventController, - ChatWindowFactory* chatWindowFactory, - JoinMUCWindowFactory* joinMUCWindowFactory, - NickResolver* nickResolver, - PresenceOracle* presenceOracle, - PresenceSender* presenceSender, - UIEventStream* uiEventStream, - ChatListWindowFactory* chatListWindowFactory, - bool useDelayForLatency, - TimerFactory* timerFactory, - MUCRegistry* mucRegistry, - EntityCapsProvider* entityCapsProvider, - MUCManager* mucManager, - MUCSearchWindowFactory* mucSearchWindowFactory, - ProfileSettingsProvider* profileSettings, - FileTransferOverview* ftOverview, - XMPPRoster* roster, - bool eagleMode, - SettingsProvider* settings, - HistoryController* historyController, - WhiteboardManager* whiteboardManager, - HighlightManager* highlightManager, - ClientBlockListManager* clientBlockListManager, - const std::map<std::string, std::string>& emoticons, - VCardManager* vcardManager) : - jid_(jid), - joinMUCWindowFactory_(joinMUCWindowFactory), - useDelayForLatency_(useDelayForLatency), - mucRegistry_(mucRegistry), - entityCapsProvider_(entityCapsProvider), - mucManager(mucManager), - ftOverview_(ftOverview), - roster_(roster), - eagleMode_(eagleMode), - settings_(settings), - historyController_(historyController), - whiteboardManager_(whiteboardManager), - highlightManager_(highlightManager), - emoticons_(emoticons), - clientBlockListManager_(clientBlockListManager), - vcardManager_(vcardManager) { - timerFactory_ = timerFactory; - eventController_ = eventController; - stanzaChannel_ = stanzaChannel; - iqRouter_ = iqRouter; - chatWindowFactory_ = chatWindowFactory; - nickResolver_ = nickResolver; - presenceOracle_ = presenceOracle; - avatarManager_ = NULL; - serverDiscoInfo_ = boost::make_shared<DiscoInfo>(); - presenceSender_ = presenceSender; - uiEventStream_ = uiEventStream; - mucBookmarkManager_ = NULL; - profileSettings_ = profileSettings; - presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1)); - uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1)); - - chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_); - chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1)); - chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1)); - chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this)); - - joinMUCWindow_ = NULL; - mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_); - mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); - ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); - whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2)); - whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted)); - whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated)); - whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected)); - roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); - roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); - roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); - roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this)); - - settings_->onSettingChanged.connect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); - - userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); - - setupBookmarks(); - loadRecents(); - - autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_); + JID jid, StanzaChannel* stanzaChannel, + IQRouter* iqRouter, + EventController* eventController, + ChatWindowFactory* chatWindowFactory, + JoinMUCWindowFactory* joinMUCWindowFactory, + NickResolver* nickResolver, + PresenceOracle* presenceOracle, + PresenceSender* presenceSender, + UIEventStream* uiEventStream, + ChatListWindowFactory* chatListWindowFactory, + bool useDelayForLatency, + TimerFactory* timerFactory, + MUCRegistry* mucRegistry, + EntityCapsProvider* entityCapsProvider, + MUCManager* mucManager, + MUCSearchWindowFactory* mucSearchWindowFactory, + ProfileSettingsProvider* profileSettings, + FileTransferOverview* ftOverview, + XMPPRoster* roster, + bool eagleMode, + SettingsProvider* settings, + HistoryController* historyController, + WhiteboardManager* whiteboardManager, + HighlightManager* highlightManager, + ClientBlockListManager* clientBlockListManager, + const std::map<std::string, std::string>& emoticons, + VCardManager* vcardManager) : + jid_(jid), + joinMUCWindowFactory_(joinMUCWindowFactory), + useDelayForLatency_(useDelayForLatency), + mucRegistry_(mucRegistry), + entityCapsProvider_(entityCapsProvider), + mucManager(mucManager), + ftOverview_(ftOverview), + roster_(roster), + eagleMode_(eagleMode), + settings_(settings), + historyController_(historyController), + whiteboardManager_(whiteboardManager), + highlightManager_(highlightManager), + emoticons_(emoticons), + clientBlockListManager_(clientBlockListManager), + vcardManager_(vcardManager) { + timerFactory_ = timerFactory; + eventController_ = eventController; + stanzaChannel_ = stanzaChannel; + iqRouter_ = iqRouter; + chatWindowFactory_ = chatWindowFactory; + nickResolver_ = nickResolver; + presenceOracle_ = presenceOracle; + avatarManager_ = nullptr; + serverDiscoInfo_ = std::make_shared<DiscoInfo>(); + presenceSender_ = presenceSender; + uiEventStream_ = uiEventStream; + mucBookmarkManager_ = nullptr; + profileSettings_ = profileSettings; + presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1)); + uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1)); + + chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_); + chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1)); + chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1)); + chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this)); + + joinMUCWindow_ = nullptr; + mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_); + mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); + ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); + whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2)); + whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted)); + whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated)); + whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected)); + roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); + roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); + roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); + roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this)); + + settings_->onSettingChanged.connect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); + + userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); + + setupBookmarks(); + loadRecents(); + + autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_); } ChatsManager::~ChatsManager() { - settings_->onSettingChanged.disconnect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); - roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); - roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); - roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); - roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this)); - delete joinMUCWindow_; - foreach (JIDChatControllerPair controllerPair, chatControllers_) { - delete controllerPair.second; - } - foreach (JIDMUCControllerPair controllerPair, mucControllers_) { - delete controllerPair.second; - } - delete mucBookmarkManager_; - delete mucSearchController_; - delete autoAcceptMUCInviteDecider_; + settings_->onSettingChanged.disconnect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); + roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); + roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); + roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); + roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this)); + ftOverview_->onNewFileTransferController.disconnect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); + delete joinMUCWindow_; + for (JIDChatControllerPair controllerPair : chatControllers_) { + delete controllerPair.second; + } + for (JIDMUCControllerPair controllerPair : mucControllers_) { + delete controllerPair.second; + } + delete mucBookmarkManager_; + delete mucSearchController_; + delete autoAcceptMUCInviteDecider_; } void ChatsManager::saveRecents() { - std::stringstream serializeStream; - boost::archive::text_oarchive oa(serializeStream); - std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); - if (recentsLimited.size() > 25) { - recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end()); - } - if (eagleMode_) { - foreach(ChatListWindow::Chat& chat, recentsLimited) { - chat.activity = ""; - } - } - - class RemoveRecent { - public: - static bool ifPrivateMessage(const ChatListWindow::Chat& chat) { - return chat.isPrivateMessage; - } - }; - - recentsLimited.erase(std::remove_if(recentsLimited.begin(), recentsLimited.end(), RemoveRecent::ifPrivateMessage), recentsLimited.end()); - - oa << recentsLimited; - std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); - profileSettings_->storeString(RECENT_CHATS, serializedStr); + std::stringstream serializeStream; + boost::archive::text_oarchive oa(serializeStream); + std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); + if (recentsLimited.size() > 25) { + recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end()); + } + if (eagleMode_) { + for (ChatListWindow::Chat& chat : recentsLimited) { + chat.activity = ""; + } + } + + class RemoveRecent { + public: + static bool ifPrivateMessage(const ChatListWindow::Chat& chat) { + return chat.isPrivateMessage; + } + }; + + recentsLimited.erase(std::remove_if(recentsLimited.begin(), recentsLimited.end(), RemoveRecent::ifPrivateMessage), recentsLimited.end()); + + oa & recentsLimited; + std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); + profileSettings_->storeString(RECENT_CHATS, serializedStr); } void ChatsManager::handleClearRecentsRequested() { - recentChats_.clear(); - saveRecents(); - handleUnreadCountChanged(NULL); + recentChats_.clear(); + saveRecents(); + handleUnreadCountChanged(nullptr); } void ChatsManager::handleJIDAddedToRoster(const JID &jid) { - updatePresenceReceivingStateOnChatController(jid); + updatePresenceReceivingStateOnChatController(jid); } void ChatsManager::handleJIDRemovedFromRoster(const JID &jid) { - updatePresenceReceivingStateOnChatController(jid); + updatePresenceReceivingStateOnChatController(jid); } void ChatsManager::handleJIDUpdatedInRoster(const JID &jid) { - updatePresenceReceivingStateOnChatController(jid); + updatePresenceReceivingStateOnChatController(jid); } void ChatsManager::handleRosterCleared() { - /* Setting that all chat controllers aren't receiving presence anymore; - including MUC 1-to-1 chats due to the assumtion that this handler - is only called on log out. */ - foreach(JIDChatControllerPair pair, chatControllers_) { - pair.second->setContactIsReceivingPresence(false); - } + /* Setting that all chat controllers aren't receiving presence anymore; + including MUC 1-to-1 chats due to the assumtion that this handler + is only called on log out. */ + for (JIDChatControllerPair pair : chatControllers_) { + pair.second->setContactIsReceivingPresence(false); + } } void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) { - ChatController* controller = getChatControllerIfExists(jid); - if (controller) { - if (!mucRegistry_->isMUC(jid.toBare())) { - RosterItemPayload::Subscription subscription = roster_->getSubscriptionStateForJID(jid); - controller->setContactIsReceivingPresence(subscription == RosterItemPayload::From || subscription == RosterItemPayload::Both); - } else { - controller->setContactIsReceivingPresence(true); - } - } + ChatController* controller = getChatControllerIfExists(jid); + if (controller) { + if (!mucRegistry_->isMUC(jid.toBare())) { + RosterItemPayload::Subscription subscription = roster_->getSubscriptionStateForJID(jid); + controller->setContactIsReceivingPresence(subscription == RosterItemPayload::From || subscription == RosterItemPayload::Both); + } else { + controller->setContactIsReceivingPresence(true); + } + } } ChatListWindow::Chat ChatsManager::updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const { - ChatListWindow::Chat fixedChat = chat; - if (fixedChat.isMUC) { - if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) { - fixedChat.statusType = StatusShow::Online; - } - } else { - if (avatarManager_) { - fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid); - } - Presence::ref presence = presenceOracle_->getAccountPresence(fixedChat.jid.toBare()); - fixedChat.statusType = presence ? presence->getShow() : StatusShow::None; - } - return fixedChat; + ChatListWindow::Chat fixedChat = chat; + if (fixedChat.isMUC) { + if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) { + fixedChat.statusType = StatusShow::Online; + } + } else { + if (avatarManager_) { + fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid); + } + Presence::ref presence = presenceOracle_->getAccountPresence(fixedChat.jid.toBare()); + fixedChat.statusType = presence ? presence->getShow() : StatusShow::None; + } + return fixedChat; } void ChatsManager::loadRecents() { - std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS)); - if (recentsString.find("\t") != std::string::npos) { - // old format - std::vector<std::string> recents; - boost::split(recents, recentsString, boost::is_any_of("\n")); - int i = 0; - foreach (std::string recentString, recents) { - if (i++ > 30) { - break; - } - std::vector<std::string> recent; - boost::split(recent, recentString, boost::is_any_of("\t")); - if (recent.size() < 4) { - continue; - } - JID jid(recent[0]); - if (!jid.isValid()) { - continue; - } - std::string activity(recent[1]); - bool isMUC = recent[2] == "true"; - std::string nick(recent[3]); - StatusShow::Type type = StatusShow::None; - boost::filesystem::path path; - - ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, false, nick); - chat = updateChatStatusAndAvatarHelper(chat); - prependRecent(chat); - } - } else if (!recentsString.empty()){ - // boost searilaize based format - ByteArray debase64 = Base64::decode(recentsString); - std::vector<ChatListWindow::Chat> recentChats; - std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size())); - try { - boost::archive::text_iarchive ia(deserializeStream); - ia >> recentChats; - } catch (const boost::archive::archive_exception& e) { - SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl; - return; - } - - foreach(ChatListWindow::Chat chat, recentChats) { - chat.statusType = StatusShow::None; - chat = updateChatStatusAndAvatarHelper(chat); - prependRecent(chat); - } - } - handleUnreadCountChanged(NULL); + std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS)); + if (recentsString.find("\t") != std::string::npos) { + // old format + std::vector<std::string> recents; + boost::split(recents, recentsString, boost::is_any_of("\n")); + int i = 0; + for (std::string recentString : recents) { + if (i++ > 30) { + break; + } + std::vector<std::string> recent; + boost::split(recent, recentString, boost::is_any_of("\t")); + if (recent.size() < 4) { + continue; + } + JID jid(recent[0]); + if (!jid.isValid()) { + continue; + } + std::string activity(recent[1]); + bool isMUC = recent[2] == "true"; + std::string nick(recent[3]); + StatusShow::Type type = StatusShow::None; + boost::filesystem::path path; + + ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, false, nick); + chat = updateChatStatusAndAvatarHelper(chat); + prependRecent(chat); + } + } else if (!recentsString.empty()){ + // boost searilaize based format + ByteArray debase64 = Base64::decode(recentsString); + std::vector<ChatListWindow::Chat> recentChats; + std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size())); + try { + boost::archive::text_iarchive ia(deserializeStream); + ia >> recentChats; + } catch (const boost::archive::archive_exception& e) { + SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl; + return; + } + + for (auto chat : recentChats) { + chat.statusType = StatusShow::None; + chat = updateChatStatusAndAvatarHelper(chat); + prependRecent(chat); + } + } + handleUnreadCountChanged(nullptr); } void ChatsManager::setupBookmarks() { - if (!mucBookmarkManager_) { - mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_); - mucBookmarkManager_->onBookmarksReady.connect(boost::bind(&ChatsManager::handleBookmarksReady, this)); - mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&ChatsManager::handleMUCBookmarkAdded, this, _1)); - mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&ChatsManager::handleMUCBookmarkRemoved, this, _1)); - - if (chatListWindow_) { - chatListWindow_->setBookmarksEnabled(false); - chatListWindow_->clearBookmarks(); - } - } + if (!mucBookmarkManager_) { + mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_); + mucBookmarkManager_->onBookmarksReady.connect(boost::bind(&ChatsManager::handleBookmarksReady, this)); + mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&ChatsManager::handleMUCBookmarkAdded, this, _1)); + mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&ChatsManager::handleMUCBookmarkRemoved, this, _1)); + + if (chatListWindow_) { + chatListWindow_->setBookmarksEnabled(false); + chatListWindow_->clearBookmarks(); + } + } } void ChatsManager::handleBookmarksReady() { - if (chatListWindow_) { - chatListWindow_->setBookmarksEnabled(true); - } + if (chatListWindow_) { + chatListWindow_->setBookmarksEnabled(true); + } } void ChatsManager::handleMUCBookmarkAdded(const MUCBookmark& bookmark) { - std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom()); - if (it == mucControllers_.end() && bookmark.getAutojoin()) { - handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false, false ); - } - chatListWindow_->addMUCBookmark(bookmark); + std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom()); + if (it == mucControllers_.end() && bookmark.getAutojoin()) { + handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false, false ); + } + chatListWindow_->addMUCBookmark(bookmark); } void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) { - chatListWindow_->removeMUCBookmark(bookmark); + chatListWindow_->removeMUCBookmark(bookmark); } ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage) { - int unreadCount = 0; - if (mucRegistry_->isMUC(jid)) { - MUCController* controller = mucControllers_[jid.toBare()]; - StatusShow::Type type = StatusShow::None; - std::string nick = ""; - std::string password = ""; - if (controller) { - unreadCount = controller->getUnreadCount(); - if (controller->isJoined()) { - type = StatusShow::Online; - } - nick = controller->getNick(); - - if (controller->getPassword()) { - password = *controller->getPassword(); - } - - if (controller->isImpromptu()) { - ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password); - std::map<std::string, JID> participants = controller->getParticipantJIDs(); - chat.impromptuJIDs = participants; - return chat; - } - } - return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password); - } else { - ChatController* controller = getChatControllerIfExists(jid, false); - if (controller) { - unreadCount = controller->getUnreadCount(); - } - JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare(); - Presence::ref presence = presenceOracle_->getAccountPresence(bareishJID); - StatusShow::Type type = presence ? presence->getShow() : StatusShow::None; - boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path(); - return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false, privateMessage); - } + int unreadCount = 0; + if (mucRegistry_->isMUC(jid)) { + MUCController* controller = mucControllers_[jid.toBare()]; + StatusShow::Type type = StatusShow::None; + std::string nick = ""; + std::string password = ""; + if (controller) { + unreadCount = controller->getUnreadCount(); + if (controller->isJoined()) { + type = StatusShow::Online; + } + nick = controller->getNick(); + + if (controller->getPassword()) { + password = *controller->getPassword(); + } + + if (controller->isImpromptu()) { + ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password); + std::map<std::string, JID> participants = controller->getParticipantJIDs(); + chat.impromptuJIDs = participants; + return chat; + } + } + return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password); + } else { + ChatController* controller = getChatControllerIfExists(jid, false); + if (controller) { + unreadCount = controller->getUnreadCount(); + } + JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare(); + Presence::ref presence = presenceOracle_->getAccountPresence(bareishJID); + StatusShow::Type type = presence ? presence->getShow() : StatusShow::None; + boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path(); + return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false, privateMessage); + } } void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) { - const bool privateMessage = mucRegistry_->isMUC(jid.toBare()) && !isMUC; - ChatListWindow::Chat chat = createChatListChatItem(jid, activity, privateMessage); - /* FIXME: handle nick changes */ - appendRecent(chat); - handleUnreadCountChanged(NULL); - saveRecents(); + const bool privateMessage = mucRegistry_->isMUC(jid.toBare()) && !isMUC; + ChatListWindow::Chat chat = createChatListChatItem(jid, activity, privateMessage); + /* FIXME: handle nick changes */ + appendRecent(chat); + handleUnreadCountChanged(nullptr); + saveRecents(); } void ChatsManager::handleChatClosed(const JID& /*jid*/) { - cleanupPrivateMessageRecents(); - chatListWindow_->setRecents(recentChats_); + cleanupPrivateMessageRecents(); + chatListWindow_->setRecents(recentChats_); } void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) { - int unreadTotal = 0; - bool controllerIsMUC = dynamic_cast<MUCController*>(controller); - bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare()); - foreach (ChatListWindow::Chat& chatItem, recentChats_) { - bool match = false; - if (controller) { - /* Matching MUC item */ - match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare(); - /* Matching PM */ - match |= isPM && chatItem.jid == controller->getToJID(); - /* Matching non-PM */ - match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare(); - } - if (match) { - chatItem.setUnreadCount(controller->getUnreadCount()); - } - unreadTotal += chatItem.unreadCount; - } - chatListWindow_->setRecents(recentChats_); - chatListWindow_->setUnreadCount(unreadTotal); + int unreadTotal = 0; + bool controllerIsMUC = dynamic_cast<MUCController*>(controller); + bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare()); + for (ChatListWindow::Chat& chatItem : recentChats_) { + bool match = false; + if (controller) { + /* Matching MUC item */ + match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare(); + /* Matching PM */ + match |= isPM && chatItem.jid == controller->getToJID(); + /* Matching non-PM */ + match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare(); + } + if (match) { + chatItem.setUnreadCount(controller->getUnreadCount()); + } + unreadTotal += chatItem.unreadCount; + } + chatListWindow_->setRecents(recentChats_); + chatListWindow_->setUnreadCount(unreadTotal); } boost::optional<ChatListWindow::Chat> ChatsManager::removeExistingChat(const ChatListWindow::Chat& chat) { - std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat); - if (result != recentChats_.end()) { - ChatListWindow::Chat existingChat = *result; - recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); - return boost::optional<ChatListWindow::Chat>(existingChat); - } else { - return boost::optional<ChatListWindow::Chat>(); - } + std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat); + if (result != recentChats_.end()) { + ChatListWindow::Chat existingChat = *result; + recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); + return boost::optional<ChatListWindow::Chat>(existingChat); + } else { + return boost::optional<ChatListWindow::Chat>(); + } } void ChatsManager::cleanupPrivateMessageRecents() { - /* if we leave a MUC and close a PM, remove it's recent chat entry */ - const std::list<ChatListWindow::Chat> chats = recentChats_; - foreach (const ChatListWindow::Chat& chat, chats) { - if (chat.isPrivateMessage) { - typedef std::map<JID, MUCController*> ControllerMap; - ControllerMap::iterator muc = mucControllers_.find(chat.jid.toBare()); - if (muc == mucControllers_.end() || !muc->second->isJoined()) { - ChatController* chatController = getChatControllerIfExists(chat.jid); - if (!chatController || !chatController->hasOpenWindow()) { - removeExistingChat(chat); - break; - } - } - } - } + /* if we leave a MUC and close a PM, remove it's recent chat entry */ + const std::list<ChatListWindow::Chat> chats = recentChats_; + for (const ChatListWindow::Chat& chat : chats) { + if (chat.isPrivateMessage) { + typedef std::map<JID, MUCController*> ControllerMap; + ControllerMap::iterator muc = mucControllers_.find(chat.jid.toBare()); + if (muc == mucControllers_.end() || !muc->second->isJoined()) { + ChatController* chatController = getChatControllerIfExists(chat.jid); + if (!chatController || !chatController->hasOpenWindow()) { + removeExistingChat(chat); + break; + } + } + } + } } void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) { - boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); - ChatListWindow::Chat mergedChat = chat; - if (oldChat && !oldChat->impromptuJIDs.empty()) { - mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); - } - recentChats_.push_front(mergedChat); + boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); + ChatListWindow::Chat mergedChat = chat; + if (oldChat && !oldChat->impromptuJIDs.empty()) { + mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); + } + recentChats_.push_front(mergedChat); } void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) { - boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); - ChatListWindow::Chat mergedChat = chat; - if (oldChat && !oldChat->impromptuJIDs.empty()) { - mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); - } - recentChats_.push_back(mergedChat); + boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); + ChatListWindow::Chat mergedChat = chat; + if (oldChat && !oldChat->impromptuJIDs.empty()) { + mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); + } + recentChats_.push_back(mergedChat); } void ChatsManager::handleUserLeftMUC(MUCController* mucController) { - std::map<JID, MUCController*>::iterator it; - for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) { - if ((*it).second == mucController) { - foreach (ChatListWindow::Chat& chat, recentChats_) { - if (chat.isMUC && chat.jid == (*it).first) { - chat.statusType = StatusShow::None; - } - } - mucControllers_.erase(it); - delete mucController; - break; - } - } - cleanupPrivateMessageRecents(); - chatListWindow_->setRecents(recentChats_); + std::map<JID, MUCController*>::iterator it; + for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) { + if ((*it).second == mucController) { + for (ChatListWindow::Chat& chat : recentChats_) { + if (chat.isMUC && chat.jid == (*it).first) { + chat.statusType = StatusShow::None; + } + } + mucControllers_.erase(it); + delete mucController; + break; + } + } + cleanupPrivateMessageRecents(); + chatListWindow_->setRecents(recentChats_); } void ChatsManager::handleSettingChanged(const std::string& settingPath) { - if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) { - userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); - return; - } + if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) { + userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); + return; + } } void ChatsManager::finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID) { - // send impromptu invites for the new MUC - std::vector<JID> missingJIDsToInvite = jidsToInvite; - - typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; - std::map<std::string, MUCOccupant> occupants = muc->getOccupants(); - foreach(StringMUCOccupantPair occupant, occupants) { - boost::optional<JID> realJID = occupant.second.getRealJID(); - if (realJID) { - missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end()); - } - } - - if (reuseChatJID) { - muc->invitePerson(reuseChatJID.get(), reason, true, true); - } - foreach(const JID& jid, missingJIDsToInvite) { - muc->invitePerson(jid, reason, true); - } + // send impromptu invites for the new MUC + std::vector<JID> missingJIDsToInvite = jidsToInvite; + + typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; + std::map<std::string, MUCOccupant> occupants = muc->getOccupants(); + for (StringMUCOccupantPair occupant : occupants) { + boost::optional<JID> realJID = occupant.second.getRealJID(); + if (realJID) { + missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end()); + } + } + + if (reuseChatJID) { + muc->invitePerson(reuseChatJID.get(), reason, true, true); + } + for (const JID& jid : missingJIDsToInvite) { + muc->invitePerson(jid, reason, true); + } } -void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { - boost::shared_ptr<RequestChatUIEvent> chatEvent = boost::dynamic_pointer_cast<RequestChatUIEvent>(event); - if (chatEvent) { - handleChatRequest(chatEvent->getContact()); - return; - } - boost::shared_ptr<RemoveMUCBookmarkUIEvent> removeMUCBookmarkEvent = boost::dynamic_pointer_cast<RemoveMUCBookmarkUIEvent>(event); - if (removeMUCBookmarkEvent) { - mucBookmarkManager_->removeBookmark(removeMUCBookmarkEvent->getBookmark()); - return; - } - boost::shared_ptr<AddMUCBookmarkUIEvent> addMUCBookmarkEvent = boost::dynamic_pointer_cast<AddMUCBookmarkUIEvent>(event); - if (addMUCBookmarkEvent) { - mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark()); - return; - } - - boost::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = boost::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event); - if (createImpromptuMUCEvent) { - assert(!localMUCServiceJID_.toString().empty()); - // create new muc - JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID(); - - // join muc - MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true); - mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>())); - mucControllers_[roomJID]->activateChatWindow(); - } - - boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event); - if (editMUCBookmarkEvent) { - mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark()); - } - else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) { - handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu()); - mucControllers_[joinEvent->getJID()]->activateChatWindow(); - } - else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) { - if (!joinMUCWindow_) { - joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_); - joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this)); - } - joinMUCWindow_->setMUC(joinEvent->getRoom()); - joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_)); - joinMUCWindow_->show(); - } +void ChatsManager::handleUIEvent(std::shared_ptr<UIEvent> event) { + std::shared_ptr<RequestChatUIEvent> chatEvent = std::dynamic_pointer_cast<RequestChatUIEvent>(event); + if (chatEvent) { + handleChatRequest(chatEvent->getContact()); + return; + } + std::shared_ptr<RemoveMUCBookmarkUIEvent> removeMUCBookmarkEvent = std::dynamic_pointer_cast<RemoveMUCBookmarkUIEvent>(event); + if (removeMUCBookmarkEvent) { + mucBookmarkManager_->removeBookmark(removeMUCBookmarkEvent->getBookmark()); + return; + } + std::shared_ptr<AddMUCBookmarkUIEvent> addMUCBookmarkEvent = std::dynamic_pointer_cast<AddMUCBookmarkUIEvent>(event); + if (addMUCBookmarkEvent) { + mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark()); + return; + } + std::shared_ptr<SendFileUIEvent> sendFileEvent = std::dynamic_pointer_cast<SendFileUIEvent>(event); + if (sendFileEvent) { + JID fileReceiver = sendFileEvent->getJID(); + if (fileReceiver.isBare()) { + // See if there is a chat controller for a conversation with a bound + // full JID. Check if this JID supports file transfer and use it instead + // of the bare JID. + ChatController* controller = getChatControllerIfExists(fileReceiver, false); + if (controller) { + JID controllerJID = controller->getToJID(); + if (!controllerJID.isBare() && (FeatureOracle(entityCapsProvider_, presenceOracle_).isFileTransferSupported(controllerJID) == Yes)) { + fileReceiver = controllerJID; + } + } + } + ftOverview_->sendFile(fileReceiver, sendFileEvent->getFilename()); + return; + } + + std::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = std::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event); + if (createImpromptuMUCEvent) { + assert(!localMUCServiceJID_.toString().empty()); + // The room JID is random for new impromptu rooms, or a predefined JID for impromptu rooms resumed from the 'Recent chats' list. + JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID(); + + // join muc + MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true); + mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>())); + mucControllers_[roomJID]->activateChatWindow(); + } + + std::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = std::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event); + if (editMUCBookmarkEvent) { + mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark()); + } + else if (JoinMUCUIEvent::ref joinEvent = std::dynamic_pointer_cast<JoinMUCUIEvent>(event)) { + handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu()); + mucControllers_[joinEvent->getJID()]->activateChatWindow(); + } + else if (std::shared_ptr<RequestJoinMUCUIEvent> joinEvent = std::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) { + if (!joinMUCWindow_) { + joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_); + joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this)); + } + joinMUCWindow_->setMUC(joinEvent->getRoom()); + joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_)); + joinMUCWindow_->show(); + } } void ChatsManager::markAllRecentsOffline() { - foreach (ChatListWindow::Chat& chat, recentChats_) { - chat.setStatusType(StatusShow::None); - } + for (ChatListWindow::Chat& chat : recentChats_) { + chat.setStatusType(StatusShow::None); + } - chatListWindow_->setRecents(recentChats_); + chatListWindow_->setRecents(recentChats_); } void ChatsManager::handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason) { - JID reuseChatInvite = chatController->getToJID(); - chatControllers_.erase(chatController->getToJID()); - delete chatController; + JID reuseChatInvite = chatController->getToJID(); + chatControllers_.erase(chatController->getToJID()); + delete chatController; - // join new impromptu muc - assert(!localMUCServiceJID_.toString().empty()); + // join new impromptu muc + assert(!localMUCServiceJID_.toString().empty()); - // create new muc - JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_); + // create new muc + JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_); - // join muc - MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow); - mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite))); + // join muc + MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow); + mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite))); } /** * If a resource goes offline, release bound chatdialog to that resource. */ -void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) { - if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return; - - foreach (ChatListWindow::Chat& chat, recentChats_) { - if (newPresence->getFrom().toBare() == chat.jid.toBare() && !chat.isMUC) { - Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare()); - chat.setStatusType(presence ? presence->getShow() : StatusShow::None); - chatListWindow_->setRecents(recentChats_); - break; - } - } - - //if (newPresence->getType() != Presence::Unavailable) return; - JID fullJID(newPresence->getFrom()); - std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID); - if (it == chatControllers_.end()) return; - JID bareJID(fullJID.toBare()); - //It doesn't make sense to have two unbound dialogs. - if (chatControllers_.find(bareJID) != chatControllers_.end()) return; - rebindControllerJID(fullJID, bareJID); +void ChatsManager::handlePresenceChange(std::shared_ptr<Presence> newPresence) { + if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return; + + for (ChatListWindow::Chat& chat : recentChats_) { + if (newPresence->getFrom().toBare() == chat.jid.toBare() && !chat.isMUC) { + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare()); + chat.setStatusType(presence ? presence->getShow() : StatusShow::None); + chatListWindow_->setRecents(recentChats_); + break; + } + } + + //if (newPresence->getType() != Presence::Unavailable) return; + JID fullJID(newPresence->getFrom()); + std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID); + if (it == chatControllers_.end()) return; + JID bareJID(fullJID.toBare()); + //It doesn't make sense to have two unbound dialogs. + if (chatControllers_.find(bareJID) != chatControllers_.end()) return; + rebindControllerJID(fullJID, bareJID); } void ChatsManager::setAvatarManager(AvatarManager* avatarManager) { - if (avatarManager_) { - avatarManager_->onAvatarChanged.disconnect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1)); - } - avatarManager_ = avatarManager; - foreach (ChatListWindow::Chat& chat, recentChats_) { - if (!chat.isMUC) { - chat.setAvatarPath(avatarManager_->getAvatarPath(chat.jid)); - } - } - avatarManager_->onAvatarChanged.connect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1)); + if (avatarManager_) { + avatarManager_->onAvatarChanged.disconnect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1)); + } + avatarManager_ = avatarManager; + for (ChatListWindow::Chat& chat : recentChats_) { + if (!chat.isMUC) { + chat.setAvatarPath(avatarManager_->getAvatarPath(chat.jid)); + } + } + avatarManager_->onAvatarChanged.connect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1)); } void ChatsManager::handleAvatarChanged(const JID& jid) { - foreach (ChatListWindow::Chat& chat, recentChats_) { - if (!chat.isMUC && jid.toBare() == chat.jid.toBare()) { - chat.setAvatarPath(avatarManager_->getAvatarPath(jid)); - break; - } - } + for (ChatListWindow::Chat& chat : recentChats_) { + if (!chat.isMUC && jid.toBare() == chat.jid.toBare()) { + chat.setAvatarPath(avatarManager_->getAvatarPath(jid)); + break; + } + } } -void ChatsManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) { - serverDiscoInfo_ = info; - foreach (JIDChatControllerPair pair, chatControllers_) { - pair.second->setAvailableServerFeatures(info); - } - foreach (JIDMUCControllerPair pair, mucControllers_) { - pair.second->setAvailableServerFeatures(info); - } +void ChatsManager::setServerDiscoInfo(std::shared_ptr<DiscoInfo> info) { + serverDiscoInfo_ = info; + for (JIDChatControllerPair pair : chatControllers_) { + pair.second->setAvailableServerFeatures(info); + } + for (JIDMUCControllerPair pair : mucControllers_) { + pair.second->setAvailableServerFeatures(info); + } } /** * This is to be called on connect/disconnect. - */ + */ void ChatsManager::setOnline(bool enabled) { - foreach (JIDChatControllerPair controllerPair, chatControllers_) { - controllerPair.second->setOnline(enabled); - } - foreach (JIDMUCControllerPair controllerPair, mucControllers_) { - controllerPair.second->setOnline(enabled); - if (enabled) { - controllerPair.second->rejoin(); - } - } - if (!enabled) { - markAllRecentsOffline(); - } else { - setupBookmarks(); - localMUCServiceJID_ = JID(); - localMUCServiceFinderWalker_ = boost::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_); - localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2)); - localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); - localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); - localMUCServiceFinderWalker_->beginWalk(); - } - - if (chatListWindow_) { - chatListWindow_->setBookmarksEnabled(enabled); - } + for (JIDChatControllerPair controllerPair : chatControllers_) { + controllerPair.second->setOnline(enabled); + } + for (JIDMUCControllerPair controllerPair : mucControllers_) { + controllerPair.second->setOnline(enabled); + if (enabled) { + controllerPair.second->rejoin(); + } + } + if (!enabled) { + markAllRecentsOffline(); + } else { + setupBookmarks(); + localMUCServiceJID_ = JID(); + localMUCServiceFinderWalker_ = std::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_); + localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2)); + localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); + localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); + localMUCServiceFinderWalker_->beginWalk(); + } + + if (chatListWindow_) { + chatListWindow_->setBookmarksEnabled(enabled); + } } void ChatsManager::handleChatRequest(const std::string &contact) { - ChatController* controller = getChatControllerOrFindAnother(JID(contact)); - controller->activateChatWindow(); + ChatController* controller = getChatControllerOrFindAnother(JID(contact)); + controller->activateChatWindow(); } ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) { - ChatController* controller = getChatControllerIfExists(contact); - if (!controller && !mucRegistry_->isMUC(contact.toBare())) { - foreach (JIDChatControllerPair pair, chatControllers_) { - if (pair.first.toBare() == contact.toBare()) { - controller = pair.second; - break; - } - } - } - return controller ? controller : createNewChatController(contact); + ChatController* controller = getChatControllerIfExists(contact); + if (!controller && !mucRegistry_->isMUC(contact.toBare())) { + for (JIDChatControllerPair pair : chatControllers_) { + if (pair.first.toBare() == contact.toBare()) { + controller = pair.second; + break; + } + } + } + return controller ? controller : createNewChatController(contact); } ChatController* ChatsManager::createNewChatController(const JID& contact) { - assert(chatControllers_.find(contact) == chatControllers_.end()); - boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */ - ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_); - chatControllers_[contact] = controller; - controller->setAvailableServerFeatures(serverDiscoInfo_); - controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); - controller->onWindowClosed.connect(boost::bind(&ChatsManager::handleChatClosed, this, contact)); - controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); - controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3)); - updatePresenceReceivingStateOnChatController(contact); - controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty()); - return controller; + assert(chatControllers_.find(contact) == chatControllers_.end()); + std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */ + ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_); + chatControllers_[contact] = controller; + controller->setAvailableServerFeatures(serverDiscoInfo_); + controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); + controller->onWindowClosed.connect(boost::bind(&ChatsManager::handleChatClosed, this, contact)); + controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); + controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3)); + updatePresenceReceivingStateOnChatController(contact); + controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty()); + return controller; } ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) { - ChatController* controller = getChatControllerIfExists(contact); - return controller ? controller : createNewChatController(contact); + ChatController* controller = getChatControllerIfExists(contact); + return controller ? controller : createNewChatController(contact); } ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) { - if (chatControllers_.find(contact) == chatControllers_.end()) { - if (mucRegistry_->isMUC(contact.toBare())) { - return NULL; - } - //Need to look for an unbound window to bind first - JID bare(contact.toBare()); - if (chatControllers_.find(bare) != chatControllers_.end()) { - if (rebindIfNeeded) { - rebindControllerJID(bare, contact); - } - else { - return chatControllers_[bare]; - } - } else { - foreach (JIDChatControllerPair pair, chatControllers_) { - if (pair.first.toBare() == contact.toBare()) { - if (rebindIfNeeded) { - rebindControllerJID(pair.first, contact); - return chatControllers_[contact]; - } else { - return pair.second; - } - } - } - return NULL; - } - } - return chatControllers_[contact]; + if (chatControllers_.find(contact) == chatControllers_.end()) { + if (mucRegistry_->isMUC(contact.toBare())) { + return nullptr; + } + //Need to look for an unbound window to bind first + JID bare(contact.toBare()); + if (chatControllers_.find(bare) != chatControllers_.end()) { + if (rebindIfNeeded) { + rebindControllerJID(bare, contact); + } + else { + return chatControllers_[bare]; + } + } else { + for (JIDChatControllerPair pair : chatControllers_) { + if (pair.first.toBare() == contact.toBare()) { + if (rebindIfNeeded) { + rebindControllerJID(pair.first, contact); + return chatControllers_[contact]; + } else { + return pair.second; + } + } + } + return nullptr; + } + } + return chatControllers_[contact]; } void ChatsManager::rebindControllerJID(const JID& from, const JID& to) { - chatControllers_[to] = chatControllers_[from]; - chatControllers_.erase(from); - chatControllers_[to]->setToJID(to); + chatControllers_[to] = chatControllers_[from]; + chatControllers_.erase(from); + chatControllers_[to]->setToJID(to); } MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow) { - MUC::ref muc; - if (addAutoJoin) { - MUCBookmark bookmark(mucJID, mucJID.getNode()); - bookmark.setAutojoin(true); - if (nickMaybe) { - bookmark.setNick(*nickMaybe); - } - if (password) { - bookmark.setPassword(*password); - } - mucBookmarkManager_->addBookmark(bookmark); - } - - std::map<JID, MUCController*>::iterator it = mucControllers_.find(mucJID); - if (it != mucControllers_.end()) { - if (stanzaChannel_->isAvailable()) { - it->second->rejoin(); - } - } else { - std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_); - muc = mucManager->createMUC(mucJID); - if (createAsReservedIfNew) { - muc->setCreateAsReservedIfNew(); - } - if (isImpromptu) { - muc->setCreateAsReservedIfNew(); - } - - MUCController* controller = NULL; - SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL; - if (reuseChatwindow) { - chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow); - } - boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */ - controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_, mucBookmarkManager_); - if (chatWindowFactoryAdapter) { - /* The adapters are only passed to chat windows, which are deleted in their - * controllers' dtor, which are deleted in ChatManager's dtor. The adapters - * are also deleted there.*/ - chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter; - } - - mucControllers_[mucJID] = controller; - controller->setAvailableServerFeatures(serverDiscoInfo_); - controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller)); - controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true)); - controller->onUserNicknameChanged.connect(boost::bind(&ChatsManager::handleUserNicknameChanged, this, controller, _1, _2)); - controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true)); - controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); - if (!stanzaChannel_->isAvailable()) { - /* When online, the MUC is added to the registry in MUCImpl::internalJoin. This method is not - * called when Swift is offline, so we add it here as only MUCs in the registry are rejoined - * when going back online. - */ - mucRegistry_->addMUC(mucJID.toBare()); - } - handleChatActivity(mucJID.toBare(), "", true); - } - - mucControllers_[mucJID]->showChatWindow(); - return muc; + MUC::ref muc; + if (addAutoJoin) { + MUCBookmark bookmark(mucJID, mucJID.getNode()); + bookmark.setAutojoin(true); + if (nickMaybe) { + bookmark.setNick(*nickMaybe); + } + if (password) { + bookmark.setPassword(*password); + } + mucBookmarkManager_->addBookmark(bookmark); + } + + std::map<JID, MUCController*>::iterator it = mucControllers_.find(mucJID); + if (it != mucControllers_.end()) { + if (stanzaChannel_->isAvailable()) { + it->second->rejoin(); + } + } else { + std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_); + muc = mucManager->createMUC(mucJID); + if (createAsReservedIfNew) { + muc->setCreateAsReservedIfNew(); + } + if (isImpromptu) { + muc->setCreateAsReservedIfNew(); + } + + MUCController* controller = nullptr; + SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = nullptr; + if (reuseChatwindow) { + chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow); + } + std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */ + controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_, mucBookmarkManager_); + if (chatWindowFactoryAdapter) { + /* The adapters are only passed to chat windows, which are deleted in their + * controllers' dtor, which are deleted in ChatManager's dtor. The adapters + * are also deleted there.*/ + chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter; + } + + mucControllers_[mucJID] = controller; + controller->setAvailableServerFeatures(serverDiscoInfo_); + controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller)); + controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true)); + controller->onUserNicknameChanged.connect(boost::bind(&ChatsManager::handleUserNicknameChanged, this, controller, _1, _2)); + controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true)); + controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); + if (!stanzaChannel_->isAvailable()) { + /* When online, the MUC is added to the registry in MUCImpl::internalJoin. This method is not + * called when Swift is offline, so we add it here as only MUCs in the registry are rejoined + * when going back online. + */ + mucRegistry_->addMUC(mucJID.toBare()); + } + handleChatActivity(mucJID.toBare(), "", true); + } + + mucControllers_[mucJID]->showChatWindow(); + return muc; } void ChatsManager::handleSearchMUCRequest() { - mucSearchController_->openSearchWindow(); + mucSearchController_->openSearchWindow(); } void ChatsManager::handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname) { - JID oldMUCChatJID = mucController->getToJID().withResource(oldNickname); - JID newMUCChatJID = mucController->getToJID().withResource(newNickname); - - SWIFT_LOG(debug) << "nickname change in " << mucController->getToJID().toString() << " from " << oldNickname << " to " << newNickname << std::endl; - - // get current chat controller - ChatController *chatController = getChatControllerIfExists(oldMUCChatJID); - if (chatController) { - // adjust chat controller - chatController->setToJID(newMUCChatJID); - nickResolver_->onNickChanged(newMUCChatJID, oldNickname); - chatControllers_.erase(oldMUCChatJID); - chatControllers_[newMUCChatJID] = chatController; - - chatController->onActivity.disconnect(boost::bind(&ChatsManager::handleChatActivity, this, oldMUCChatJID, _1, false)); - chatController->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, newMUCChatJID, _1, false)); - } + JID oldMUCChatJID = mucController->getToJID().withResource(oldNickname); + JID newMUCChatJID = mucController->getToJID().withResource(newNickname); + + SWIFT_LOG(debug) << "nickname change in " << mucController->getToJID().toString() << " from " << oldNickname << " to " << newNickname << std::endl; + + // get current chat controller + ChatController *chatController = getChatControllerIfExists(oldMUCChatJID); + if (chatController) { + // adjust chat controller + chatController->setToJID(newMUCChatJID); + nickResolver_->onNickChanged(newMUCChatJID, oldNickname); + chatControllers_.erase(oldMUCChatJID); + chatControllers_[newMUCChatJID] = chatController; + + chatController->onActivity.disconnect(boost::bind(&ChatsManager::handleChatActivity, this, oldMUCChatJID, _1, false)); + chatController->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, newMUCChatJID, _1, false)); + } +} + +bool ChatsManager::messageCausesSessionBinding(std::shared_ptr<Message> message) { + bool causesRebind = false; + ChatState::ref chatState = message->getPayload<ChatState>(); + if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) { + causesRebind = true; + } + return causesRebind; } -void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { - JID jid = message->getFrom(); - boost::shared_ptr<MessageEvent> event(new MessageEvent(message)); - bool isInvite = !!message->getPayload<MUCInvitationPayload>(); - bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite()); - if (isMediatedInvite) { - jid = (*message->getPayload<MUCUserPayload>()->getInvite()).from; - } - if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !isMediatedInvite && !message->hasSubject()) { - return; - } - - // Try to deliver it to a MUC - if (message->getType() == Message::Groupchat || message->getType() == Message::Error /*|| (isInvite && message->getType() == Message::Normal)*/) { - std::map<JID, MUCController*>::iterator i = mucControllers_.find(jid.toBare()); - if (i != mucControllers_.end()) { - i->second->handleIncomingMessage(event); - return; - } - else if (message->getType() == Message::Groupchat) { - //FIXME: Error handling - groupchat messages from an unknown muc. - return; - } - } - - // check for impromptu invite to potentially auto-accept - MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); - if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { - if (invite->getIsContinuation()) { - // check for existing chat controller for the from JID - ChatController* controller = getChatControllerIfExists(jid); - if (controller) { - ChatWindow* window = controller->detachChatWindow(); - chatControllers_.erase(jid); - delete controller; - handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window); - return; - } - } else { - handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true); - return; - } - } - - //if not a mucroom - if (!event->isReadable() && !isInvite && !isMediatedInvite) { - /* Only route such messages if a window exists, don't open new windows for them.*/ - - // Do not bind a controller to a full JID, for delivery receipts or chat state notifications. - bool bindControllerToJID = false; - ChatState::ref chatState = message->getPayload<ChatState>(); - if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) { - bindControllerToJID = true; - } - - ChatController* controller = getChatControllerIfExists(jid, bindControllerToJID); - if (controller) { - controller->handleIncomingMessage(event); - } - } else { - getChatControllerOrCreate(jid)->handleIncomingMessage(event); - } +void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> incomingMessage) { + std::shared_ptr<Message> message = incomingMessage; + if (message->getFrom().toBare() == jid_.toBare()) { + CarbonsReceived::ref carbonsReceived; + CarbonsSent::ref carbonsSent; + Forwarded::ref forwarded; + Message::ref forwardedMessage; + if ((carbonsReceived = incomingMessage->getPayload<CarbonsReceived>()) && + (forwarded = carbonsReceived->getForwarded()) && + (forwardedMessage = std::dynamic_pointer_cast<Message>(forwarded->getStanza()))) { + message = forwardedMessage; + } + else if ((carbonsSent = incomingMessage->getPayload<CarbonsSent>()) && + (forwarded = carbonsSent->getForwarded()) && + (forwardedMessage = std::dynamic_pointer_cast<Message>(forwarded->getStanza()))) { + JID toJID = forwardedMessage->getTo(); + + ChatController* controller = getChatControllerOrCreate(toJID); + if (controller) { + controller->handleIncomingOwnMessage(forwardedMessage); + } + else { + SWIFT_LOG(error) << "Carbons message ignored." << std::endl; + } + return; + } + } + JID fromJID = message->getFrom(); + + std::shared_ptr<MessageEvent> event(new MessageEvent(message)); + bool isInvite = !!message->getPayload<MUCInvitationPayload>(); + bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite()); + if (isMediatedInvite) { + fromJID = (*message->getPayload<MUCUserPayload>()->getInvite()).from; + } + if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !isMediatedInvite && !message->hasSubject()) { + return; + } + + // Try to deliver MUC errors to a MUC PM window if a suitable window is open. + if (message->getType() == Message::Error) { + auto controller = getChatControllerIfExists(fromJID, messageCausesSessionBinding(message)); + if (controller) { + controller->handleIncomingMessage(event); + return; + } + } + + // Try to deliver it to a MUC. + if (message->getType() == Message::Groupchat || message->getType() == Message::Error) { + // Try to deliver it to a MUC room. + std::map<JID, MUCController*>::iterator i = mucControllers_.find(fromJID.toBare()); + if (i != mucControllers_.end()) { + i->second->handleIncomingMessage(event); + return; + } + else if (message->getType() == Message::Groupchat) { + //FIXME: Error handling - groupchat messages from an unknown muc. + return; + } + } + + // check for impromptu invite to potentially auto-accept + MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); + if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { + if (invite->getIsContinuation()) { + // check for existing chat controller for the from JID + ChatController* controller = getChatControllerIfExists(fromJID); + if (controller) { + ChatWindow* window = controller->detachChatWindow(); + chatControllers_.erase(fromJID); + delete controller; + handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window); + return; + } + } else { + handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true); + return; + } + } + + //if not a mucroom + if (!event->isReadable() && !isInvite && !isMediatedInvite) { + /* Only route such messages if a window exists, don't open new windows for them.*/ + + // Do not bind a controller to a full JID, for delivery receipts or chat state notifications. + ChatController* controller = getChatControllerIfExists(fromJID, messageCausesSessionBinding(message)); + if (controller) { + controller->handleIncomingMessage(event); + } + } else { + getChatControllerOrCreate(fromJID)->handleIncomingMessage(event); + } } void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) { - if (joinMUCWindow_) { - joinMUCWindow_->setMUC(muc.toString()); - } + if (joinMUCWindow_) { + joinMUCWindow_->setMUC(muc.toString()); + } } void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) { - uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getPassword(), mucBookmark.getNick())); + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getPassword(), mucBookmark.getNick())); } void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) { - ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty()); - chatController->handleNewFileTransferController(ftc); - chatController->activateChatWindow(); - if (ftc->isIncoming()) { - eventController_->handleIncomingEvent(boost::make_shared<IncomingFileTransferEvent>(ftc->getOtherParty())); - } + ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty()); + chatController->handleNewFileTransferController(ftc); + if (!ftc->isIncoming()) { + chatController->activateChatWindow(); + } } void ChatsManager::handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf) { - ChatController* chatController = getChatControllerOrCreate(contact); - chatController->handleWhiteboardSessionRequest(senderIsSelf); - chatController->activateChatWindow(); + ChatController* chatController = getChatControllerOrCreate(contact); + chatController->handleWhiteboardSessionRequest(senderIsSelf); + chatController->activateChatWindow(); } void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state) { - ChatController* chatController = getChatControllerOrCreate(contact); - chatController->handleWhiteboardStateChange(state); - chatController->activateChatWindow(); - if (state == ChatWindow::WhiteboardAccepted) { - boost::filesystem::path path; - JID bareJID = contact.toBare(); - if (avatarManager_) { - path = avatarManager_->getAvatarPath(bareJID); - } - ChatListWindow::Chat chat(bareJID, nickResolver_->jidToNick(bareJID), "", 0, StatusShow::None, path, false); - chatListWindow_->addWhiteboardSession(chat); - } else { - chatListWindow_->removeWhiteboardSession(contact.toBare()); - } + ChatController* chatController = getChatControllerOrCreate(contact); + chatController->handleWhiteboardStateChange(state); + chatController->activateChatWindow(); + if (state == ChatWindow::WhiteboardAccepted) { + boost::filesystem::path path; + JID bareJID = contact.toBare(); + if (avatarManager_) { + path = avatarManager_->getAvatarPath(bareJID); + } + ChatListWindow::Chat chat(bareJID, nickResolver_->jidToNick(bareJID), "", 0, StatusShow::None, path, false); + chatListWindow_->addWhiteboardSession(chat); + } else { + chatListWindow_->removeWhiteboardSession(contact.toBare()); + } } void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { - if (chat.isMUC && !chat.impromptuJIDs.empty()) { - typedef std::pair<std::string, JID> StringJIDPair; - std::vector<JID> inviteJIDs; - foreach(StringJIDPair pair, chat.impromptuJIDs) { - inviteJIDs.push_back(pair.second); - } - uiEventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, "")); - } - else if (chat.isMUC) { - /* FIXME: This means that recents requiring passwords will just flat-out not work */ - uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick)); - } - else { - uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(chat.jid)); - } + if (chat.isMUC && !chat.impromptuJIDs.empty()) { + typedef std::pair<std::string, JID> StringJIDPair; + std::vector<JID> inviteJIDs; + for (StringJIDPair pair : chat.impromptuJIDs) { + inviteJIDs.push_back(pair.second); + } + uiEventStream_->send(std::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, "")); + } + else if (chat.isMUC) { + /* FIXME: This means that recents requiring passwords will just flat-out not work */ + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick)); + } + else { + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(chat.jid)); + } } -void ChatsManager::handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info) { - foreach (DiscoInfo::Identity identity, info->getIdentities()) { - if ((identity.getCategory() == "directory" - && identity.getType() == "chatroom") - || (identity.getCategory() == "conference" - && identity.getType() == "text")) { - localMUCServiceJID_ = service; - localMUCServiceFinderWalker_->endWalk(); - SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl; - break; - } - } +void ChatsManager::handleLocalServiceFound(const JID& service, std::shared_ptr<DiscoInfo> info) { + for (DiscoInfo::Identity identity : info->getIdentities()) { + if ((identity.getCategory() == "directory" + && identity.getType() == "chatroom") + || (identity.getCategory() == "conference" + && identity.getType() == "text")) { + localMUCServiceJID_ = service; + localMUCServiceFinderWalker_->endWalk(); + SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl; + break; + } + } } void ChatsManager::handleLocalServiceWalkFinished() { - bool impromptuMUCSupported = !localMUCServiceJID_.toString().empty(); - foreach (JIDChatControllerPair controllerPair, chatControllers_) { - controllerPair.second->setCanStartImpromptuChats(impromptuMUCSupported); - } - foreach (JIDMUCControllerPair controllerPair, mucControllers_) { - controllerPair.second->setCanStartImpromptuChats(impromptuMUCSupported); - } - onImpromptuMUCServiceDiscovered(impromptuMUCSupported); + bool impromptuMUCSupported = !localMUCServiceJID_.toString().empty(); + for (JIDChatControllerPair controllerPair : chatControllers_) { + controllerPair.second->setCanStartImpromptuChats(impromptuMUCSupported); + } + for (JIDMUCControllerPair controllerPair : mucControllers_) { + controllerPair.second->setCanStartImpromptuChats(impromptuMUCSupported); + } + onImpromptuMUCServiceDiscovered(impromptuMUCSupported); } std::vector<ChatListWindow::Chat> ChatsManager::getRecentChats() const { - return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); + return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); } std::vector<Contact::ref> Swift::ChatsManager::getContacts(bool withMUCNicks) { - std::vector<Contact::ref> result; - foreach (ChatListWindow::Chat chat, recentChats_) { - if (!chat.isMUC) { - result.push_back(boost::make_shared<Contact>(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath)); - } - } - if (withMUCNicks) { - /* collect MUC nicks */ - typedef std::map<JID, MUCController*>::value_type Item; - foreach (const Item& item, mucControllers_) { - JID mucJID = item.second->getToJID(); - std::map<std::string, JID> participants = item.second->getParticipantJIDs(); - typedef std::map<std::string, JID>::value_type ParticipantType; - foreach (const ParticipantType& participant, participants) { - const JID nickJID = JID(mucJID.getNode(), mucJID.getDomain(), participant.first); - Presence::ref presence = presenceOracle_->getLastPresence(nickJID); - const boost::filesystem::path avatar = avatarManager_->getAvatarPath(nickJID); - result.push_back(boost::make_shared<Contact>(participant.first, JID(), presence->getShow(), avatar)); - } - } - } - return result; + std::vector<Contact::ref> result; + for (ChatListWindow::Chat chat : recentChats_) { + if (!chat.isMUC) { + result.push_back(std::make_shared<Contact>(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath)); + } + } + if (withMUCNicks) { + /* collect MUC nicks */ + typedef std::map<JID, MUCController*>::value_type Item; + for (const Item& item : mucControllers_) { + JID mucJID = item.second->getToJID(); + std::map<std::string, JID> participants = item.second->getParticipantJIDs(); + typedef std::map<std::string, JID>::value_type ParticipantType; + for (const ParticipantType& participant : participants) { + const JID nickJID = JID(mucJID.getNode(), mucJID.getDomain(), participant.first); + Presence::ref presence = presenceOracle_->getLastPresence(nickJID); + const boost::filesystem::path avatar = avatarManager_->getAvatarPath(nickJID); + result.push_back(std::make_shared<Contact>(participant.first, JID(), presence->getShow(), avatar)); + } + } + } + return result; } ChatsManager::SingleChatWindowFactoryAdapter::SingleChatWindowFactoryAdapter(ChatWindow* chatWindow) : chatWindow_(chatWindow) {} ChatsManager::SingleChatWindowFactoryAdapter::~SingleChatWindowFactoryAdapter() {} ChatWindow* ChatsManager::SingleChatWindowFactoryAdapter::createChatWindow(const JID &, UIEventStream*) { - return chatWindow_; + return chatWindow_; } } diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 58a9017..593624d 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -7,10 +7,9 @@ #pragma once #include <map> +#include <memory> #include <string> -#include <boost/shared_ptr.hpp> - #include <Swiften/Base/IDGenerator.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/Message.h> @@ -27,159 +26,160 @@ #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> namespace Swift { - class EventController; - class ChatController; - class ChatControllerBase; - class MUCController; - class MUCManager; - class JoinMUCWindow; - class JoinMUCWindowFactory; - class NickResolver; - class PresenceOracle; - class AvatarManager; - class StanzaChannel; - class IQRouter; - class PresenceSender; - class MUCBookmarkManager; - class ChatListWindowFactory; - class TimerFactory; - class EntityCapsProvider; - class DirectedPresenceSender; - class MUCSearchWindowFactory; - class ProfileSettingsProvider; - class MUCSearchController; - class FileTransferOverview; - class FileTransferController; - class XMPPRoster; - class SettingsProvider; - class WhiteboardManager; - class HistoryController; - class HighlightManager; - class ClientBlockListManager; - class ChatMessageParser; - class DiscoServiceWalker; - class AutoAcceptMUCInviteDecider; - class VCardManager; - - class ChatsManager : public ContactProvider { - public: - ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, VCardManager* vcardManager); - virtual ~ChatsManager(); - void setAvatarManager(AvatarManager* avatarManager); - void setOnline(bool enabled); - void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); - void handleIncomingMessage(boost::shared_ptr<Message> message); - std::vector<ChatListWindow::Chat> getRecentChats() const; - virtual std::vector<Contact::ref> getContacts(bool withMUCNicks); - - boost::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered; - - private: - class SingleChatWindowFactoryAdapter : public ChatWindowFactory { - public: - SingleChatWindowFactoryAdapter(ChatWindow* chatWindow); - virtual ~SingleChatWindowFactoryAdapter(); - virtual ChatWindow* createChatWindow(const JID &, UIEventStream*); - - private: - ChatWindow* chatWindow_; - }; - - private: - ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage); - void handleChatRequest(const std::string& contact); - void finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID = boost::optional<JID>()); - MUC::ref handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = 0); - void handleSearchMUCRequest(); - void handleMUCSelectedAfterSearch(const JID&); - void rebindControllerJID(const JID& from, const JID& to); - void handlePresenceChange(boost::shared_ptr<Presence> newPresence); - void handleUIEvent(boost::shared_ptr<UIEvent> event); - void handleMUCBookmarkAdded(const MUCBookmark& bookmark); - void handleMUCBookmarkRemoved(const MUCBookmark& bookmark); - void handleUserLeftMUC(MUCController* mucController); - void handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname); - void handleBookmarksReady(); - void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC); - void handleChatClosed(const JID& jid); - void handleNewFileTransferController(FileTransferController*); - void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf); - void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state); - boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat); - void cleanupPrivateMessageRecents(); - void appendRecent(const ChatListWindow::Chat& chat); - void prependRecent(const ChatListWindow::Chat& chat); - void setupBookmarks(); - void loadRecents(); - void saveRecents(); - void handleChatMadeRecent(); - void handleMUCBookmarkActivated(const MUCBookmark&); - void handleRecentActivated(const ChatListWindow::Chat&); - void handleUnreadCountChanged(ChatControllerBase* controller); - void handleAvatarChanged(const JID& jid); - void handleClearRecentsRequested(); - void handleJIDAddedToRoster(const JID&); - void handleJIDRemovedFromRoster(const JID&); - void handleJIDUpdatedInRoster(const JID&); - void handleRosterCleared(); - void handleSettingChanged(const std::string& settingPath); - void markAllRecentsOffline(); - void handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason); - - void handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info); - void handleLocalServiceWalkFinished(); - - void updatePresenceReceivingStateOnChatController(const JID&); - ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const; - - - ChatController* getChatControllerOrFindAnother(const JID &contact); - ChatController* createNewChatController(const JID &contact); - ChatController* getChatControllerOrCreate(const JID &contact); - ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true); - - private: - std::map<JID, MUCController*> mucControllers_; - std::map<JID, ChatController*> chatControllers_; - std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_; - EventController* eventController_; - JID jid_; - StanzaChannel* stanzaChannel_; - IQRouter* iqRouter_; - ChatWindowFactory* chatWindowFactory_; - JoinMUCWindowFactory* joinMUCWindowFactory_; - NickResolver* nickResolver_; - PresenceOracle* presenceOracle_; - AvatarManager* avatarManager_; - PresenceSender* presenceSender_; - UIEventStream* uiEventStream_; - MUCBookmarkManager* mucBookmarkManager_; - boost::shared_ptr<DiscoInfo> serverDiscoInfo_; - ChatListWindow* chatListWindow_; - JoinMUCWindow* joinMUCWindow_; - boost::bsignals::scoped_connection uiEventConnection_; - bool useDelayForLatency_; - TimerFactory* timerFactory_; - MUCRegistry* mucRegistry_; - EntityCapsProvider* entityCapsProvider_; - MUCManager* mucManager; - MUCSearchController* mucSearchController_; - std::list<ChatListWindow::Chat> recentChats_; - ProfileSettingsProvider* profileSettings_; - FileTransferOverview* ftOverview_; - XMPPRoster* roster_; - bool eagleMode_; - bool userWantsReceipts_; - SettingsProvider* settings_; - HistoryController* historyController_; - WhiteboardManager* whiteboardManager_; - HighlightManager* highlightManager_; - std::map<std::string, std::string> emoticons_; - ClientBlockListManager* clientBlockListManager_; - JID localMUCServiceJID_; - boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_; - AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; - IDGenerator idGenerator_; - VCardManager* vcardManager_; - }; + class EventController; + class ChatController; + class ChatControllerBase; + class MUCController; + class MUCManager; + class JoinMUCWindow; + class JoinMUCWindowFactory; + class NickResolver; + class PresenceOracle; + class AvatarManager; + class StanzaChannel; + class IQRouter; + class PresenceSender; + class MUCBookmarkManager; + class ChatListWindowFactory; + class TimerFactory; + class EntityCapsProvider; + class DirectedPresenceSender; + class MUCSearchWindowFactory; + class ProfileSettingsProvider; + class MUCSearchController; + class FileTransferOverview; + class FileTransferController; + class XMPPRoster; + class SettingsProvider; + class WhiteboardManager; + class HistoryController; + class HighlightManager; + class ClientBlockListManager; + class ChatMessageParser; + class DiscoServiceWalker; + class AutoAcceptMUCInviteDecider; + class VCardManager; + + class ChatsManager : public ContactProvider { + public: + ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, VCardManager* vcardManager); + virtual ~ChatsManager(); + void setAvatarManager(AvatarManager* avatarManager); + void setOnline(bool enabled); + void setServerDiscoInfo(std::shared_ptr<DiscoInfo> info); + void handleIncomingMessage(std::shared_ptr<Message> incomingMessage); + std::vector<ChatListWindow::Chat> getRecentChats() const; + virtual std::vector<Contact::ref> getContacts(bool withMUCNicks); + + boost::signals2::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered; + + private: + class SingleChatWindowFactoryAdapter : public ChatWindowFactory { + public: + SingleChatWindowFactoryAdapter(ChatWindow* chatWindow); + virtual ~SingleChatWindowFactoryAdapter(); + virtual ChatWindow* createChatWindow(const JID &, UIEventStream*); + + private: + ChatWindow* chatWindow_; + }; + + private: + ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage); + void handleChatRequest(const std::string& contact); + void finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID = boost::optional<JID>()); + MUC::ref handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = nullptr); + void handleSearchMUCRequest(); + void handleMUCSelectedAfterSearch(const JID&); + void rebindControllerJID(const JID& from, const JID& to); + void handlePresenceChange(std::shared_ptr<Presence> newPresence); + void handleUIEvent(std::shared_ptr<UIEvent> event); + void handleMUCBookmarkAdded(const MUCBookmark& bookmark); + void handleMUCBookmarkRemoved(const MUCBookmark& bookmark); + void handleUserLeftMUC(MUCController* mucController); + void handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname); + void handleBookmarksReady(); + void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC); + void handleChatClosed(const JID& jid); + void handleNewFileTransferController(FileTransferController*); + void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf); + void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state); + boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat); + bool messageCausesSessionBinding(std::shared_ptr<Message> message); + void cleanupPrivateMessageRecents(); + void appendRecent(const ChatListWindow::Chat& chat); + void prependRecent(const ChatListWindow::Chat& chat); + void setupBookmarks(); + void loadRecents(); + void saveRecents(); + void handleChatMadeRecent(); + void handleMUCBookmarkActivated(const MUCBookmark&); + void handleRecentActivated(const ChatListWindow::Chat&); + void handleUnreadCountChanged(ChatControllerBase* controller); + void handleAvatarChanged(const JID& jid); + void handleClearRecentsRequested(); + void handleJIDAddedToRoster(const JID&); + void handleJIDRemovedFromRoster(const JID&); + void handleJIDUpdatedInRoster(const JID&); + void handleRosterCleared(); + void handleSettingChanged(const std::string& settingPath); + void markAllRecentsOffline(); + void handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason); + + void handleLocalServiceFound(const JID& service, std::shared_ptr<DiscoInfo> info); + void handleLocalServiceWalkFinished(); + + void updatePresenceReceivingStateOnChatController(const JID&); + ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const; + + + ChatController* getChatControllerOrFindAnother(const JID &contact); + ChatController* createNewChatController(const JID &contact); + ChatController* getChatControllerOrCreate(const JID &contact); + ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true); + + private: + std::map<JID, MUCController*> mucControllers_; + std::map<JID, ChatController*> chatControllers_; + std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_; + EventController* eventController_; + JID jid_; + StanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + ChatWindowFactory* chatWindowFactory_; + JoinMUCWindowFactory* joinMUCWindowFactory_; + NickResolver* nickResolver_; + PresenceOracle* presenceOracle_; + AvatarManager* avatarManager_; + PresenceSender* presenceSender_; + UIEventStream* uiEventStream_; + MUCBookmarkManager* mucBookmarkManager_; + std::shared_ptr<DiscoInfo> serverDiscoInfo_; + ChatListWindow* chatListWindow_; + JoinMUCWindow* joinMUCWindow_; + boost::signals2::scoped_connection uiEventConnection_; + bool useDelayForLatency_; + TimerFactory* timerFactory_; + MUCRegistry* mucRegistry_; + EntityCapsProvider* entityCapsProvider_; + MUCManager* mucManager; + MUCSearchController* mucSearchController_; + std::list<ChatListWindow::Chat> recentChats_; + ProfileSettingsProvider* profileSettings_; + FileTransferOverview* ftOverview_; + XMPPRoster* roster_; + bool eagleMode_; + bool userWantsReceipts_; + SettingsProvider* settings_; + HistoryController* historyController_; + WhiteboardManager* whiteboardManager_; + HighlightManager* highlightManager_; + std::map<std::string, std::string> emoticons_; + ClientBlockListManager* clientBlockListManager_; + JID localMUCServiceJID_; + std::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + IDGenerator idGenerator_; + VCardManager* vcardManager_; + }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index 409fe1f..ceed776 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -7,14 +7,15 @@ #include <Swift/Controllers/Chat/MUCController.h> #include <algorithm> +#include <memory> #include <boost/bind.hpp> #include <boost/regex.hpp> #include <boost/algorithm/string.hpp> +#include <boost/range/adaptor/reversed.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Log.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> #include <Swiften/Base/Tristate.h> #include <Swiften/Client/BlockList.h> @@ -58,1163 +59,1158 @@ namespace Swift { class MUCBookmarkPredicate { - public: - MUCBookmarkPredicate(const JID& mucJID) : roomJID_(mucJID) { } - bool operator()(const MUCBookmark& operand) { - return operand.getRoom() == roomJID_; - } - - private: - JID roomJID_; + public: + MUCBookmarkPredicate(const JID& mucJID) : roomJID_(mucJID) { } + bool operator()(const MUCBookmark& operand) { + return operand.getRoom() == roomJID_; + } + + private: + JID roomJID_; }; /** * The controller does not gain ownership of the stanzaChannel, nor the factory. */ MUCController::MUCController ( - const JID& self, - MUC::ref muc, - const boost::optional<std::string>& password, - const std::string &nick, - StanzaChannel* stanzaChannel, - IQRouter* iqRouter, - ChatWindowFactory* chatWindowFactory, - PresenceOracle* presenceOracle, - AvatarManager* avatarManager, - UIEventStream* uiEventStream, - bool useDelayForLatency, - TimerFactory* timerFactory, - EventController* eventController, - EntityCapsProvider* entityCapsProvider, - XMPPRoster* roster, - HistoryController* historyController, - MUCRegistry* mucRegistry, - HighlightManager* highlightManager, - ClientBlockListManager* clientBlockListManager, - boost::shared_ptr<ChatMessageParser> chatMessageParser, - bool isImpromptu, - AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, - VCardManager* vcardManager, - MUCBookmarkManager* mucBookmarkManager) : - ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) { - parting_ = true; - joined_ = false; - lastWasPresence_ = false; - shouldJoinOnReconnect_ = true; - doneGettingHistory_ = false; - events_ = uiEventStream; - xmppRoster_ = roster; - - roster_ = new Roster(false, true); - rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource); - completer_ = new TabComplete(); - chatWindow_->setRosterModel(roster_); - chatWindow_->setTabComplete(completer_); - chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); - chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1)); - chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2)); - chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); - chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this)); - chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); - chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this)); - chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this)); - chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1)); - chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this)); - chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1)); - chatWindow_->onUnblockUserRequest.connect(boost::bind(&MUCController::handleUnblockUserRequest, this)); - muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); - muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); - muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); - muc_->onOccupantNicknameChanged.connect(boost::bind(&MUCController::handleOccupantNicknameChanged, this, _1, _2)); - muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); - muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); - muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3)); - muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2)); - muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); - muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); - highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode); - highlighter_->setNick(nick_); - if (timerFactory && stanzaChannel_->isAvailable()) { - loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); - loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); - loginCheckTimer_->start(); - } - else { - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You are currently offline. You will enter this room when you are connected.")), ChatWindow::DefaultDirection); - } - if (isImpromptu) { - muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this)); - chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC); - } else { - muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); - muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3)); - chatWindow_->convertToMUC(ChatWindow::StandardMUC); - chatWindow_->setName(muc->getJID().getNode()); - } - if (stanzaChannel->isAvailable()) { - MUCController::setOnline(true); - } - if (avatarManager_ != NULL) { - avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1))); - } - MUCController::handleBareJIDCapsChanged(muc->getJID()); - eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1)); - - - // setup handling of MUC bookmark changes - mucBookmarkManagerBookmarkAddedConnection_ = (mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&MUCController::handleMUCBookmarkAdded, this, _1))); - mucBookmarkManagerBookmarkRemovedConnection_ = (mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&MUCController::handleMUCBookmarkRemoved, this, _1))); - - std::vector<MUCBookmark> mucBookmarks = mucBookmarkManager_->getBookmarks(); - std::vector<MUCBookmark>::iterator bookmarkIterator = std::find_if(mucBookmarks.begin(), mucBookmarks.end(), MUCBookmarkPredicate(muc->getJID())); - if (bookmarkIterator != mucBookmarks.end()) { - updateChatWindowBookmarkStatus(*bookmarkIterator); - } - else { - updateChatWindowBookmarkStatus(boost::optional<MUCBookmark>()); - } + const JID& self, + MUC::ref muc, + const boost::optional<std::string>& password, + const std::string &nick, + StanzaChannel* stanzaChannel, + IQRouter* iqRouter, + ChatWindowFactory* chatWindowFactory, + PresenceOracle* presenceOracle, + AvatarManager* avatarManager, + UIEventStream* uiEventStream, + bool useDelayForLatency, + TimerFactory* timerFactory, + EventController* eventController, + EntityCapsProvider* entityCapsProvider, + XMPPRoster* roster, + HistoryController* historyController, + MUCRegistry* mucRegistry, + HighlightManager* highlightManager, + ClientBlockListManager* clientBlockListManager, + std::shared_ptr<ChatMessageParser> chatMessageParser, + bool isImpromptu, + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, + VCardManager* vcardManager, + MUCBookmarkManager* mucBookmarkManager) : + ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) { + parting_ = true; + joined_ = false; + lastWasPresence_ = false; + shouldJoinOnReconnect_ = true; + doneGettingHistory_ = false; + events_ = uiEventStream; + xmppRoster_ = roster; + + roster_ = new Roster(false, true); + rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource); + completer_ = new TabComplete(); + chatWindow_->setRosterModel(roster_); + chatWindow_->setTabComplete(completer_); + chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); + chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1)); + chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2)); + chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); + chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this)); + chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); + chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this)); + chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this)); + chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1)); + chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this)); + chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1)); + chatWindow_->onUnblockUserRequest.connect(boost::bind(&MUCController::handleUnblockUserRequest, this)); + muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); + muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); + muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); + muc_->onOccupantNicknameChanged.connect(boost::bind(&MUCController::handleOccupantNicknameChanged, this, _1, _2)); + muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); + muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); + muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3)); + muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2)); + muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); + muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); + highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode); + highlighter_->setNick(nick_); + if (timerFactory && stanzaChannel_->isAvailable()) { + loginCheckTimer_ = std::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); + loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); + loginCheckTimer_->start(); + } + else { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You are currently offline. You will enter this room when you are connected.")), ChatWindow::DefaultDirection); + } + if (isImpromptu) { + muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this)); + chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC); + } else { + muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); + muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3)); + chatWindow_->convertToMUC(ChatWindow::StandardMUC); + chatWindow_->setName(muc->getJID().getNode()); + } + if (stanzaChannel->isAvailable()) { + MUCController::setOnline(true); + } + if (avatarManager_ != nullptr) { + avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1))); + } + MUCController::handleBareJIDCapsChanged(muc->getJID()); + eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1)); + + + // setup handling of MUC bookmark changes + mucBookmarkManagerBookmarkAddedConnection_ = (mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&MUCController::handleMUCBookmarkAdded, this, _1))); + mucBookmarkManagerBookmarkRemovedConnection_ = (mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&MUCController::handleMUCBookmarkRemoved, this, _1))); + + std::vector<MUCBookmark> mucBookmarks = mucBookmarkManager_->getBookmarks(); + std::vector<MUCBookmark>::iterator bookmarkIterator = std::find_if(mucBookmarks.begin(), mucBookmarks.end(), MUCBookmarkPredicate(muc->getJID())); + if (bookmarkIterator != mucBookmarks.end()) { + updateChatWindowBookmarkStatus(*bookmarkIterator); + } + else { + updateChatWindowBookmarkStatus(boost::optional<MUCBookmark>()); + } } MUCController::~MUCController() { - eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1)); - chatWindow_->setRosterModel(NULL); - delete rosterVCardProvider_; - delete roster_; - if (loginCheckTimer_) { - loginCheckTimer_->stop(); - } - chatWindow_->setTabComplete(NULL); - delete completer_; + eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1)); + chatWindow_->setRosterModel(nullptr); + delete rosterVCardProvider_; + delete roster_; + if (loginCheckTimer_) { + loginCheckTimer_->stop(); + } + chatWindow_->setTabComplete(nullptr); + delete completer_; } void MUCController::cancelReplaces() { - lastWasPresence_ = false; + lastWasPresence_ = false; } void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item) { - std::vector<ChatWindow::OccupantAction> actions; - - if (item) { - MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation(); - MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole(); - if (role == MUCOccupant::Moderator && !isImpromptu_) - { - if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) { - actions.push_back(ChatWindow::Ban); - } - - actions.push_back(ChatWindow::Kick); - actions.push_back(ChatWindow::MakeModerator); - actions.push_back(ChatWindow::MakeParticipant); - actions.push_back(ChatWindow::MakeVisitor); - } - // Add contact is available only if the real JID is also available - if (muc_->getOccupant(item->getJID().getResource()).getRealJID()) { - actions.push_back(ChatWindow::AddContact); - } - actions.push_back(ChatWindow::ShowProfile); - } - chatWindow_->setAvailableOccupantActions(actions); + std::vector<ChatWindow::OccupantAction> actions; + + if (item) { + MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation(); + MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole(); + if (role == MUCOccupant::Moderator && !isImpromptu_) + { + if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) { + actions.push_back(ChatWindow::Ban); + } + + actions.push_back(ChatWindow::Kick); + actions.push_back(ChatWindow::MakeModerator); + actions.push_back(ChatWindow::MakeParticipant); + actions.push_back(ChatWindow::MakeVisitor); + } + // Add contact is available only if the real JID is also available + if (muc_->getOccupant(item->getJID().getResource()).getRealJID()) { + actions.push_back(ChatWindow::AddContact); + } + actions.push_back(ChatWindow::ShowProfile); + } + chatWindow_->setAvailableOccupantActions(actions); } void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction action, ContactRosterItem* item) { - JID mucJID = item->getJID(); - MUCOccupant occupant = muc_->getOccupant(mucJID.getResource()); - JID realJID; - if (occupant.getRealJID()) { - realJID = occupant.getRealJID().get(); - } - switch (action) { - case ChatWindow::Kick: muc_->kickOccupant(mucJID);break; - case ChatWindow::Ban: muc_->changeAffiliation(realJID, MUCOccupant::Outcast);break; - case ChatWindow::MakeModerator: muc_->changeOccupantRole(mucJID, MUCOccupant::Moderator);break; - case ChatWindow::MakeParticipant: muc_->changeOccupantRole(mucJID, MUCOccupant::Participant);break; - case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break; - case ChatWindow::AddContact: if (occupant.getRealJID()) events_->send(boost::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break; - case ChatWindow::ShowProfile: events_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break; - } + JID mucJID = item->getJID(); + MUCOccupant occupant = muc_->getOccupant(mucJID.getResource()); + JID realJID; + if (occupant.getRealJID()) { + realJID = occupant.getRealJID().get(); + } + switch (action) { + case ChatWindow::Kick: muc_->kickOccupant(mucJID);break; + case ChatWindow::Ban: muc_->changeAffiliation(realJID, MUCOccupant::Outcast);break; + case ChatWindow::MakeModerator: muc_->changeOccupantRole(mucJID, MUCOccupant::Moderator);break; + case ChatWindow::MakeParticipant: muc_->changeOccupantRole(mucJID, MUCOccupant::Participant);break; + case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break; + case ChatWindow::AddContact: if (occupant.getRealJID()) events_->send(std::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break; + case ChatWindow::ShowProfile: events_->send(std::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break; + } } void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) { - Tristate support = Yes; - bool any = false; - foreach (const std::string& nick, currentOccupants_) { - DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick); - if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) { - any = true; - } else { - support = Maybe; - } - } - if (!any) { - support = No; - } - chatWindow_->setCorrectionEnabled(support); + Tristate support = Yes; + bool any = false; + for (const auto& nick : currentOccupants_) { + DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick); + if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) { + any = true; + } else { + support = Maybe; + } + } + if (!any) { + support = No; + } + chatWindow_->setCorrectionEnabled(support); } /** * Join the MUC if not already in it. */ void MUCController::rejoin() { - if (parting_) { - joined_ = false; - parting_ = false; - if (password_) { - muc_->setPassword(*password_); - } - //FIXME: check for received activity + if (parting_) { + joined_ = false; + parting_ = false; + if (password_) { + muc_->setPassword(*password_); + } + //FIXME: check for received activity #ifdef SWIFT_EXPERIMENTAL_HISTORY - if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) { - lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_); - } + if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) { + lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_); + } #endif - if (lastActivity_ == boost::posix_time::not_a_date_time) { - muc_->joinAs(nick_); - } - else { - muc_->joinWithContextSince(nick_, lastActivity_); - } - } + if (lastActivity_ == boost::posix_time::not_a_date_time) { + muc_->joinAs(nick_); + } + else { + muc_->joinWithContextSince(nick_, lastActivity_); + } + } } bool MUCController::isJoined() { - return joined_; + return joined_; } const std::string& MUCController::getNick() { - return nick_; + return nick_; } const boost::optional<std::string> MUCController::getPassword() const { - return password_; + return password_; } bool MUCController::isImpromptu() const { - return isImpromptu_; + return isImpromptu_; } std::map<std::string, JID> MUCController::getParticipantJIDs() const { - std::map<std::string, JID> participants; - typedef std::pair<std::string, MUCOccupant> MUCOccupantPair; - std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); - foreach(const MUCOccupantPair& occupant, occupants) { - if (occupant.first != nick_) { - participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID(); - } - } - return participants; + std::map<std::string, JID> participants; + std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); + for (const auto& occupant : occupants) { + if (occupant.first != nick_) { + participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID(); + } + } + return participants; } void MUCController::sendInvites(const std::vector<JID>& jids, const std::string& reason) const { - foreach (const JID& jid, jids) { - muc_->invitePerson(jid, reason, isImpromptu_); - } + for (const auto& jid : jids) { + muc_->invitePerson(jid, reason, isImpromptu_); + } } void MUCController::handleJoinTimeoutTick() { - receivedActivity(); - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection); + receivedActivity(); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection); } void MUCController::receivedActivity() { - if (loginCheckTimer_) { - loginCheckTimer_->stop(); - } + if (loginCheckTimer_) { + loginCheckTimer_->stop(); + } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" -void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { - receivedActivity(); - std::string errorMessage = QT_TRANSLATE_NOOP("", "Unable to enter this room"); - std::string rejoinNick; - if (error) { - switch (error->getCondition()) { - case ErrorPayload::Conflict: - rejoinNick = nick_ + "_"; - errorMessage = str(format(QT_TRANSLATE_NOOP("", "Unable to enter this room as %1%, retrying as %2%")) % nick_ % rejoinNick); - break; - case ErrorPayload::JIDMalformed: - errorMessage += ": "; - errorMessage += QT_TRANSLATE_NOOP("", "No nickname specified"); - break; - case ErrorPayload::NotAuthorized: - errorMessage += ": "; - errorMessage += QT_TRANSLATE_NOOP("", "The correct room password is needed"); - break; - case ErrorPayload::RegistrationRequired: - errorMessage += ": "; - errorMessage += QT_TRANSLATE_NOOP("", "Only members may enter"); - break; - case ErrorPayload::Forbidden: - errorMessage += ": "; - errorMessage += QT_TRANSLATE_NOOP("", "You are banned from the room"); - break; - case ErrorPayload::ServiceUnavailable: - errorMessage += ": "; - errorMessage += QT_TRANSLATE_NOOP("", "The room is full"); - break; - case ErrorPayload::ItemNotFound: - errorMessage += ": "; - errorMessage += QT_TRANSLATE_NOOP("", "The room does not exist"); - break; - - default: break; - } - } - errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't enter room: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); - parting_ = true; - if (!rejoinNick.empty() && renameCounter_ < 10) { - renameCounter_++; - setNick(rejoinNick); - rejoin(); - } +void MUCController::handleJoinFailed(std::shared_ptr<ErrorPayload> error) { + receivedActivity(); + std::string errorMessage = QT_TRANSLATE_NOOP("", "Unable to enter this room"); + std::string rejoinNick; + if (error) { + switch (error->getCondition()) { + case ErrorPayload::Conflict: + rejoinNick = nick_ + "_"; + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Unable to enter this room as %1%, retrying as %2%")) % nick_ % rejoinNick); + break; + case ErrorPayload::JIDMalformed: + errorMessage += ": "; + errorMessage += QT_TRANSLATE_NOOP("", "No nickname specified"); + break; + case ErrorPayload::NotAuthorized: + errorMessage += ": "; + errorMessage += QT_TRANSLATE_NOOP("", "The correct room password is needed"); + break; + case ErrorPayload::RegistrationRequired: + errorMessage += ": "; + errorMessage += QT_TRANSLATE_NOOP("", "Only members may enter"); + break; + case ErrorPayload::Forbidden: + errorMessage += ": "; + errorMessage += QT_TRANSLATE_NOOP("", "You are banned from the room"); + break; + case ErrorPayload::ServiceUnavailable: + errorMessage += ": "; + errorMessage += QT_TRANSLATE_NOOP("", "The room is full"); + break; + case ErrorPayload::ItemNotFound: + errorMessage += ": "; + errorMessage += QT_TRANSLATE_NOOP("", "The room does not exist"); + break; + + default: break; + } + } + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't enter room: %1%.")) % errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); + parting_ = true; + if (!rejoinNick.empty() && renameCounter_ < 10) { + renameCounter_++; + setNick(rejoinNick); + rejoin(); + } } #pragma clang diagnostic pop void MUCController::handleJoinComplete(const std::string& nick) { - receivedActivity(); - renameCounter_ = 0; - joined_ = true; - std::string joinMessage; - if (isImpromptu_) { - joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have joined the chat as %1%.")) % nick); - } else { - joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); - } - setNick(nick); - chatWindow_->replaceSystemMessage(chatMessageParser_->parseMessageBody(joinMessage), lastJoinMessageUID_, ChatWindow::UpdateTimestamp); - lastJoinMessageUID_ = ""; + receivedActivity(); + renameCounter_ = 0; + joined_ = true; + std::string joinMessage; + if (isImpromptu_) { + joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have joined the chat as %1%.")) % nick); + } else { + joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); + } + setNick(nick); + chatWindow_->replaceSystemMessage(chatMessageParser_->parseMessageBody(joinMessage), lastJoinMessageUID_, ChatWindow::UpdateTimestamp); + lastJoinMessageUID_ = ""; #ifdef SWIFT_EXPERIMENTAL_HISTORY - addRecentLogs(); + addRecentLogs(); #endif - clearPresenceQueue(); - shouldJoinOnReconnect_ = true; - setEnabled(true); - if (isImpromptu_) { - setAvailableRoomActions(MUCOccupant::NoAffiliation, MUCOccupant::Participant); - } else { - MUCOccupant occupant = muc_->getOccupant(nick); - setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); - } - onUserJoined(); + clearPresenceQueue(); + shouldJoinOnReconnect_ = true; + setEnabled(true); + if (isImpromptu_) { + setAvailableRoomActions(MUCOccupant::NoAffiliation, MUCOccupant::Participant); + } else { + MUCOccupant occupant = muc_->getOccupant(nick); + setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); + } + onUserJoined(); - if (isImpromptu_) { - setImpromptuWindowTitle(); - } + if (isImpromptu_) { + setImpromptuWindowTitle(); + } } void MUCController::handleAvatarChanged(const JID& jid) { - if (parting_ || !jid.equals(toJID_, JID::WithoutResource)) { - return; - } - roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource)); + if (parting_ || !jid.equals(toJID_, JID::WithoutResource)) { + return; + } + roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource)); } void MUCController::handleWindowClosed() { - parting_ = true; - shouldJoinOnReconnect_ = false; - muc_->part(); - onUserLeft(); + parting_ = true; + shouldJoinOnReconnect_ = false; + muc_->part(); + onUserLeft(); } void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { - if (nick_ != occupant.getNick()) { - completer_->addWord(occupant.getNick()); - } - receivedActivity(); - JID jid(nickToJID(occupant.getNick())); - JID realJID; - if (occupant.getRealJID()) { - realJID = occupant.getRealJID().get(); - } - currentOccupants_.insert(occupant.getNick()); - NickJoinPart event(occupant.getNick(), Join); - appendToJoinParts(joinParts_, event); - MUCOccupant::Role role = MUCOccupant::Participant; - MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; - if (!isImpromptu_) { - role = occupant.getRole(); - affiliation = occupant.getAffiliation(); - } - std::string groupName(roleToGroupName(role)); - roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid)); - roster_->applyOnItems(SetMUC(jid, role, affiliation)); - roster_->getGroup(groupName)->setManualSort(roleToSortName(role)); - if (joined_) { - std::string joinString; - if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) { - joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); - } - else { - joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); - } - if (shouldUpdateJoinParts()) { - updateJoinParts(); - } else { - addPresenceMessage(joinString); - } - - if (isImpromptu_) { - setImpromptuWindowTitle(); - onActivity(""); - } - } - if (avatarManager_ != NULL) { - handleAvatarChanged(jid); - } + if (nick_ != occupant.getNick()) { + completer_->addWord(occupant.getNick()); + } + receivedActivity(); + JID jid(nickToJID(occupant.getNick())); + JID realJID; + if (occupant.getRealJID()) { + realJID = occupant.getRealJID().get(); + } + currentOccupants_.insert(occupant.getNick()); + NickJoinPart event(occupant.getNick(), Join); + appendToJoinParts(joinParts_, event); + MUCOccupant::Role role = MUCOccupant::Participant; + MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; + if (!isImpromptu_) { + role = occupant.getRole(); + affiliation = occupant.getAffiliation(); + } + std::string groupName(roleToGroupName(role)); + roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid)); + roster_->applyOnItems(SetMUC(jid, role, affiliation)); + roster_->getGroup(groupName)->setManualSort(roleToSortName(role)); + if (joined_) { + std::string joinString; + if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) { + joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); + } + else { + joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); + } + if (shouldUpdateJoinParts()) { + updateJoinParts(); + } else { + addPresenceMessage(joinString); + } + + if (isImpromptu_) { + setImpromptuWindowTitle(); + onActivity(""); + } + } + if (avatarManager_ != nullptr) { + handleAvatarChanged(jid); + } } void MUCController::addPresenceMessage(const std::string& message) { - lastWasPresence_ = true; - chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection); + lastWasPresence_ = true; + chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection); } void MUCController::setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role) { - std::vector<ChatWindow::RoomAction> actions; - - if (role <= MUCOccupant::Participant) { - actions.push_back(ChatWindow::ChangeSubject); - } - if (affiliation == MUCOccupant::Owner) { - actions.push_back(ChatWindow::Configure); - } - if (affiliation <= MUCOccupant::Admin) { - actions.push_back(ChatWindow::Affiliations); - } - if (affiliation == MUCOccupant::Owner) { - actions.push_back(ChatWindow::Destroy); - } - if (role <= MUCOccupant::Visitor) { - actions.push_back(ChatWindow::Invite); - } - chatWindow_->setAvailableRoomActions(actions); + std::vector<ChatWindow::RoomAction> actions; + + if (role <= MUCOccupant::Participant) { + actions.push_back(ChatWindow::ChangeSubject); + } + if (affiliation == MUCOccupant::Owner) { + actions.push_back(ChatWindow::Configure); + } + if (affiliation <= MUCOccupant::Admin) { + actions.push_back(ChatWindow::Affiliations); + } + if (affiliation == MUCOccupant::Owner) { + actions.push_back(ChatWindow::Destroy); + } + if (role <= MUCOccupant::Visitor) { + actions.push_back(ChatWindow::Invite); + } + chatWindow_->setAvailableRoomActions(actions); } void MUCController::clearPresenceQueue() { - lastWasPresence_ = false; - joinParts_.clear(); + lastWasPresence_ = false; + joinParts_.clear(); } std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) { - switch (role) { - case MUCOccupant::Moderator: return QT_TRANSLATE_NOOP("", "moderator"); - case MUCOccupant::Participant: return QT_TRANSLATE_NOOP("", "participant"); - case MUCOccupant::Visitor: return QT_TRANSLATE_NOOP("", "visitor"); - case MUCOccupant::NoRole: return ""; - } - assert(false); - return ""; + switch (role) { + case MUCOccupant::Moderator: return QT_TRANSLATE_NOOP("", "moderator"); + case MUCOccupant::Participant: return QT_TRANSLATE_NOOP("", "participant"); + case MUCOccupant::Visitor: return QT_TRANSLATE_NOOP("", "visitor"); + case MUCOccupant::NoRole: return ""; + } + assert(false); + return ""; } std::string MUCController::roleToSortName(MUCOccupant::Role role) { - switch (role) { - case MUCOccupant::Moderator: return "1"; - case MUCOccupant::Participant: return "2"; - case MUCOccupant::Visitor: return "3"; - case MUCOccupant::NoRole: return "4"; - } - assert(false); - return "5"; + switch (role) { + case MUCOccupant::Moderator: return "1"; + case MUCOccupant::Participant: return "2"; + case MUCOccupant::Visitor: return "3"; + case MUCOccupant::NoRole: return "4"; + } + assert(false); + return "5"; } JID MUCController::nickToJID(const std::string& nick) { - return muc_->getJID().withResource(nick); -} - -bool MUCController::messageTargetsMe(boost::shared_ptr<Message> message) { - std::string stringRegexp(".*\\b" + boost::to_lower_copy(nick_) + "\\b.*"); - boost::regex myRegexp(stringRegexp); - return boost::regex_match(boost::to_lower_copy(message->getBody().get_value_or("")), myRegexp); -} - -void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { - if (messageEvent->getStanza()->getType() == Message::Groupchat) { - lastActivity_ = boost::posix_time::microsec_clock::universal_time(); - } - clearPresenceQueue(); - boost::shared_ptr<Message> message = messageEvent->getStanza(); - if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable()) { - chatWindow_->flash(); - } - else { - messageEvent->setTargetsMe(false); - } - if (messageEvent->isReadable() && isImpromptu_) { - chatWindow_->flash(); /* behave like a regular char*/ - } - if (joined_) { - std::string nick = message->getFrom().getResource(); - if (nick != nick_ && currentOccupants_.find(nick) != currentOccupants_.end()) { - completer_->addWord(nick); - } - } - /*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/ - receivedActivity(); - joined_ = true; - - if (message->hasSubject() && !message->getPayload<Body>() && !message->getPayload<Thread>()) { - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection); - chatWindow_->setSubject(message->getSubject()); - doneGettingHistory_ = true; - } - - if (!doneGettingHistory_ && !message->getPayload<Delay>()) { - doneGettingHistory_ = true; - } - - if (!doneGettingHistory_) { - checkDuplicates(message); - messageEvent->conclude(); - } -} - -void MUCController::addMessageHandleIncomingMessage(const JID& from, const std::string& message, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time, const HighlightAction& highlight) { - if (from.isBare()) { - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1%")) % message)), ChatWindow::DefaultDirection); - } - else { - ChatControllerBase::addMessageHandleIncomingMessage(from, message, senderIsSelf, label, time, highlight); - } -} - -void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { - boost::shared_ptr<Message> message = messageEvent->getStanza(); - if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { - if (messageTargetsMe(message) || isImpromptu_) { - eventController_->handleIncomingEvent(messageEvent); - } - if (!messageEvent->getConcluded()) { - highlighter_->handleHighlightAction(highlight); - } - } + return muc_->getJID().withResource(nick); +} + +bool MUCController::messageTargetsMe(std::shared_ptr<Message> message) { + std::string stringRegexp(".*\\b" + boost::to_lower_copy(nick_) + "\\b.*"); + boost::regex myRegexp(stringRegexp); + return boost::regex_match(boost::to_lower_copy(message->getBody().get_value_or("")), myRegexp); +} + +void MUCController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) { + if (messageEvent->getStanza()->getType() == Message::Groupchat) { + lastActivity_ = boost::posix_time::microsec_clock::universal_time(); + } + clearPresenceQueue(); + std::shared_ptr<Message> message = messageEvent->getStanza(); + if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable()) { + chatWindow_->flash(); + } + else { + messageEvent->setTargetsMe(false); + } + if (messageEvent->isReadable() && isImpromptu_) { + chatWindow_->flash(); /* behave like a regular char*/ + } + if (joined_) { + std::string nick = message->getFrom().getResource(); + if (nick != nick_ && currentOccupants_.find(nick) != currentOccupants_.end()) { + completer_->addWord(nick); + } + } + /*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/ + receivedActivity(); + joined_ = true; + + if (message->hasSubject() && !message->getPayload<Body>() && !message->getPayload<Thread>()) { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection); + chatWindow_->setSubject(message->getSubject()); + doneGettingHistory_ = true; + } + + if (!doneGettingHistory_ && !message->getPayload<Delay>()) { + doneGettingHistory_ = true; + } + + if (!doneGettingHistory_) { + checkDuplicates(message); + messageEvent->conclude(); + } +} + +void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) { + if (from.isBare()) { + chatWindow_->addSystemMessage(message, ChatWindow::DefaultDirection); + } + else { + ChatControllerBase::addMessageHandleIncomingMessage(from, message, senderIsSelf, label, time); + } +} + +void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) { + std::shared_ptr<Message> message = messageEvent->getStanza(); + if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { + if (messageTargetsMe(message) || isImpromptu_) { + eventController_->handleIncomingEvent(messageEvent); + } + if (!messageEvent->getConcluded()) { + handleHighlightActions(chatMessage); + } + } } void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant, const MUCOccupant::Role& oldRole) { - clearPresenceQueue(); - receivedActivity(); - JID jid(nickToJID(nick)); - roster_->removeContactFromGroup(jid, roleToGroupName(oldRole)); - JID realJID; - if (occupant.getRealJID()) { - realJID = occupant.getRealJID().get(); - } - std::string group(roleToGroupName(occupant.getRole())); - roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); - roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); - roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); - if (nick == nick_) { - setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); - } + clearPresenceQueue(); + receivedActivity(); + JID jid(nickToJID(nick)); + roster_->removeContactFromGroup(jid, roleToGroupName(oldRole)); + JID realJID; + if (occupant.getRealJID()) { + realJID = occupant.getRealJID().get(); + } + std::string group(roleToGroupName(occupant.getRole())); + roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); + roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); + if (nick == nick_) { + setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); + } } void MUCController::handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Affiliation& /*oldAffiliation*/) { - if (nick == nick_) { - setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole()); - } - JID jid(nickToJID(nick)); - MUCOccupant occupant = muc_->getOccupant(nick); - roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation)); + if (nick == nick_) { + setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole()); + } + JID jid(nickToJID(nick)); + MUCOccupant occupant = muc_->getOccupant(nick); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation)); } std::string MUCController::roleToGroupName(MUCOccupant::Role role) { - std::string result; - switch (role) { - case MUCOccupant::Moderator: result = QT_TRANSLATE_NOOP("", "Moderators"); break; - case MUCOccupant::Participant: result = QT_TRANSLATE_NOOP("", "Participants"); break; - case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break; - case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break; - } - return result; + std::string result; + switch (role) { + case MUCOccupant::Moderator: result = QT_TRANSLATE_NOOP("", "Moderators"); break; + case MUCOccupant::Participant: result = QT_TRANSLATE_NOOP("", "Participants"); break; + case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break; + case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break; + } + return result; } void MUCController::setOnline(bool online) { - ChatControllerBase::setOnline(online); - if (!online) { - muc_->part(); - parting_ = true; - processUserPart(); - } else { - if (shouldJoinOnReconnect_) { - renameCounter_ = 0; - boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); - if (blockList && blockList->isBlocked(muc_->getJID())) { - handleBlockingStateChanged(); - lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again")), ChatWindow::DefaultDirection); - } - else { - if (isImpromptu_) { - lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to join chat")), ChatWindow::DefaultDirection); - } else { - lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection); - } - if (loginCheckTimer_) { - loginCheckTimer_->start(); - } - setNick(desiredNick_); - rejoin(); - } - } - } + ChatControllerBase::setOnline(online); + if (!online) { + muc_->part(); + parting_ = true; + processUserPart(); + } else { + if (shouldJoinOnReconnect_) { + renameCounter_ = 0; + std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + if (blockList && blockList->isBlocked(muc_->getJID())) { + handleBlockingStateChanged(); + lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again")), ChatWindow::DefaultDirection); + } + else { + if (isImpromptu_) { + lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to join chat")), ChatWindow::DefaultDirection); + } else { + lastJoinMessageUID_ = chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection); + } + if (loginCheckTimer_) { + loginCheckTimer_->start(); + } + setNick(desiredNick_); + rejoin(); + } + } + } } void MUCController::processUserPart() { - roster_->removeAll(); - /* handleUserLeft won't throw a part back up unless this is called - when it doesn't yet know we've left - which only happens on - disconnect, so call with disconnect here so if the signal does - bubble back up, it'll be with the right type.*/ - muc_->handleUserLeft(MUC::Disconnect); - setEnabled(false); + roster_->removeAll(); + /* handleUserLeft won't throw a part back up unless this is called + when it doesn't yet know we've left - which only happens on + disconnect, so call with disconnect here so if the signal does + bubble back up, it'll be with the right type.*/ + muc_->handleUserLeft(MUC::Disconnect); + setEnabled(false); } bool MUCController::shouldUpdateJoinParts() { - return lastWasPresence_; + return lastWasPresence_; } void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason) { - NickJoinPart event(occupant.getNick(), Part); - appendToJoinParts(joinParts_, event); - currentOccupants_.erase(occupant.getNick()); - completer_->removeWord(occupant.getNick()); - std::string partMessage; - bool clearAfter = false; - if (occupant.getNick() != nick_) { - std::string partType; - switch (type) { - case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partType = " (kicked)"; break; - case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partType = " (banned)"; break; - case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partType = " (no longer a member)"; break; - case MUC::LeaveDestroy: - case MUC::Disconnect: - case MUC::LeavePart: break; - } - if (isImpromptu_) { - partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the chat%2%")) % occupant.getNick() % partType); - } else { - partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the room%2%")) % occupant.getNick() % partType); - } - } - else if (isImpromptu_) { - switch (type) { - case MUC::LeaveKick: - case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; - case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; - case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "This chat has ended"); break; - case MUC::Disconnect: - case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the chat"); - } - } - else { - switch (type) { - case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been kicked out of the room"); break; - case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been banned from the room"); break; - case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You are no longer a member of the room and have been removed"); break; - case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "The room has been destroyed"); break; - case MUC::Disconnect: - case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the room"); - } - } - if (!reason.empty()) { - partMessage += " (" + reason + ")"; - } - partMessage += "."; - - if (occupant.getNick() != nick_) { - if (shouldUpdateJoinParts()) { - updateJoinParts(); - } else { - addPresenceMessage(partMessage); - } - roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick())); - } else { - addPresenceMessage(partMessage); - parting_ = true; - processUserPart(); - } - if (clearAfter) { - clearPresenceQueue(); - } - - if (isImpromptu_) { - setImpromptuWindowTitle(); - } + NickJoinPart event(occupant.getNick(), Part); + appendToJoinParts(joinParts_, event); + currentOccupants_.erase(occupant.getNick()); + completer_->removeWord(occupant.getNick()); + std::string partMessage; + bool clearAfter = false; + if (occupant.getNick() != nick_) { + std::string partType; + switch (type) { + case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partType = " (kicked)"; break; + case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partType = " (banned)"; break; + case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partType = " (no longer a member)"; break; + case MUC::LeaveDestroy: + case MUC::Disconnect: + case MUC::LeavePart: break; + } + if (isImpromptu_) { + partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the chat%2%")) % occupant.getNick() % partType); + } else { + partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the room%2%")) % occupant.getNick() % partType); + } + } + else if (isImpromptu_) { + switch (type) { + case MUC::LeaveKick: + case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; + case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; + case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "This chat has ended"); break; + case MUC::Disconnect: + case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the chat"); + } + } + else { + switch (type) { + case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been kicked out of the room"); break; + case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been banned from the room"); break; + case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You are no longer a member of the room and have been removed"); break; + case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "The room has been destroyed"); break; + case MUC::Disconnect: + case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the room"); + } + } + if (!reason.empty()) { + partMessage += " (" + reason + ")"; + } + partMessage += "."; + + if (occupant.getNick() != nick_) { + if (shouldUpdateJoinParts()) { + updateJoinParts(); + } else { + addPresenceMessage(partMessage); + } + roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick())); + } else { + addPresenceMessage(partMessage); + parting_ = true; + processUserPart(); + } + if (clearAfter) { + clearPresenceQueue(); + } + + if (isImpromptu_) { + setImpromptuWindowTitle(); + } } void MUCController::handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname) { - addPresenceMessage(generateNicknameChangeString(oldNickname, newNickname)); - JID oldJID = muc_->getJID().withResource(oldNickname); - JID newJID = muc_->getJID().withResource(newNickname); + addPresenceMessage(generateNicknameChangeString(oldNickname, newNickname)); + JID oldJID = muc_->getJID().withResource(oldNickname); + JID newJID = muc_->getJID().withResource(newNickname); - // adjust occupants - currentOccupants_.erase(oldNickname); - currentOccupants_.insert(newNickname); + // adjust occupants + currentOccupants_.erase(oldNickname); + currentOccupants_.insert(newNickname); - // adjust completer - completer_->removeWord(oldNickname); - completer_->addWord(newNickname); + // adjust completer + completer_->removeWord(oldNickname); + completer_->addWord(newNickname); - // update contact - roster_->removeContact(oldJID); - MUCOccupant occupant = muc_->getOccupant(newNickname); + // update contact + roster_->removeContact(oldJID); + MUCOccupant occupant = muc_->getOccupant(newNickname); - JID realJID; - if (occupant.getRealJID()) { - realJID = occupant.getRealJID().get(); - } - MUCOccupant::Role role = MUCOccupant::Participant; - MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; - if (!isImpromptu_) { - role = occupant.getRole(); - affiliation = occupant.getAffiliation(); - } - std::string groupName(roleToGroupName(role)); - roster_->addContact(newJID, realJID, newNickname, groupName, avatarManager_->getAvatarPath(newJID)); - roster_->applyOnItems(SetMUC(newJID, role, affiliation)); - if (avatarManager_ != NULL) { - handleAvatarChanged(newJID); - } + JID realJID; + if (occupant.getRealJID()) { + realJID = occupant.getRealJID().get(); + } + MUCOccupant::Role role = MUCOccupant::Participant; + MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; + if (!isImpromptu_) { + role = occupant.getRole(); + affiliation = occupant.getAffiliation(); + } + std::string groupName(roleToGroupName(role)); + roster_->addContact(newJID, realJID, newNickname, groupName, avatarManager_->getAvatarPath(newJID)); + roster_->applyOnItems(SetMUC(newJID, role, affiliation)); + if (avatarManager_ != nullptr) { + handleAvatarChanged(newJID); + } - clearPresenceQueue(); - onUserNicknameChanged(oldNickname, newNickname); + clearPresenceQueue(); + onUserNicknameChanged(oldNickname, newNickname); } -void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) { - receivedActivity(); - roster_->applyOnItems(SetPresence(presence, JID::WithResource)); +void MUCController::handleOccupantPresenceChange(std::shared_ptr<Presence> presence) { + receivedActivity(); + roster_->applyOnItems(SetPresence(presence, JID::WithResource)); } -bool MUCController::isIncomingMessageFromMe(boost::shared_ptr<Message> message) { - JID from = message->getFrom(); - return nick_ == from.getResource(); +bool MUCController::isIncomingMessageFromMe(std::shared_ptr<Message> message) { + JID from = message->getFrom(); + return nick_ == from.getResource(); } std::string MUCController::senderHighlightNameFromMessage(const JID& from) { - return from.getResource(); + return from.getResource(); } std::string MUCController::senderDisplayNameFromMessage(const JID& from) { - return from.getResource(); + return from.getResource(); } -void MUCController::preSendMessageRequest(boost::shared_ptr<Message> message) { - message->setType(Swift::Message::Groupchat); +void MUCController::preSendMessageRequest(std::shared_ptr<Message> message) { + message->setType(Swift::Message::Groupchat); } -boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boost::shared_ptr<Message> message) const { - return message->getTimestampFrom(toJID_); +boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(std::shared_ptr<Message> message) const { + return message->getTimestampFrom(toJID_); } void MUCController::updateJoinParts() { - chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())), ChatWindow::UpdateTimestamp); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())), ChatWindow::UpdateTimestamp); } void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) { - std::vector<NickJoinPart>::iterator it = joinParts.begin(); - bool matched = false; - for (; it != joinParts.end(); ++it) { - if ((*it).nick == newEvent.nick) { - matched = true; - JoinPart type = (*it).type; - switch (newEvent.type) { - case Join: type = (type == Part) ? PartThenJoin : Join; break; - case Part: type = (type == Join) ? JoinThenPart : Part; break; - case PartThenJoin: break; - case JoinThenPart: break; - } - (*it).type = type; - break; - } - } - if (!matched) { - joinParts.push_back(newEvent); - } + std::vector<NickJoinPart>::iterator it = joinParts.begin(); + bool matched = false; + for (; it != joinParts.end(); ++it) { + if ((*it).nick == newEvent.nick) { + matched = true; + JoinPart type = (*it).type; + switch (newEvent.type) { + case Join: type = (type == Part) ? PartThenJoin : Join; break; + case Part: type = (type == Join) ? JoinThenPart : Part; break; + case PartThenJoin: break; + case JoinThenPart: break; + } + (*it).type = type; + break; + } + } + if (!matched) { + joinParts.push_back(newEvent); + } } std::string MUCController::concatenateListOfNames(const std::vector<NickJoinPart>& joinParts) { - std::string result; - for (size_t i = 0; i < joinParts.size(); i++) { - if (i > 0) { - if (i < joinParts.size() - 1) { - result += ", "; - } else { - result += QT_TRANSLATE_NOOP("", " and "); - } - } - NickJoinPart event = joinParts[i]; - result += event.nick; - } - return result; + std::string result; + for (size_t i = 0; i < joinParts.size(); i++) { + if (i > 0) { + if (i < joinParts.size() - 1) { + result += ", "; + } else { + result += QT_TRANSLATE_NOOP("", " and "); + } + } + NickJoinPart event = joinParts[i]; + result += event.nick; + } + return result; } std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu) { - std::vector<NickJoinPart> sorted[4]; - std::string eventStrings[4]; - foreach (NickJoinPart event, joinParts) { - sorted[event.type].push_back(event); - } - std::string result; - std::vector<JoinPart> populatedEvents; - for (size_t i = 0; i < 4; i++) { - std::string names = concatenateListOfNames(sorted[i]); - if (!names.empty()) { - std::string eventString; - switch (i) { - case Join: - if (sorted[i].size() > 1) { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room")); - } - else { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room")); - } - break; - case Part: - if (sorted[i].size() > 1) { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room")); - } - else { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room")); - } - break; - case JoinThenPart: - if (sorted[i].size() > 1) { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% have entered then left the room")); - } - else { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% has entered then left the room")); - } - break; - case PartThenJoin: - if (sorted[i].size() > 1) { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% have left then returned to the room")); - } - else { - eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% has left then returned to the room")); - } - break; - } - populatedEvents.push_back(static_cast<JoinPart>(i)); - eventStrings[i] = str(boost::format(eventString) % names); - } - } - for (size_t i = 0; i < populatedEvents.size(); i++) { - if (i > 0) { - if (i < populatedEvents.size() - 1) { - result += ", "; - } else { - result += QT_TRANSLATE_NOOP("", " and "); - } - } - result += eventStrings[populatedEvents[i]]; - } - return result; + std::vector<NickJoinPart> sorted[4]; + std::string eventStrings[4]; + for (const auto& event : joinParts) { + sorted[event.type].push_back(event); + } + std::string result; + std::vector<JoinPart> populatedEvents; + for (size_t i = 0; i < 4; i++) { + std::string names = concatenateListOfNames(sorted[i]); + if (!names.empty()) { + std::string eventString; + switch (i) { + case Join: + if (sorted[i].size() > 1) { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room")); + } + else { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room")); + } + break; + case Part: + if (sorted[i].size() > 1) { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room")); + } + else { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room")); + } + break; + case JoinThenPart: + if (sorted[i].size() > 1) { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% have entered then left the room")); + } + else { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% has entered then left the room")); + } + break; + case PartThenJoin: + if (sorted[i].size() > 1) { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% have left then returned to the room")); + } + else { + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% has left then returned to the room")); + } + break; + } + populatedEvents.push_back(static_cast<JoinPart>(i)); + eventStrings[i] = str(boost::format(eventString) % names); + } + } + for (size_t i = 0; i < populatedEvents.size(); i++) { + if (i > 0) { + if (i < populatedEvents.size() - 1) { + result += ", "; + } else { + result += QT_TRANSLATE_NOOP("", " and "); + } + } + result += eventStrings[populatedEvents[i]]; + } + return result; } std::string MUCController::generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname) { - return str(boost::format(QT_TRANSLATE_NOOP("", "%1% is now known as %2%.")) % oldNickname % newNickname); + return str(boost::format(QT_TRANSLATE_NOOP("", "%1% is now known as %2%.")) % oldNickname % newNickname); } void MUCController::handleChangeSubjectRequest(const std::string& subject) { - muc_->changeSubject(subject); + muc_->changeSubject(subject); } void MUCController::handleBookmarkRequest() { - const JID jid = muc_->getJID(); + const JID jid = muc_->getJID(); - // Prepare new bookmark for this room. - MUCBookmark roomBookmark(jid, jid.toBare().toString()); - roomBookmark.setPassword(password_); - roomBookmark.setNick(nick_); + // Prepare new bookmark for this room. + MUCBookmark roomBookmark(jid, jid.toBare().toString()); + roomBookmark.setPassword(password_); + roomBookmark.setNick(nick_); - // Check for existing bookmark for this room and, if it exists, use it instead. - std::vector<MUCBookmark> bookmarks = mucBookmarkManager_->getBookmarks(); - foreach (const MUCBookmark& bookmark, bookmarks) { - if (bookmark.getRoom() == jid.toBare()) { - roomBookmark = bookmark; - break; - } - } + // Check for existing bookmark for this room and, if it exists, use it instead. + std::vector<MUCBookmark> bookmarks = mucBookmarkManager_->getBookmarks(); + for (const auto& bookmark : bookmarks) { + if (bookmark.getRoom() == jid.toBare()) { + roomBookmark = bookmark; + break; + } + } - chatWindow_->showBookmarkWindow(roomBookmark); + chatWindow_->showBookmarkWindow(roomBookmark); } void MUCController::handleConfigureRequest(Form::ref form) { - if (form) { - muc_->configureRoom(form); - } - else { - muc_->requestConfigurationForm(); - } + if (form) { + muc_->configureRoom(form); + } + else { + muc_->requestConfigurationForm(); + } } void MUCController::handleConfigurationFailed(ErrorPayload::ref error) { - std::string errorMessage = getErrorMessage(error); - errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); + std::string errorMessage = getErrorMessage(error); + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, const JID&, MUCOccupant::Role) { - std::string errorMessage = getErrorMessage(error); - errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); + std::string errorMessage = getErrorMessage(error); + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } void MUCController::configureAsImpromptuRoom(Form::ref form) { - muc_->configureRoom(buildImpromptuRoomConfiguration(form)); - isImpromptuAlreadyConfigured_ = true; - onImpromptuConfigCompleted(); + muc_->configureRoom(buildImpromptuRoomConfiguration(form)); + isImpromptuAlreadyConfigured_ = true; + onImpromptuConfigCompleted(); } void MUCController::handleConfigurationFormReceived(Form::ref form) { - if (isImpromptu_) { - if (!isImpromptuAlreadyConfigured_) { - configureAsImpromptuRoom(form); - } - } else { - chatWindow_->showRoomConfigurationForm(form); - } + if (isImpromptu_) { + if (!isImpromptuAlreadyConfigured_) { + configureAsImpromptuRoom(form); + } + } else { + chatWindow_->showRoomConfigurationForm(form); + } } void MUCController::handleConfigurationCancelled() { - muc_->cancelConfigureRoom(); + muc_->cancelConfigureRoom(); } void MUCController::handleDestroyRoomRequest() { - muc_->destroyRoom(); + muc_->destroyRoom(); } void MUCController::handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite) { - RequestInviteToMUCUIEvent::ImpromptuMode mode = isImpromptu_ ? RequestInviteToMUCUIEvent::Impromptu : RequestInviteToMUCUIEvent::NotImpromptu; - boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(muc_->getJID(), jidsToInvite, mode)); - eventStream_->send(event); + RequestInviteToMUCUIEvent::ImpromptuMode mode = isImpromptu_ ? RequestInviteToMUCUIEvent::Impromptu : RequestInviteToMUCUIEvent::NotImpromptu; + eventStream_->send(std::make_shared<RequestInviteToMUCUIEvent>(getToJID(), jidsToInvite, mode)); } -void MUCController::handleUIEvent(boost::shared_ptr<UIEvent> event) { - boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); - if (inviteEvent && inviteEvent->getRoom() == muc_->getJID()) { - foreach (const JID& jid, inviteEvent->getInvites()) { - muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_); - } - } +void MUCController::handleUIEvent(std::shared_ptr<UIEvent> event) { + std::shared_ptr<InviteToMUCUIEvent> inviteEvent = std::dynamic_pointer_cast<InviteToMUCUIEvent>(event); + if (inviteEvent && inviteEvent->getOriginator() == muc_->getJID()) { + for (const auto& jid : inviteEvent->getInvites()) { + muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_); + } + } } void MUCController::handleGetAffiliationsRequest() { - muc_->requestAffiliationList(MUCOccupant::Owner); - muc_->requestAffiliationList(MUCOccupant::Admin); - muc_->requestAffiliationList(MUCOccupant::Member); - muc_->requestAffiliationList(MUCOccupant::Outcast); + muc_->requestAffiliationList(MUCOccupant::Owner); + muc_->requestAffiliationList(MUCOccupant::Admin); + muc_->requestAffiliationList(MUCOccupant::Member); + muc_->requestAffiliationList(MUCOccupant::Outcast); } -typedef std::pair<MUCOccupant::Affiliation, JID> AffiliationChangePair; - void MUCController::handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes) { - std::set<JID> addedJIDs; - foreach (const AffiliationChangePair& change, changes) { - if (change.first != MUCOccupant::NoAffiliation) { - addedJIDs.insert(change.second); - } - } - foreach (const AffiliationChangePair& change, changes) { - if (change.first != MUCOccupant::NoAffiliation || addedJIDs.find(change.second) == addedJIDs.end()) { - muc_->changeAffiliation(change.second, change.first); - } - } + std::set<JID> addedJIDs; + for (const auto& change : changes) { + if (change.first != MUCOccupant::NoAffiliation) { + addedJIDs.insert(change.second); + } + } + for (const auto& change : changes) { + if (change.first != MUCOccupant::NoAffiliation || addedJIDs.find(change.second) == addedJIDs.end()) { + muc_->changeAffiliation(change.second, change.first); + } + } } void MUCController::handleUnblockUserRequest() { - eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, muc_->getJID())); + eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, muc_->getJID())); } void MUCController::handleBlockingStateChanged() { - boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); - if (blockList->getState() == BlockList::Available) { - if (blockList->isBlocked(toJID_)) { - if (!blockedContactAlert_) { - blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again")); - } - chatWindow_->setBlockingState(ChatWindow::IsBlocked); - } else { - if (blockedContactAlert_) { - chatWindow_->removeAlert(*blockedContactAlert_); - blockedContactAlert_.reset(); - } - chatWindow_->setBlockingState(ChatWindow::IsUnblocked); - } - } + std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + if (blockList->getState() == BlockList::Available) { + if (blockList->isBlocked(toJID_)) { + if (!blockedContactAlert_) { + blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've blocked this room. To enter the room, first unblock it using the cog menu and try again")); + } + chatWindow_->setBlockingState(ChatWindow::IsBlocked); + } else { + if (blockedContactAlert_) { + chatWindow_->removeAlert(*blockedContactAlert_); + blockedContactAlert_.reset(); + } + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + } + } } void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) { - chatWindow_->setAffiliations(affiliation, jids); + chatWindow_->setAffiliations(affiliation, jids); } void MUCController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) { - // log only incoming messages - if (isIncoming && historyController_) { - historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp); - } + // log only incoming messages + if (isIncoming && historyController_) { + historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp); + } } void MUCController::addRecentLogs() { - if (!historyController_) { - return; - } + if (!historyController_) { + return; + } - joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_); + joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_); - foreach (const HistoryMessage& message, joinContext_) { - bool senderIsSelf = nick_ == message.getFromJID().getResource(); + for (const auto& message : joinContext_) { + bool senderIsSelf = nick_ == message.getFromJID().getResource(); - // the chatWindow uses utc timestamps - addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset()), HighlightAction()); - } + // the chatWindow uses utc timestamps + addMessage(chatMessageParser_->parseMessageBody(message.getMessage()), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, std::make_shared<SecurityLabel>(), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset())); + } } -void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) { - std::string body = newMessage->getBody().get_value_or(""); - JID jid = newMessage->getFrom(); - boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp(); +void MUCController::checkDuplicates(std::shared_ptr<Message> newMessage) { + std::string body = newMessage->getBody().get_value_or(""); + JID jid = newMessage->getFrom(); + boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp(); - reverse_foreach (const HistoryMessage& message, joinContext_) { - boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset()); - if (time && time < messageTime) { - break; - } - if (time && time != messageTime) { - continue; - } - if (message.getFromJID() != jid) { - continue; - } - if (message.getMessage() != body) { - continue; - } + for (const auto& message : boost::adaptors::reverse(joinContext_)) { + boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset()); + if (time && time < messageTime) { + break; + } + if (time && time != messageTime) { + continue; + } + if (message.getFromJID() != jid) { + continue; + } + if (message.getMessage() != body) { + continue; + } - // Mark the message as unreadable - newMessage->setBody(""); - } + // Mark the message as unreadable + newMessage->setBody(""); + } } void MUCController::setNick(const std::string& nick) { - nick_ = nick; - highlighter_->setNick(nick_); + nick_ = nick; + highlighter_->setNick(nick_); } Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) { - Form::ref result = boost::make_shared<Form>(Form::SubmitType); - std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"}; - std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4); - foreach (boost::shared_ptr<FormField> field, roomConfigurationForm->getFields()) { - boost::shared_ptr<FormField> resultField; - if (field->getName() == "muc#roomconfig_enablelogging") { - resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); - } - if (field->getName() == "muc#roomconfig_persistentroom") { - resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); - } - if (field->getName() == "muc#roomconfig_publicroom") { - resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); - } - if (field->getName() == "muc#roomconfig_whois") { - resultField = boost::make_shared<FormField>(FormField::ListSingleType, "anyone"); - } - - if (field->getName() == "FORM_TYPE") { - resultField = boost::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig"); - } - - if (resultField) { - impromptuConfigsMissing.erase(field->getName()); - resultField->setName(field->getName()); - result->addField(resultField); - } - } - - foreach (const std::string& config, impromptuConfigsMissing) { - if (config == "muc#roomconfig_publicroom") { - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection); - } else if (config == "muc#roomconfig_whois") { - chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection); - } - } - - return result; + Form::ref result = std::make_shared<Form>(Form::SubmitType); + std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"}; + std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4); + for (const auto& field : roomConfigurationForm->getFields()) { + std::shared_ptr<FormField> resultField; + if (field->getName() == "muc#roomconfig_enablelogging") { + resultField = std::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_persistentroom") { + resultField = std::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_publicroom") { + resultField = std::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_whois") { + resultField = std::make_shared<FormField>(FormField::ListSingleType, "anyone"); + } + + if (field->getName() == "FORM_TYPE") { + resultField = std::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig"); + } + + if (resultField) { + impromptuConfigsMissing.erase(field->getName()); + resultField->setName(field->getName()); + result->addField(resultField); + } + } + + for (const auto& config : impromptuConfigsMissing) { + if (config == "muc#roomconfig_publicroom") { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection); + } else if (config == "muc#roomconfig_whois") { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection); + } + } + + return result; } void MUCController::setImpromptuWindowTitle() { - std::string title; - typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; - std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); - if (occupants.size() <= 1) { - title = QT_TRANSLATE_NOOP("", "Empty Chat"); - } else { - foreach (StringMUCOccupantPair pair, occupants) { - if (pair.first != nick_) { - title += (title.empty() ? "" : ", ") + pair.first; - } - } - } - chatWindow_->setName(title); + std::string title; + std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); + if (occupants.size() <= 1) { + title = QT_TRANSLATE_NOOP("", "Empty Chat"); + } else { + for (const auto& pair : occupants) { + if (pair.first != nick_) { + title += (title.empty() ? "" : ", ") + pair.first; + } + } + } + chatWindow_->setName(title); } void MUCController::handleRoomUnlocked() { - // Handle buggy MUC implementations where the joined room already exists and is unlocked. - // Configure the room again in this case. - if (!isImpromptuAlreadyConfigured_) { - if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) { - muc_->requestConfigurationForm(); - } else if (isImpromptu_) { - onImpromptuConfigCompleted(); - } - } + // Handle buggy MUC implementations where the joined room already exists and is unlocked. + // Configure the room again in this case. + if (!isImpromptuAlreadyConfigured_) { + if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) { + muc_->requestConfigurationForm(); + } else if (isImpromptu_) { + onImpromptuConfigCompleted(); + } + } } -void MUCController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { - ChatControllerBase::setAvailableServerFeatures(info); - if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { - boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); +void MUCController::setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) { + ChatControllerBase::setAvailableServerFeatures(info); + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + std::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); - blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&MUCController::handleBlockingStateChanged, this)); - blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&MUCController::handleBlockingStateChanged, this)); - blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&MUCController::handleBlockingStateChanged, this)); + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&MUCController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&MUCController::handleBlockingStateChanged, this)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&MUCController::handleBlockingStateChanged, this)); - handleBlockingStateChanged(); - } + handleBlockingStateChanged(); + } } void MUCController::handleMUCBookmarkAdded(const MUCBookmark& bookmark) { - if (bookmark.getRoom() == muc_->getJID()) { - updateChatWindowBookmarkStatus(bookmark); - } + if (bookmark.getRoom() == muc_->getJID()) { + updateChatWindowBookmarkStatus(bookmark); + } } void MUCController::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) { - if (bookmark.getRoom() == muc_->getJID()) { - updateChatWindowBookmarkStatus(boost::optional<MUCBookmark>()); - } + if (bookmark.getRoom() == muc_->getJID()) { + updateChatWindowBookmarkStatus(boost::optional<MUCBookmark>()); + } } void MUCController::updateChatWindowBookmarkStatus(const boost::optional<MUCBookmark>& bookmark) { - assert(chatWindow_); - if (bookmark) { - if (bookmark->getAutojoin()) { - chatWindow_->setBookmarkState(ChatWindow::RoomAutoJoined); - } - else { - chatWindow_->setBookmarkState(ChatWindow::RoomBookmarked); - } - } - else { - chatWindow_->setBookmarkState(ChatWindow::RoomNotBookmarked); - } + assert(chatWindow_); + if (bookmark) { + if (bookmark->getAutojoin()) { + chatWindow_->setBookmarkState(ChatWindow::RoomAutoJoined); + } + else { + chatWindow_->setBookmarkState(ChatWindow::RoomBookmarked); + } + } + else { + chatWindow_->setBookmarkState(ChatWindow::RoomNotBookmarked); + } } } diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index a08d541..e0ffd7e 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -7,14 +7,14 @@ #pragma once #include <map> +#include <memory> #include <set> #include <string> -#include <boost/shared_ptr.hpp> -#include <boost/signals/connection.hpp> +#include <boost/signals2.hpp> +#include <boost/signals2/connection.hpp> #include <Swiften/Base/Override.h> -#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/MUCOccupant.h> #include <Swiften/Elements/Message.h> @@ -27,159 +27,158 @@ #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { - class StanzaChannel; - class IQRouter; - class ChatWindowFactory; - class Roster; - class AvatarManager; - class UIEventStream; - class TimerFactory; - class TabComplete; - class XMPPRoster; - class HighlightManager; - class UIEvent; - class VCardManager; - class RosterVCardProvider; - class ClientBlockListManager; - class MUCBookmarkManager; - class MUCBookmark; - - enum JoinPart {Join, Part, JoinThenPart, PartThenJoin}; - - struct NickJoinPart { - NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {} - std::string nick; - JoinPart type; - }; - - class MUCController : public ChatControllerBase { - public: - MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager); - virtual ~MUCController(); - boost::signal<void ()> onUserLeft; - boost::signal<void ()> onUserJoined; - boost::signal<void ()> onImpromptuConfigCompleted; - boost::signal<void (const std::string&, const std::string& )> onUserNicknameChanged; - virtual void setOnline(bool online) SWIFTEN_OVERRIDE; - virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; - void rejoin(); - static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent); - static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu); - static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); - static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname); - bool isJoined(); - const std::string& getNick(); - const boost::optional<std::string> getPassword() const; - bool isImpromptu() const; - std::map<std::string, JID> getParticipantJIDs() const; - void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; - - protected: - virtual void preSendMessageRequest(boost::shared_ptr<Message> message) SWIFTEN_OVERRIDE; - virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message> message) SWIFTEN_OVERRIDE; - virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; - virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message> message) const SWIFTEN_OVERRIDE; - virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) SWIFTEN_OVERRIDE; - virtual void addMessageHandleIncomingMessage(const JID& from, const std::string& message, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; - virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&) SWIFTEN_OVERRIDE; - virtual void cancelReplaces() SWIFTEN_OVERRIDE; - virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; - - private: - void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role); - void clearPresenceQueue(); - void addPresenceMessage(const std::string& message); - void handleWindowOccupantSelectionChanged(ContactRosterItem* item); - void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item); - void handleWindowClosed(); - void handleAvatarChanged(const JID& jid); - void handleOccupantJoined(const MUCOccupant& occupant); - void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname); - void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason); - void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence); - void handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant,const MUCOccupant::Role& oldRole); - void handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation,const MUCOccupant::Affiliation& oldAffiliation); - void handleJoinComplete(const std::string& nick); - void handleJoinFailed(boost::shared_ptr<ErrorPayload> error); - void handleJoinTimeoutTick(); - void handleChangeSubjectRequest(const std::string&); - void handleBookmarkRequest(); - std::string roleToGroupName(MUCOccupant::Role role); - std::string roleToSortName(MUCOccupant::Role role); - JID nickToJID(const std::string& nick); - std::string roleToFriendlyName(MUCOccupant::Role role); - void receivedActivity(); - bool messageTargetsMe(boost::shared_ptr<Message> message); - void updateJoinParts(); - bool shouldUpdateJoinParts(); - virtual void dayTicked() SWIFTEN_OVERRIDE { clearPresenceQueue(); } - void processUserPart(); - virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; - void handleConfigureRequest(Form::ref); - void handleConfigurationFailed(ErrorPayload::ref); - void handleConfigurationFormReceived(Form::ref); - void handleDestroyRoomRequest(); - void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite); - void handleConfigurationCancelled(); - void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role); - void handleGetAffiliationsRequest(); - void handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids); - void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes); - void handleInviteToMUCWindowDismissed(); - void handleInviteToMUCWindowCompleted(); - void handleUIEvent(boost::shared_ptr<UIEvent> event); - void addRecentLogs(); - void checkDuplicates(boost::shared_ptr<Message> newMessage); - void setNick(const std::string& nick); - void setImpromptuWindowTitle(); - void handleRoomUnlocked(); - void configureAsImpromptuRoom(Form::ref form); - Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm); - - void handleUnblockUserRequest(); - void handleBlockingStateChanged(); - - void handleMUCBookmarkAdded(const MUCBookmark& bookmark); - void handleMUCBookmarkRemoved(const MUCBookmark& bookmark); - void updateChatWindowBookmarkStatus(const boost::optional<MUCBookmark>& bookmark); - - private: - MUC::ref muc_; - UIEventStream* events_; - std::string nick_; - std::string desiredNick_; - Roster* roster_; - TabComplete* completer_; - bool parting_; - bool joined_; - bool lastWasPresence_; - bool shouldJoinOnReconnect_; - bool doneGettingHistory_; - boost::bsignals::scoped_connection avatarChangedConnection_; - boost::shared_ptr<Timer> loginCheckTimer_; - std::set<std::string> currentOccupants_; - std::vector<NickJoinPart> joinParts_; - boost::posix_time::ptime lastActivity_; - boost::optional<std::string> password_; - XMPPRoster* xmppRoster_; - std::vector<HistoryMessage> joinContext_; - size_t renameCounter_; - bool isImpromptu_; - bool isImpromptuAlreadyConfigured_; - RosterVCardProvider* rosterVCardProvider_; - std::string lastJoinMessageUID_; - - ClientBlockListManager* clientBlockListManager_; - boost::bsignals::scoped_connection blockingOnStateChangedConnection_; - boost::bsignals::scoped_connection blockingOnItemAddedConnection_; - boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; - - boost::optional<ChatWindow::AlertID> blockedContactAlert_; - - MUCBookmarkManager* mucBookmarkManager_; - boost::bsignals::scoped_connection mucBookmarkManagerBookmarkAddedConnection_; - boost::bsignals::scoped_connection mucBookmarkManagerBookmarkRemovedConnection_; - }; + class StanzaChannel; + class IQRouter; + class ChatWindowFactory; + class Roster; + class AvatarManager; + class UIEventStream; + class TimerFactory; + class TabComplete; + class XMPPRoster; + class HighlightManager; + class UIEvent; + class VCardManager; + class RosterVCardProvider; + class ClientBlockListManager; + class MUCBookmarkManager; + class MUCBookmark; + + enum JoinPart {Join, Part, JoinThenPart, PartThenJoin}; + + struct NickJoinPart { + NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {} + std::string nick; + JoinPart type; + }; + + class MUCController : public ChatControllerBase { + public: + MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager); + virtual ~MUCController(); + boost::signals2::signal<void ()> onUserLeft; + boost::signals2::signal<void ()> onUserJoined; + boost::signals2::signal<void ()> onImpromptuConfigCompleted; + boost::signals2::signal<void (const std::string&, const std::string& )> onUserNicknameChanged; + virtual void setOnline(bool online) SWIFTEN_OVERRIDE; + virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; + void rejoin(); + static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent); + static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu); + static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); + static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname); + bool isJoined(); + const std::string& getNick(); + const boost::optional<std::string> getPassword() const; + bool isImpromptu() const; + std::map<std::string, JID> getParticipantJIDs() const; + void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; + + protected: + virtual void preSendMessageRequest(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; + virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; + virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; + virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; + virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message> message) const SWIFTEN_OVERRIDE; + virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) SWIFTEN_OVERRIDE; + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE; + virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; + virtual void cancelReplaces() SWIFTEN_OVERRIDE; + virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; + + private: + void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role); + void clearPresenceQueue(); + void addPresenceMessage(const std::string& message); + void handleWindowOccupantSelectionChanged(ContactRosterItem* item); + void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item); + void handleWindowClosed(); + void handleAvatarChanged(const JID& jid); + void handleOccupantJoined(const MUCOccupant& occupant); + void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname); + void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason); + void handleOccupantPresenceChange(std::shared_ptr<Presence> presence); + void handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant,const MUCOccupant::Role& oldRole); + void handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation,const MUCOccupant::Affiliation& oldAffiliation); + void handleJoinComplete(const std::string& nick); + void handleJoinFailed(std::shared_ptr<ErrorPayload> error); + void handleJoinTimeoutTick(); + void handleChangeSubjectRequest(const std::string&); + void handleBookmarkRequest(); + std::string roleToGroupName(MUCOccupant::Role role); + std::string roleToSortName(MUCOccupant::Role role); + JID nickToJID(const std::string& nick); + std::string roleToFriendlyName(MUCOccupant::Role role); + void receivedActivity(); + bool messageTargetsMe(std::shared_ptr<Message> message); + void updateJoinParts(); + bool shouldUpdateJoinParts(); + virtual void dayTicked() SWIFTEN_OVERRIDE { clearPresenceQueue(); } + void processUserPart(); + virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; + void handleConfigureRequest(Form::ref); + void handleConfigurationFailed(ErrorPayload::ref); + void handleConfigurationFormReceived(Form::ref); + void handleDestroyRoomRequest(); + void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite); + void handleConfigurationCancelled(); + void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role); + void handleGetAffiliationsRequest(); + void handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids); + void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes); + void handleInviteToMUCWindowDismissed(); + void handleInviteToMUCWindowCompleted(); + void handleUIEvent(std::shared_ptr<UIEvent> event); + void addRecentLogs(); + void checkDuplicates(std::shared_ptr<Message> newMessage); + void setNick(const std::string& nick); + void setImpromptuWindowTitle(); + void handleRoomUnlocked(); + void configureAsImpromptuRoom(Form::ref form); + Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm); + + void handleUnblockUserRequest(); + void handleBlockingStateChanged(); + + void handleMUCBookmarkAdded(const MUCBookmark& bookmark); + void handleMUCBookmarkRemoved(const MUCBookmark& bookmark); + void updateChatWindowBookmarkStatus(const boost::optional<MUCBookmark>& bookmark); + + private: + MUC::ref muc_; + UIEventStream* events_; + std::string nick_; + std::string desiredNick_; + Roster* roster_; + TabComplete* completer_; + bool parting_; + bool joined_; + bool shouldJoinOnReconnect_; + bool doneGettingHistory_; + boost::signals2::scoped_connection avatarChangedConnection_; + std::shared_ptr<Timer> loginCheckTimer_; + std::set<std::string> currentOccupants_; + std::vector<NickJoinPart> joinParts_; + boost::posix_time::ptime lastActivity_; + boost::optional<std::string> password_; + XMPPRoster* xmppRoster_; + std::vector<HistoryMessage> joinContext_; + size_t renameCounter_; + bool isImpromptu_; + bool isImpromptuAlreadyConfigured_; + RosterVCardProvider* rosterVCardProvider_; + std::string lastJoinMessageUID_; + + ClientBlockListManager* clientBlockListManager_; + boost::signals2::scoped_connection blockingOnStateChangedConnection_; + boost::signals2::scoped_connection blockingOnItemAddedConnection_; + boost::signals2::scoped_connection blockingOnItemRemovedConnection_; + + boost::optional<ChatWindow::AlertID> blockedContactAlert_; + + MUCBookmarkManager* mucBookmarkManager_; + boost::signals2::scoped_connection mucBookmarkManagerBookmarkAddedConnection_; + boost::signals2::scoped_connection mucBookmarkManagerBookmarkRemovedConnection_; + }; } diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp index d40f427..5db917a 100644 --- a/Swift/Controllers/Chat/MUCSearchController.cpp +++ b/Swift/Controllers/Chat/MUCSearchController.cpp @@ -1,183 +1,183 @@ /* - * Copyright (c) 2010-2011 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ -#include "Swift/Controllers/Chat/MUCSearchController.h" +#include <Swift/Controllers/Chat/MUCSearchController.h> #include <iostream> +#include <memory> #include <boost/bind.hpp> -#include <boost/shared_ptr.hpp> -#include <Swiften/Base/foreach.h> -#include <Swiften/Disco/GetDiscoItemsRequest.h> #include <Swiften/Base/Log.h> #include <Swiften/Base/String.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Disco/DiscoServiceWalker.h> +#include <Swiften/Disco/GetDiscoItemsRequest.h> + #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> -#include <Swiften/Disco/DiscoServiceWalker.h> -#include <Swiften/Client/NickResolver.h> namespace Swift { static const std::string SEARCHED_SERVICES = "searchedServices"; -MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, ProfileSettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) { - itemsInProgress_ = 0; - loadSavedServices(); +MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, ProfileSettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(nullptr), walker_(nullptr) { + itemsInProgress_ = 0; + loadSavedServices(); } MUCSearchController::~MUCSearchController() { - delete walker_; - delete window_; + delete walker_; + delete window_; } void MUCSearchController::openSearchWindow() { - if (!window_) { - window_ = factory_->createMUCSearchWindow(); - window_->onSearchService.connect(boost::bind(&MUCSearchController::handleSearchService, this, _1)); - window_->onFinished.connect(boost::bind(&MUCSearchController::handleMUCSearchFinished, this, _1)); - window_->addSavedServices(savedServices_); - } - handleSearchService(JID(jid_.getDomain())); - window_->show(); + if (!window_) { + window_ = factory_->createMUCSearchWindow(); + window_->onSearchService.connect(boost::bind(&MUCSearchController::handleSearchService, this, _1)); + window_->onFinished.connect(boost::bind(&MUCSearchController::handleMUCSearchFinished, this, _1)); + window_->addSavedServices(savedServices_); + } + handleSearchService(JID(jid_.getDomain())); + window_->show(); } void MUCSearchController::loadSavedServices() { - savedServices_.clear(); - foreach (std::string stringItem, String::split(settings_->getStringSetting(SEARCHED_SERVICES), '\n')) { - savedServices_.push_back(JID(stringItem)); - } + savedServices_.clear(); + for (auto&& stringItem : String::split(settings_->getStringSetting(SEARCHED_SERVICES), '\n')) { + savedServices_.push_back(JID(stringItem)); + } } void MUCSearchController::addToSavedServices(const JID& jid) { - savedServices_.erase(std::remove(savedServices_.begin(), savedServices_.end(), jid), savedServices_.end()); - savedServices_.push_front(jid); - - std::string collapsed; - int i = 0; - foreach (JID jidItem, savedServices_) { - if (i >= 15) { - break; - } - if (!collapsed.empty()) { - collapsed += "\n"; - } - collapsed += jidItem.toString(); - ++i; - } - settings_->storeString(SEARCHED_SERVICES, collapsed); - window_->addSavedServices(savedServices_); + savedServices_.erase(std::remove(savedServices_.begin(), savedServices_.end(), jid), savedServices_.end()); + savedServices_.push_front(jid); + + std::string collapsed; + int i = 0; + for (auto&& jidItem : savedServices_) { + if (i >= 15) { + break; + } + if (!collapsed.empty()) { + collapsed += "\n"; + } + collapsed += jidItem.toString(); + ++i; + } + settings_->storeString(SEARCHED_SERVICES, collapsed); + window_->addSavedServices(savedServices_); } void MUCSearchController::handleSearchService(const JID& jid) { - if (!jid.isValid()) { - //Set Window to say error this isn't valid - return; - } - addToSavedServices(jid); - - services_.clear(); - serviceDetails_.clear(); - - window_->setSearchInProgress(true); - refreshView(); - - if (walker_) { - walker_->endWalk(); - walker_->onServiceFound.disconnect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2)); - walker_->onWalkComplete.disconnect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this)); - delete walker_; - } - - SWIFT_LOG(debug) << "Starting walking MUC services" << std::endl; - itemsInProgress_ = 0; - walker_ = new DiscoServiceWalker(jid, iqRouter_); - walker_->onServiceFound.connect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2)); - walker_->onWalkComplete.connect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this)); - walker_->beginWalk(); + if (!jid.isValid()) { + //Set Window to say error this isn't valid + return; + } + addToSavedServices(jid); + + services_.clear(); + serviceDetails_.clear(); + + window_->setSearchInProgress(true); + refreshView(); + + if (walker_) { + walker_->endWalk(); + walker_->onServiceFound.disconnect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2)); + walker_->onWalkComplete.disconnect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this)); + delete walker_; + } + + SWIFT_LOG(debug) << "Starting walking MUC services" << std::endl; + itemsInProgress_ = 0; + walker_ = new DiscoServiceWalker(jid, iqRouter_); + walker_->onServiceFound.connect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2)); + walker_->onWalkComplete.connect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this)); + walker_->beginWalk(); } -void MUCSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { - bool isMUC = false; - std::string name; - foreach (DiscoInfo::Identity identity, info->getIdentities()) { - if ((identity.getCategory() == "directory" - && identity.getType() == "chatroom") - || (identity.getCategory() == "conference" - && identity.getType() == "text")) { - isMUC = true; - name = identity.getName(); - } - } - if (isMUC) { - SWIFT_LOG(debug) << "MUC Service found: " << jid << std::endl; - services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); - services_.push_back(jid); - serviceDetails_[jid].setName(name); - serviceDetails_[jid].setJID(jid); - serviceDetails_[jid].setComplete(false); - itemsInProgress_++; - SWIFT_LOG(debug) << "Requesting items of " << jid << " (" << itemsInProgress_ << " item requests in progress)" << std::endl; - GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_); - discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleRoomsItemsResponse, this, _1, _2, jid)); - discoItemsRequest->send(); - } - else { - removeService(jid); - } - refreshView(); +void MUCSearchController::handleDiscoServiceFound(const JID& jid, std::shared_ptr<DiscoInfo> info) { + bool isMUC = false; + std::string name; + for (auto&& identity : info->getIdentities()) { + if ((identity.getCategory() == "directory" + && identity.getType() == "chatroom") + || (identity.getCategory() == "conference" + && identity.getType() == "text")) { + isMUC = true; + name = identity.getName(); + } + } + if (isMUC) { + SWIFT_LOG(debug) << "MUC Service found: " << jid << std::endl; + services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); + services_.push_back(jid); + serviceDetails_[jid].setName(name); + serviceDetails_[jid].setJID(jid); + serviceDetails_[jid].setComplete(false); + itemsInProgress_++; + SWIFT_LOG(debug) << "Requesting items of " << jid << " (" << itemsInProgress_ << " item requests in progress)" << std::endl; + GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_); + discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleRoomsItemsResponse, this, _1, _2, jid)); + discoItemsRequest->send(); + } + else { + removeService(jid); + } + refreshView(); } void MUCSearchController::handleDiscoWalkFinished() { - SWIFT_LOG(debug) << "MUC Walk finished" << std::endl; - updateInProgressness(); + SWIFT_LOG(debug) << "MUC Walk finished" << std::endl; + updateInProgressness(); } void MUCSearchController::removeService(const JID& jid) { - serviceDetails_.erase(jid); - services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); - refreshView(); + serviceDetails_.erase(jid); + services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); + refreshView(); } -void MUCSearchController::handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) { - itemsInProgress_--; - SWIFT_LOG(debug) << "Items received for " << jid << " (" << itemsInProgress_ << " item requests in progress)" << std::endl; - updateInProgressness(); - if (error) { - handleDiscoError(jid, error); - return; - } - serviceDetails_[jid].clearRooms(); - foreach (DiscoItems::Item item, items->getItems()) { - serviceDetails_[jid].addRoom(MUCService::MUCRoom(item.getJID().getNode(), item.getName(), -1)); - } - serviceDetails_[jid].setComplete(true); - refreshView(); +void MUCSearchController::handleRoomsItemsResponse(std::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) { + itemsInProgress_--; + SWIFT_LOG(debug) << "Items received for " << jid << " (" << itemsInProgress_ << " item requests in progress)" << std::endl; + updateInProgressness(); + if (error) { + handleDiscoError(jid, error); + return; + } + serviceDetails_[jid].clearRooms(); + for (auto&& item : items->getItems()) { + serviceDetails_[jid].addRoom(MUCService::MUCRoom(item.getJID().getNode(), item.getName(), -1)); + } + serviceDetails_[jid].setComplete(true); + refreshView(); } void MUCSearchController::handleDiscoError(const JID& jid, ErrorPayload::ref error) { - serviceDetails_[jid].setComplete(true); - serviceDetails_[jid].setError(error->getText()); + serviceDetails_[jid].setComplete(true); + serviceDetails_[jid].setError(error->getText()); } void MUCSearchController::refreshView() { - window_->clearList(); - foreach (JID jid, services_) { - window_->addService(serviceDetails_[jid]); - } + window_->clearList(); + for (auto&& jid : services_) { + window_->addService(serviceDetails_[jid]); + } } void MUCSearchController::updateInProgressness() { - window_->setSearchInProgress((walker_ && walker_->isActive()) || itemsInProgress_ > 0); + window_->setSearchInProgress((walker_ && walker_->isActive()) || itemsInProgress_ > 0); } void MUCSearchController::handleMUCSearchFinished(const boost::optional<JID>& result) { - if (result) { - onMUCSelected(*result); - } + if (result) { + onMUCSelected(*result); + } } } diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h index 068c930..f853bcd 100644 --- a/Swift/Controllers/Chat/MUCSearchController.h +++ b/Swift/Controllers/Chat/MUCSearchController.h @@ -1,124 +1,124 @@ /* - * Copyright (c) 2010-2011 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once -#include <vector> #include <map> +#include <memory> +#include <string> +#include <vector> -#include <boost/shared_ptr.hpp> -#include "Swiften/Base/boost_bsignals.h" +#include <boost/signals2.hpp> -#include <string> -#include "Swiften/JID/JID.h" +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/JID/JID.h> -#include "Swift/Controllers/UIEvents/UIEvent.h" -#include "Swift/Controllers/ProfileSettingsProvider.h" -#include "Swiften/Elements/DiscoInfo.h" -#include "Swiften/Elements/DiscoItems.h" -#include "Swiften/Elements/ErrorPayload.h" +#include <Swift/Controllers/ProfileSettingsProvider.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> namespace Swift { - class UIEventStream; - class MUCSearchWindow; - class MUCSearchWindowFactory; - class IQRouter; - class DiscoServiceWalker; - class NickResolver; - - class MUCService { - public: - class MUCRoom { - public: - MUCRoom(const std::string& node, const std::string& name, int occupants) : node_(node), name_(name), occupants_(occupants) {} - std::string getNode() {return node_;} - std::string getName() {return name_;} - int getOccupantCount() {return occupants_;} - private: - std::string node_; - std::string name_; - int occupants_; - }; - - MUCService() {error_ = false; complete_ = false;} - - void setComplete(bool complete) { - complete_ = complete; - } - - void setName(const std::string& name) { - name_ = name; - } - - void setJID(const JID& jid) { - jid_ = jid; - } - - bool getComplete() const { - return complete_; - } - - JID getJID() const { - return jid_; - } - - std::string getName() const { - return name_; - } - - void setError(const std::string& errorText) {error_ = true; errorText_ = errorText;} - - void clearRooms() {rooms_.clear();} - - void addRoom(const MUCRoom& room) {rooms_.push_back(room);} - - std::vector<MUCRoom> getRooms() const {return rooms_;} - private: - std::string name_; - JID jid_; - std::vector<MUCRoom> rooms_; - bool complete_; - bool error_; - std::string errorText_; - }; - - class MUCSearchController { - public: - MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, ProfileSettingsProvider* settings); - ~MUCSearchController(); - - void openSearchWindow(); - - public: - boost::signal<void (const JID&)> onMUCSelected; - - private: - void handleSearchService(const JID& jid); - void handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid); - void handleDiscoError(const JID& jid, ErrorPayload::ref error); - void handleDiscoServiceFound(const JID&, boost::shared_ptr<DiscoInfo>); - void handleDiscoWalkFinished(); - void handleMUCSearchFinished(const boost::optional<JID>& result); - void removeService(const JID& jid); - void refreshView(); - void loadSavedServices(); - void addToSavedServices(const JID& jid); - void updateInProgressness(); - - private: - JID jid_; - MUCSearchWindowFactory* factory_; - IQRouter* iqRouter_; - ProfileSettingsProvider* settings_; - MUCSearchWindow* window_; - DiscoServiceWalker* walker_; - std::list<JID> services_; - std::list<JID> savedServices_; - std::map<JID, MUCService> serviceDetails_; - std::vector<DiscoServiceWalker*> walksInProgress_; - int itemsInProgress_; - }; + class UIEventStream; + class MUCSearchWindow; + class MUCSearchWindowFactory; + class IQRouter; + class DiscoServiceWalker; + class NickResolver; + + class MUCService { + public: + class MUCRoom { + public: + MUCRoom(const std::string& node, const std::string& name, int occupants) : node_(node), name_(name), occupants_(occupants) {} + std::string getNode() {return node_;} + std::string getName() {return name_;} + int getOccupantCount() {return occupants_;} + private: + std::string node_; + std::string name_; + int occupants_; + }; + + MUCService() {error_ = false; complete_ = false;} + + void setComplete(bool complete) { + complete_ = complete; + } + + void setName(const std::string& name) { + name_ = name; + } + + void setJID(const JID& jid) { + jid_ = jid; + } + + bool getComplete() const { + return complete_; + } + + JID getJID() const { + return jid_; + } + + std::string getName() const { + return name_; + } + + void setError(const std::string& errorText) {error_ = true; errorText_ = errorText;} + + void clearRooms() {rooms_.clear();} + + void addRoom(const MUCRoom& room) {rooms_.push_back(room);} + + std::vector<MUCRoom> getRooms() const {return rooms_;} + private: + std::string name_; + JID jid_; + std::vector<MUCRoom> rooms_; + bool complete_; + bool error_; + std::string errorText_; + }; + + class MUCSearchController { + public: + MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, ProfileSettingsProvider* settings); + ~MUCSearchController(); + + void openSearchWindow(); + + public: + boost::signals2::signal<void (const JID&)> onMUCSelected; + + private: + void handleSearchService(const JID& jid); + void handleRoomsItemsResponse(std::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid); + void handleDiscoError(const JID& jid, ErrorPayload::ref error); + void handleDiscoServiceFound(const JID&, std::shared_ptr<DiscoInfo>); + void handleDiscoWalkFinished(); + void handleMUCSearchFinished(const boost::optional<JID>& result); + void removeService(const JID& jid); + void refreshView(); + void loadSavedServices(); + void addToSavedServices(const JID& jid); + void updateInProgressness(); + + private: + JID jid_; + MUCSearchWindowFactory* factory_; + IQRouter* iqRouter_; + ProfileSettingsProvider* settings_; + MUCSearchWindow* window_; + DiscoServiceWalker* walker_; + std::list<JID> services_; + std::list<JID> savedServices_; + std::map<JID, MUCService> serviceDetails_; + std::vector<DiscoServiceWalker*> walksInProgress_; + int itemsInProgress_; + }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp index 1b92bb6..bc72b33 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015 Isode Limited. + * Copyright (c) 2013-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -13,266 +13,269 @@ using namespace Swift; class ChatMessageParserTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(ChatMessageParserTest); - CPPUNIT_TEST(testFullBody); - CPPUNIT_TEST(testOneEmoticon); - CPPUNIT_TEST(testBareEmoticon); - CPPUNIT_TEST(testHiddenEmoticon); - CPPUNIT_TEST(testEndlineEmoticon); - CPPUNIT_TEST(testBoundedEmoticons); - CPPUNIT_TEST(testNoColourNoHighlight); - CPPUNIT_TEST_SUITE_END(); - + CPPUNIT_TEST_SUITE(ChatMessageParserTest); + CPPUNIT_TEST(testFullBody); + CPPUNIT_TEST(testOneEmoticon); + CPPUNIT_TEST(testBareEmoticon); + CPPUNIT_TEST(testHiddenEmoticon); + CPPUNIT_TEST(testEndlineEmoticon); + CPPUNIT_TEST(testBoundedEmoticons); + CPPUNIT_TEST(testNoColourNoHighlight); + CPPUNIT_TEST_SUITE_END(); + public: - void setUp() { - smile1_ = ":)"; - smile1Path_ = "/blah/smile1.png"; - smile2_ = ":("; - smile2Path_ = "/blah/smile2.jpg"; - emoticons_[smile1_] = smile1Path_; - emoticons_[smile2_] = smile2Path_; - } - - void tearDown() { - emoticons_.clear(); - } - - void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { - boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->text); - } - - void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { - boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT(!!part); - CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); - CPPUNIT_ASSERT_EQUAL(path, part->imagePath); - } - - void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { - boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->text); - } - - void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { - boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->target); - } - - static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) - { - HighlightRule rule; - std::vector<std::string> keywords; - keywords.push_back(keyword); - rule.setKeywords(keywords); - rule.setMatchCase(matchCase); - rule.setMatchWholeWords(matchWholeWord); - rule.setMatchChat(true); - rule.getAction().setTextBackground("white"); - return rule; - } - - static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) - { - boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); - list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); - return list; - } - - static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) - { - boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); - list->addRule(rule1); - list->addRule(rule2); - return list; - } - - static HighlightRulesListPtr ruleListWithNickHighlight(bool withHighlightColour = true) - { - HighlightRule rule; - rule.setMatchChat(true); - rule.setNickIsKeyword(true); - rule.setMatchCase(true); - rule.setMatchWholeWords(true); - if (withHighlightColour) { - rule.getAction().setTextBackground("white"); - } - boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); - list->addRule(rule); - return list; - } - - void testFullBody() { - const std::string no_special_message = "a message with no special content"; - ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); - assertText(result, 0, no_special_message); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); - result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); - assertEmoticon(result, 0, smile1_, smile1Path_); - assertText(result, 1, " shiny "); - assertEmoticon(result, 2, smile2_, smile2Path_); - assertText(result, 3, " "); - assertHighlight(result, 4, "trigger"); - assertText(result, 5, " "); - assertEmoticon(result, 6, smile1_, smile1Path_); - assertText(result, 7, " "); - assertURL(result, 8, "http://wonderland.lit/blah"); - assertText(result, 9, " "); - assertURL(result, 10, "http://denmark.lit"); - assertText(result, 11, " boom boom"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); - result = testling.parseMessageBody("testtriggermessage"); - assertText(result, 0, "test"); - assertHighlight(result, 1, "trigger"); - assertText(result, 2, "message"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true)); - result = testling.parseMessageBody("testtriggermessage"); - assertText(result, 0, "testtriggermessage"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false)); - result = testling.parseMessageBody("TrIgGeR"); - assertText(result, 0, "TrIgGeR"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); - result = testling.parseMessageBody("TrIgGeR"); - assertHighlight(result, 0, "TrIgGeR"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); - result = testling.parseMessageBody("partialTrIgGeRmatch"); - assertText(result, 0, "partial"); - assertHighlight(result, 1, "TrIgGeR"); - assertText(result, 2, "match"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zero one two three"); - assertText(result, 0, "zero "); - assertHighlight(result, 1, "one"); - assertText(result, 2, " two "); - assertHighlight(result, 3, "three"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zero oNe two tHrEe"); - assertText(result, 0, "zero "); - assertHighlight(result, 1, "oNe"); - assertText(result, 2, " two "); - assertHighlight(result, 3, "tHrEe"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false))); - result = testling.parseMessageBody("zero oNe two tHrEe"); - assertText(result, 0, "zero "); - assertHighlight(result, 1, "oNe"); - assertText(result, 2, " two tHrEe"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false))); - result = testling.parseMessageBody("zero oNe two tHrEe"); - assertText(result, 0, "zero oNe two tHrEe"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zeroonetwothree"); - assertText(result, 0, "zero"); - assertHighlight(result, 1, "one"); - assertText(result, 2, "two"); - assertHighlight(result, 3, "three"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zeroOnEtwoThReE"); - assertText(result, 0, "zeroOnEtwo"); - assertHighlight(result, 1, "ThReE"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zeroonetwothree"); - assertText(result, 0, "zeroonetwo"); - assertHighlight(result, 1, "three"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true))); - result = testling.parseMessageBody("zeroonetwothree"); - assertText(result, 0, "zeroonetwothree"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Alice", "Alice"); - assertHighlight(result, 0, "Alice"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("TextAliceText", "Alice"); - assertText(result, 0, "TextAliceText"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Text Alice Text", "Alice"); - assertText(result, 0, "Text "); - assertHighlight(result, 1, "Alice"); - assertText(result, 2, " Text"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Alice Text", "Alice"); - assertHighlight(result, 0, "Alice"); - assertText(result, 1, " Text"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Text Alice", "Alice"); - assertText(result, 0, "Text "); - assertHighlight(result, 1, "Alice"); - } - - void testOneEmoticon() { - ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(" :) "); - assertText(result, 0, " "); - assertEmoticon(result, 1, smile1_, smile1Path_); - assertText(result, 2, " "); - } - - - void testBareEmoticon() { - ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); - assertEmoticon(result, 0, smile1_, smile1Path_); - } - - void testHiddenEmoticon() { - ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); - assertText(result, 0, "b:)a"); - } - - void testEndlineEmoticon() { - ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)"); - assertText(result, 0, "Lazy"); - assertEmoticon(result, 1, smile1_, smile1Path_); - } - - void testBoundedEmoticons() { - ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:("); - assertEmoticon(result, 0, smile1_, smile1Path_); - assertText(result, 1, "Lazy"); - assertEmoticon(result, 2, smile2_, smile2Path_); - } - - void testEmoticonParenthesis() { - ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); - assertText(result, 0, "(Like this "); - assertEmoticon(result, 1, smile1_, smile1Path_); - assertText(result, 2, ")"); - } - - void testNoColourNoHighlight() { - ChatMessageParser testling(emoticons_, ruleListWithNickHighlight(false)); - ChatWindow::ChatMessage result = testling.parseMessageBody("Alice", "Alice"); - assertText(result, 0, "Alice"); - } + void setUp() { + smile1_ = ":)"; + smile1Path_ = "/blah/smile1.png"; + smile2_ = ":("; + smile2Path_ = "/blah/smile2.jpg"; + emoticons_[smile1_] = smile1Path_; + emoticons_[smile2_] = smile2Path_; + } + + void tearDown() { + emoticons_.clear(); + } + + void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + std::shared_ptr<ChatWindow::ChatTextMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->text); + } + + void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { + std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT(!!part); + CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); + CPPUNIT_ASSERT_EQUAL(path, part->imagePath); + } + +#define assertHighlight(RESULT, INDEX, TEXT, EXPECTED_HIGHLIGHT) \ + { \ + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(RESULT.getParts()[INDEX]); \ + CPPUNIT_ASSERT_EQUAL(std::string(TEXT), part->text); \ + CPPUNIT_ASSERT(EXPECTED_HIGHLIGHT == part->action); \ + } + + void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + std::shared_ptr<ChatWindow::ChatURIMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->target); + } + + static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + HighlightRule rule; + std::vector<std::string> keywords; + keywords.push_back(keyword); + rule.setKeywords(keywords); + rule.setMatchCase(matchCase); + rule.setMatchWholeWords(matchWholeWord); + rule.setMatchChat(true); + rule.getAction().setTextBackground("white"); + return rule; + } + + static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); + return list; + } + + static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) + { + std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule1); + list->addRule(rule2); + return list; + } + + static HighlightRulesListPtr ruleListWithNickHighlight(bool withHighlightColour = true) + { + HighlightRule rule; + rule.setMatchChat(true); + rule.setNickIsKeyword(true); + rule.setMatchCase(true); + rule.setMatchWholeWords(true); + if (withHighlightColour) { + rule.getAction().setTextBackground("white"); + } + std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule); + return list; + } + + void testFullBody() { + const std::string no_special_message = "a message with no special content"; + ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); + assertText(result, 0, no_special_message); + + HighlightRulesListPtr highlightRuleList = ruleListFromKeyword("trigger", false, false); + testling = ChatMessageParser(emoticons_, highlightRuleList); + result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, " shiny "); + assertEmoticon(result, 2, smile2_, smile2Path_); + assertText(result, 3, " "); + assertHighlight(result, 4, "trigger", highlightRuleList->getRule(0).getAction()); + assertText(result, 5, " "); + assertEmoticon(result, 6, smile1_, smile1Path_); + assertText(result, 7, " "); + assertURL(result, 8, "http://wonderland.lit/blah"); + assertText(result, 9, " "); + assertURL(result, 10, "http://denmark.lit"); + assertText(result, 11, " boom boom"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "test"); + assertHighlight(result, 1, "trigger", highlightRuleList->getRule(0).getAction()); + assertText(result, 2, "message"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "testtriggermessage"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertText(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertHighlight(result, 0, "TrIgGeR", highlightRuleList->getRule(0).getAction()); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("partialTrIgGeRmatch"); + assertText(result, 0, "partial"); + assertHighlight(result, 1, "TrIgGeR", highlightRuleList->getRule(0).getAction()); + assertText(result, 2, "match"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero one two three"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "one", highlightRuleList->getRule(0).getAction()); + assertText(result, 2, " two "); + assertHighlight(result, 3, "three", highlightRuleList->getRule(0).getAction()); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe", highlightRuleList->getRule(0).getAction()); + assertText(result, 2, " two "); + assertHighlight(result, 3, "tHrEe", highlightRuleList->getRule(0).getAction()); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe", highlightRuleList->getRule(0).getAction()); + assertText(result, 2, " two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero oNe two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zero"); + assertHighlight(result, 1, "one", highlightRuleList->getRule(0).getAction()); + assertText(result, 2, "two"); + assertHighlight(result, 3, "three", highlightRuleList->getRule(0).getAction()); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroOnEtwoThReE"); + assertText(result, 0, "zeroOnEtwo"); + assertHighlight(result, 1, "ThReE", highlightRuleList->getRule(0).getAction()); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwo"); + assertHighlight(result, 1, "three", highlightRuleList->getRule(0).getAction()); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwothree"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice", "Alice"); + assertHighlight(result, 0, "Alice", highlightRuleList->getRule(0).getAction()); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("TextAliceText", "Alice"); + assertText(result, 0, "TextAliceText"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice Text", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice", highlightRuleList->getRule(0).getAction()); + assertText(result, 2, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice Text", "Alice"); + assertHighlight(result, 0, "Alice", highlightRuleList->getRule(0).getAction()); + assertText(result, 1, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice", highlightRuleList->getRule(0).getAction()); + } + + void testOneEmoticon() { + ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(" :) "); + assertText(result, 0, " "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, " "); + } + + + void testBareEmoticon() { + ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); + assertEmoticon(result, 0, smile1_, smile1Path_); + } + + void testHiddenEmoticon() { + ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); + assertText(result, 0, "b:)a"); + } + + void testEndlineEmoticon() { + ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)"); + assertText(result, 0, "Lazy"); + assertEmoticon(result, 1, smile1_, smile1Path_); + } + + void testBoundedEmoticons() { + ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:("); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, "Lazy"); + assertEmoticon(result, 2, smile2_, smile2Path_); + } + + void testEmoticonParenthesis() { + ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); + assertText(result, 0, "(Like this "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, ")"); + } + + void testNoColourNoHighlight() { + ChatMessageParser testling(emoticons_, ruleListWithNickHighlight(false)); + ChatWindow::ChatMessage result = testling.parseMessageBody("Alice", "Alice"); + assertText(result, 0, "Alice"); + } private: - std::map<std::string, std::string> emoticons_; - std::string smile1_; - std::string smile1Path_; - std::string smile2_; - std::string smile2Path_; + std::map<std::string, std::string> emoticons_; + std::string smile1_; + std::string smile1Path_; + std::string smile2_; + std::string smile2Path_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 4f8cf5a..cff54f8 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -4,6 +4,9 @@ * See the COPYING file for more information. */ +#include <map> +#include <set> + #include <boost/bind.hpp> #include <cppunit/extensions/HelperMacros.h> @@ -20,8 +23,13 @@ #include <Swiften/Crypto/CryptoProvider.h> #include <Swiften/Crypto/PlatformCryptoProvider.h> #include <Swiften/Disco/DummyEntityCapsProvider.h> +#include <Swiften/Elements/CarbonsReceived.h> +#include <Swiften/Elements/CarbonsSent.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Forwarded.h> +#include <Swiften/Elements/MUCInvitationPayload.h> +#include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> #include <Swiften/Jingle/JingleSessionManager.h> #include <Swiften/MUC/MUCManager.h> @@ -58,723 +66,1124 @@ using namespace Swift; class ChatsManagerTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(ChatsManagerTest); - CPPUNIT_TEST(testFirstOpenWindowIncoming); - CPPUNIT_TEST(testSecondOpenWindowIncoming); - CPPUNIT_TEST(testFirstOpenWindowOutgoing); - CPPUNIT_TEST(testFirstOpenWindowBareToFull); - CPPUNIT_TEST(testSecondWindow); - CPPUNIT_TEST(testUnbindRebind); - CPPUNIT_TEST(testNoDuplicateUnbind); - CPPUNIT_TEST(testThreeMUCWindows); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom); - CPPUNIT_TEST(testChatControllerFullJIDBindingOnMessageAndNotReceipt); - CPPUNIT_TEST(testChatControllerFullJIDBindingOnTypingAndNotActive); - CPPUNIT_TEST(testChatControllerPMPresenceHandling); - CPPUNIT_TEST(testLocalMUCServiceDiscoveryResetOnDisconnect); - CPPUNIT_TEST_SUITE_END(); + CPPUNIT_TEST_SUITE(ChatsManagerTest); + CPPUNIT_TEST(testFirstOpenWindowIncoming); + CPPUNIT_TEST(testSecondOpenWindowIncoming); + CPPUNIT_TEST(testFirstOpenWindowOutgoing); + CPPUNIT_TEST(testFirstOpenWindowBareToFull); + CPPUNIT_TEST(testSecondWindow); + CPPUNIT_TEST(testUnbindRebind); + CPPUNIT_TEST(testNoDuplicateUnbind); + CPPUNIT_TEST(testThreeMUCWindows); + CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster); + CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster); + CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth); + CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom); + CPPUNIT_TEST(testChatControllerFullJIDBindingOnMessageAndNotReceipt); + CPPUNIT_TEST(testChatControllerFullJIDBindingOnTypingAndNotActive); + CPPUNIT_TEST(testLocalMUCServiceDiscoveryResetOnDisconnect); + CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite); + + // MUC PM Tests + CPPUNIT_TEST(testChatControllerPMPresenceHandling); + CPPUNIT_TEST(testChatControllerMucPmUnavailableErrorHandling); + + // Highlighting tests + CPPUNIT_TEST(testChatControllerHighlightingNotificationTesting); + CPPUNIT_TEST(testChatControllerHighlightingNotificationDeduplicateSounds); + CPPUNIT_TEST(testChatControllerMeMessageHandling); + CPPUNIT_TEST(testRestartingMUCComponentCrash); + CPPUNIT_TEST(testChatControllerMeMessageHandlingInMUC); + + // Carbons tests + CPPUNIT_TEST(testCarbonsForwardedIncomingMessageToSecondResource); + CPPUNIT_TEST(testCarbonsForwardedOutgoingMessageFromSecondResource); + + CPPUNIT_TEST_SUITE_END(); public: - void setUp() { - mocks_ = new MockRepository(); - jid_ = JID("test@test.com/resource"); - stanzaChannel_ = new DummyStanzaChannel(); - iqChannel_ = new DummyIQChannel(); - iqRouter_ = new IQRouter(iqChannel_); -// capsProvider_ = new DummyCapsProvider(); - eventController_ = new EventController(); - chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); - joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>(); - xmppRoster_ = new XMPPRosterImpl(); - mucRegistry_ = new MUCRegistry(); - nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, NULL, mucRegistry_); - presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); - serverDiscoInfo_ = boost::make_shared<DiscoInfo>(); - presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); - directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); - mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_); - uiEventStream_ = new UIEventStream(); -// entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); - entityCapsProvider_ = new DummyEntityCapsProvider(); - chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>(); - mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>(); - settings_ = new DummySettingsProvider(); - profileSettings_ = new ProfileSettingsProvider("a", settings_); - chatListWindow_ = new MockChatListWindow(); - ftManager_ = new DummyFileTransferManager(); - ftOverview_ = new FileTransferOverview(ftManager_); - avatarManager_ = new NullAvatarManager(); - wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsProvider_); - wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); - highlightManager_ = new HighlightManager(settings_); - - crypto_ = PlatformCryptoProvider::create(); - vcardStorage_ = new VCardMemoryStorage(crypto_); - vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_); - mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); - clientBlockListManager_ = new ClientBlockListManager(iqRouter_); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsProvider_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, vcardManager_); - - manager_->setAvatarManager(avatarManager_); - } - - void tearDown() { - delete highlightManager_; - //delete chatListWindowFactory - delete profileSettings_; - delete avatarManager_; - delete manager_; - delete clientBlockListManager_; - delete vcardManager_; - delete vcardStorage_; - delete crypto_; - delete ftOverview_; - delete ftManager_; - delete wbSessionManager_; - delete wbManager_; - delete directedPresenceSender_; - delete presenceSender_; - delete presenceOracle_; - delete nickResolver_; - delete mucRegistry_; - delete stanzaChannel_; - delete eventController_; - delete iqRouter_; - delete iqChannel_; - delete uiEventStream_; - delete mucManager_; - delete xmppRoster_; - delete entityCapsProvider_; - delete chatListWindow_; - delete mocks_; - delete settings_; - } - - void testFirstOpenWindowIncoming() { - JID messageJID("testling@test.com/resource1"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - - boost::shared_ptr<Message> message(new Message()); - message->setFrom(messageJID); - std::string body("This is a legible message. >HEH@)oeueu"); - message->setBody(body); - manager_->handleIncomingMessage(message); - CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_); - } - - void testSecondOpenWindowIncoming() { - JID messageJID1("testling@test.com/resource1"); - - MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); - - boost::shared_ptr<Message> message1(new Message()); - message1->setFrom(messageJID1); - std::string body1("This is a legible message. >HEH@)oeueu"); - message1->setBody(body1); - manager_->handleIncomingMessage(message1); - CPPUNIT_ASSERT_EQUAL(body1, window1->lastMessageBody_); - - JID messageJID2("testling@test.com/resource2"); - - //MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - //mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2, uiEventStream_).Return(window2); - - boost::shared_ptr<Message> message2(new Message()); - message2->setFrom(messageJID2); - std::string body2("This is a legible message. .cmaulm.chul"); - message2->setBody(body2); - manager_->handleIncomingMessage(message2); - CPPUNIT_ASSERT_EQUAL(body2, window1->lastMessageBody_); - } - - void testFirstOpenWindowOutgoing() { - std::string messageJIDString("testling@test.com"); - - ChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString), uiEventStream_).Return(window); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString)))); - } - - - void testFirstOpenWindowBareToFull() { - std::string bareJIDString("testling@test.com"); - std::string fullJIDString("testling@test.com/resource1"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(bareJIDString)))); - - boost::shared_ptr<Message> message(new Message()); - message->setFrom(JID(fullJIDString)); - std::string body("This is a legible message. mjuga3089gm8G(*>M)@*("); - message->setBody(body); - manager_->handleIncomingMessage(message); - CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_); - } - - void testSecondWindow() { - std::string messageJIDString1("testling1@test.com"); - ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1)))); - - std::string messageJIDString2("testling2@test.com"); - ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString2)))); - } - - /** Complete cycle. - Create unbound window. - Bind it. - Unbind it. - Rebind it. - */ - void testUnbindRebind() { - std::string bareJIDString("testling@test.com"); - std::string fullJIDString1("testling@test.com/resource1"); - std::string fullJIDString2("testling@test.com/resource2"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(bareJIDString)))); - - boost::shared_ptr<Message> message1(new Message()); - message1->setFrom(JID(fullJIDString1)); - std::string messageBody1("This is a legible message."); - message1->setBody(messageBody1); - manager_->handleIncomingMessage(message1); - CPPUNIT_ASSERT_EQUAL(messageBody1, window->lastMessageBody_); - - boost::shared_ptr<Presence> jid1Online(new Presence()); - jid1Online->setFrom(JID(fullJIDString1)); - boost::shared_ptr<Presence> jid1Offline(new Presence()); - jid1Offline->setFrom(JID(fullJIDString1)); - jid1Offline->setType(Presence::Unavailable); - presenceOracle_->onPresenceChange(jid1Offline); - - boost::shared_ptr<Message> message2(new Message()); - message2->setFrom(JID(fullJIDString2)); - std::string messageBody2("This is another legible message."); - message2->setBody(messageBody2); - manager_->handleIncomingMessage(message2); - CPPUNIT_ASSERT_EQUAL(messageBody2, window->lastMessageBody_); - } - - /** - * Test that MUC PMs get opened in the right windows - */ - void testThreeMUCWindows() { - JID muc("testling@test.com"); - ChatWindow* mucWindow = new MockChatWindow(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc, uiEventStream_).Return(mucWindow); - uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(muc, std::string("nick"))); - - - std::string messageJIDString1("testling@test.com/1"); - ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1)))); - - std::string messageJIDString2("testling@test.com/2"); - ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString2)))); - - std::string messageJIDString3("testling@test.com/3"); - ChatWindow* window3 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString3), uiEventStream_).Return(window3); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString3)))); - - /* Refetch an earlier window */ - /* We do not expect a new window to be created */ - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1)))); - - } - - /** - Test that a second window isn't unbound where there's already an unbound one. - Bind 1 - Bind 2 - Unbind 1 - Unbind 2 (but it doesn't) - Sent to bound 2 - Rebind 1 - */ - void testNoDuplicateUnbind() { - JID messageJID1("testling@test.com/resource1"); - - MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); - - boost::shared_ptr<Message> message1(new Message()); - message1->setFrom(messageJID1); - message1->setBody("This is a legible message1."); - manager_->handleIncomingMessage(message1); - - JID messageJID2("testling@test.com/resource2"); - - //MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - //mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2, uiEventStream_).Return(window2); - - boost::shared_ptr<Message> message2(new Message()); - message2->setFrom(messageJID2); - message2->setBody("This is a legible message2."); - manager_->handleIncomingMessage(message2); - - boost::shared_ptr<Presence> jid1Online(new Presence()); - jid1Online->setFrom(JID(messageJID1)); - boost::shared_ptr<Presence> jid1Offline(new Presence()); - jid1Offline->setFrom(JID(messageJID1)); - jid1Offline->setType(Presence::Unavailable); - presenceOracle_->onPresenceChange(jid1Offline); - - boost::shared_ptr<Presence> jid2Online(new Presence()); - jid2Online->setFrom(JID(messageJID2)); - boost::shared_ptr<Presence> jid2Offline(new Presence()); - jid2Offline->setFrom(JID(messageJID2)); - jid2Offline->setType(Presence::Unavailable); - presenceOracle_->onPresenceChange(jid2Offline); - - JID messageJID3("testling@test.com/resource3"); - - boost::shared_ptr<Message> message3(new Message()); - message3->setFrom(messageJID3); - std::string body3("This is a legible message3."); - message3->setBody(body3); - manager_->handleIncomingMessage(message3); - CPPUNIT_ASSERT_EQUAL(body3, window1->lastMessageBody_); - - boost::shared_ptr<Message> message2b(new Message()); - message2b->setFrom(messageJID2); - std::string body2b("This is a legible message2b."); - message2b->setBody(body2b); - manager_->handleIncomingMessage(message2b); - CPPUNIT_ASSERT_EQUAL(body2b, window1->lastMessageBody_); - } - - /** - * Test that ChatController doesn't send receipts anymore after removal of the contact from the roster. - */ - void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() { - JID messageJID("testling@test.com/resource1"); - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); - manager_->handleIncomingMessage(message); - Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); - - xmppRoster_->removeContact(messageJID); - - message->setID("2"); - manager_->handleIncomingMessage(message); - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - } - - /** - * Test that ChatController sends receipts after the contact has been added to the roster. - */ - void testChatControllerPresenceAccessUpdatedOnAddToRoster() { - JID messageJID("testling@test.com/resource1"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->sentStanzas.size()); - - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); - message->setID("2"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); - CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); - } - - /** - * Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both. - */ - void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() { - testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both); - } - - /** - * Test that ChatController sends receipts if requested after change from subscription state To to subscription state From. - */ - void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() { - testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From); - } - - void testChatControllerFullJIDBindingOnMessageAndNotReceipt() { - JID ownJID("test@test.com/resource"); - JID sender("foo@test.com"); - std::vector<JID> senderResource; - senderResource.push_back(sender.withResource("resourceA")); - senderResource.push_back(sender.withResource("resourceB")); - - // We support delivery receipts. - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - // Open chat window to a sender. - MockChatWindow* window = new MockChatWindow(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); - - uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(sender)); - - foreach(const JID& senderJID, senderResource) { - // The sender supports delivery receipts. - DiscoInfo::ref disco = boost::make_shared<DiscoInfo>(); - disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); - entityCapsProvider_->caps[senderJID] = disco; - - // The sender is online. - Presence::ref senderPresence = boost::make_shared<Presence>(); - senderPresence->setFrom(senderJID); - senderPresence->setTo(ownJID); - stanzaChannel_->onPresenceReceived(senderPresence); - - entityCapsProvider_->onCapsChanged(senderJID); - } - - // Send first message. - window->onSendMessageRequest("hello there", false); - - // A bare message is send because no resources is bound. - CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(0)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(0)->getPayload<DeliveryReceiptRequest>()); - - // Two resources respond with message receipts. - foreach(const JID& senderJID, senderResource) { - Message::ref receiptReply = boost::make_shared<Message>(); - receiptReply->setFrom(senderJID); - receiptReply->setTo(ownJID); - - boost::shared_ptr<DeliveryReceipt> receipt = boost::make_shared<DeliveryReceipt>(); - receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(0)->getID()); - receiptReply->addPayload(receipt); - manager_->handleIncomingMessage(receiptReply); - } - - // Send second message. - window->onSendMessageRequest("how are you?", false); - - // A bare message is send because no resources is bound. - CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); - - // Two resources respond with message receipts. - foreach(const JID& senderJID, senderResource) { - Message::ref receiptReply = boost::make_shared<Message>(); - receiptReply->setFrom(senderJID); - receiptReply->setTo(ownJID); - - boost::shared_ptr<DeliveryReceipt> receipt = boost::make_shared<DeliveryReceipt>(); - receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); - receiptReply->addPayload(receipt); - manager_->handleIncomingMessage(receiptReply); - } - - // Reply with a message including a body text. - Message::ref reply = boost::make_shared<Message>(); - reply->setFrom(senderResource[0]); - reply->setTo(ownJID); - reply->setBody("fine."); - manager_->handleIncomingMessage(reply); - - // Send third message. - window->onSendMessageRequest("great to hear.", false); - - // The chat session is bound to the full JID of the first resource. - CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(2)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(2)->getPayload<DeliveryReceiptRequest>()); - - // Receive random receipt from second sender resource. - reply = boost::make_shared<Message>(); - reply->setFrom(senderResource[1]); - reply->setTo(ownJID); - - boost::shared_ptr<DeliveryReceipt> receipt = boost::make_shared<DeliveryReceipt>(); - receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(2)->getID()); - reply->addPayload(receipt); - manager_->handleIncomingMessage(reply); - - // Send forth message. - window->onSendMessageRequest("what else is new?", false); - - // The chat session is bound to the full JID of the first resource. - CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>()); - - // Reply with a message including a body text from second resource. - reply = boost::make_shared<Message>(); - reply->setFrom(senderResource[1]); - reply->setTo(ownJID); - reply->setBody("nothing."); - manager_->handleIncomingMessage(reply); - - // Send fifth message. - window->onSendMessageRequest("okay", false); - - // The chat session is now bound to the full JID of the second resource. - CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(4)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(4)->getPayload<DeliveryReceiptRequest>()); - } - - void testChatControllerFullJIDBindingOnTypingAndNotActive() { - JID ownJID("test@test.com/resource"); - JID sender("foo@test.com"); - std::vector<JID> senderResource; - senderResource.push_back(sender.withResource("resourceA")); - senderResource.push_back(sender.withResource("resourceB")); - - // We support delivery receipts. - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - // Open chat window to a sender. - MockChatWindow* window = new MockChatWindow(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); - - uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(sender)); - - foreach(const JID& senderJID, senderResource) { - // The sender supports delivery receipts. - DiscoInfo::ref disco = boost::make_shared<DiscoInfo>(); - disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); - entityCapsProvider_->caps[senderJID] = disco; - - // The sender is online. - Presence::ref senderPresence = boost::make_shared<Presence>(); - senderPresence->setFrom(senderJID); - senderPresence->setTo(ownJID); - stanzaChannel_->onPresenceReceived(senderPresence); - - entityCapsProvider_->onCapsChanged(senderJID); - } - - // Send first message. - window->onSendMessageRequest("hello there", false); - - // A bare message is send because no resources is bound. - CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(0)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(0)->getPayload<DeliveryReceiptRequest>()); - - // Two resources respond with message receipts. - foreach(const JID& senderJID, senderResource) { - Message::ref reply = boost::make_shared<Message>(); - reply->setFrom(senderJID); - reply->setTo(ownJID); - - boost::shared_ptr<ChatState> csn = boost::make_shared<ChatState>(); - csn->setChatState(ChatState::Active); - reply->addPayload(csn); - manager_->handleIncomingMessage(reply); - } - - // Send second message. - window->onSendMessageRequest("how are you?", false); - - // A bare message is send because no resources is bound. - CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); - - // Two resources respond with message receipts. - foreach(const JID& senderJID, senderResource) { - Message::ref receiptReply = boost::make_shared<Message>(); - receiptReply->setFrom(senderJID); - receiptReply->setTo(ownJID); - - boost::shared_ptr<DeliveryReceipt> receipt = boost::make_shared<DeliveryReceipt>(); - receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); - receiptReply->addPayload(receipt); - manager_->handleIncomingMessage(receiptReply); - } - - // Reply with a message including a CSN. - Message::ref reply = boost::make_shared<Message>(); - reply->setFrom(senderResource[0]); - reply->setTo(ownJID); - - boost::shared_ptr<ChatState> csn = boost::make_shared<ChatState>(); - csn->setChatState(ChatState::Composing); - reply->addPayload(csn); - manager_->handleIncomingMessage(reply); - - // Send third message. - window->onSendMessageRequest("great to hear.", false); - - // The chat session is now bound to the full JID of the first resource due to its recent composing message. - CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(2)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(2)->getPayload<DeliveryReceiptRequest>()); - - // Reply with a message including a CSN from the other resource. - reply = boost::make_shared<Message>(); - reply->setFrom(senderResource[1]); - reply->setTo(ownJID); - - csn = boost::make_shared<ChatState>(); - csn->setChatState(ChatState::Composing); - reply->addPayload(csn); - manager_->handleIncomingMessage(reply); - - // Send third message. - window->onSendMessageRequest("ping.", false); - - // The chat session is now bound to the full JID of the second resource due to its recent composing message. - CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); - CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>()); - } - - void testChatControllerPMPresenceHandling() { - JID participantA = JID("test@rooms.test.com/participantA"); - JID participantB = JID("test@rooms.test.com/participantB"); - - mucRegistry_->addMUC("test@rooms.test.com"); - - MockChatWindow* window = new MockChatWindow(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(participantA)))); - - Presence::ref presence = Presence::create(); - presence->setFrom(participantA); - presence->setShow(StatusShow::Online); - stanzaChannel_->onPresenceReceived(presence); - CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); - - presence = Presence::create(); - presence->setFrom(participantB); - presence->setShow(StatusShow::Away); - stanzaChannel_->onPresenceReceived(presence); - - presence = Presence::create(); - presence->setFrom(participantA); - presence->setShow(StatusShow::None); - presence->setType(Presence::Unavailable); - stanzaChannel_->onPresenceReceived(presence); - CPPUNIT_ASSERT_EQUAL(std::string("participantA has gone offline."), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); - } - - void testLocalMUCServiceDiscoveryResetOnDisconnect() { - JID ownJID("test@test.com/resource"); - JID sender("foo@test.com"); - - manager_->setOnline(true); - - // Open chat window to a sender. - MockChatWindow* window = new MockChatWindow(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); - - uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(sender)); - - CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_); - - boost::shared_ptr<IQ> infoRequest= iqChannel_->iqs_[1]; - boost::shared_ptr<IQ> infoResponse = IQ::createResult(infoRequest->getFrom(), infoRequest->getTo(), infoRequest->getID()); - - DiscoInfo info; - info.addIdentity(DiscoInfo::Identity("Shakespearean Chat Service", "conference", "text")); - info.addFeature("http://jabber.org/protocol/muc"); - infoResponse->addPayload(boost::make_shared<DiscoInfo>(info)); - iqChannel_->onIQReceived(infoResponse); - - CPPUNIT_ASSERT_EQUAL(true, window->impromptuMUCSupported_); - manager_->setOnline(false); - CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_); - } - - void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) { - JID messageJID("testling@test.com/resource1"); - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->sentStanzas.size()); - - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to); - message->setID("2"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); - CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); - } + void setUp() { + mocks_ = new MockRepository(); + jid_ = JID("test@test.com/resource"); + stanzaChannel_ = new DummyStanzaChannel(); + iqRouter_ = new IQRouter(stanzaChannel_); + eventController_ = new EventController(); + chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); + joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>(); + xmppRoster_ = new XMPPRosterImpl(); + mucRegistry_ = new MUCRegistry(); + nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, nullptr, mucRegistry_); + presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); + serverDiscoInfo_ = std::make_shared<DiscoInfo>(); + presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); + directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); + mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_); + uiEventStream_ = new UIEventStream(); + entityCapsProvider_ = new DummyEntityCapsProvider(); + chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>(); + mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>(); + settings_ = new DummySettingsProvider(); + profileSettings_ = new ProfileSettingsProvider("a", settings_); + chatListWindow_ = new MockChatListWindow(); + ftManager_ = new DummyFileTransferManager(); + ftOverview_ = new FileTransferOverview(ftManager_); + avatarManager_ = new NullAvatarManager(); + wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsProvider_); + wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); + highlightManager_ = new HighlightManager(settings_); + highlightManager_->resetToDefaultRulesList(); + handledHighlightActions_ = 0; + soundsPlayed_.clear(); + highlightManager_->onHighlight.connect(boost::bind(&ChatsManagerTest::handleHighlightAction, this, _1)); + + crypto_ = PlatformCryptoProvider::create(); + vcardStorage_ = new VCardMemoryStorage(crypto_); + vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_); + mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, nullptr, mucRegistry_, entityCapsProvider_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, nullptr, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, vcardManager_); + + manager_->setAvatarManager(avatarManager_); + } + + void tearDown() { + delete highlightManager_; + delete profileSettings_; + delete avatarManager_; + delete manager_; + delete clientBlockListManager_; + delete vcardManager_; + delete vcardStorage_; + delete crypto_; + delete ftOverview_; + delete ftManager_; + delete wbSessionManager_; + delete wbManager_; + delete directedPresenceSender_; + delete presenceSender_; + delete presenceOracle_; + delete nickResolver_; + delete mucRegistry_; + delete iqRouter_; + delete stanzaChannel_; + delete eventController_; + delete uiEventStream_; + delete mucManager_; + delete xmppRoster_; + delete entityCapsProvider_; + delete chatListWindow_; + delete mocks_; + delete settings_; + } + + void testFirstOpenWindowIncoming() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This is a legible message. >HEH@)oeueu"); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + } + + void testSecondOpenWindowIncoming() { + JID messageJID1("testling@test.com/resource1"); + + MockChatWindow* window1 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); + + std::shared_ptr<Message> message1(new Message()); + message1->setFrom(messageJID1); + std::string body1("This is a legible message. >HEH@)oeueu"); + message1->setBody(body1); + manager_->handleIncomingMessage(message1); + CPPUNIT_ASSERT_EQUAL(body1, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + + JID messageJID2("testling@test.com/resource2"); + + std::shared_ptr<Message> message2(new Message()); + message2->setFrom(messageJID2); + std::string body2("This is a legible message. .cmaulm.chul"); + message2->setBody(body2); + manager_->handleIncomingMessage(message2); + CPPUNIT_ASSERT_EQUAL(body2, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + } + + void testFirstOpenWindowOutgoing() { + std::string messageJIDString("testling@test.com"); + + ChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString), uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString))); + } + + + void testFirstOpenWindowBareToFull() { + std::string bareJIDString("testling@test.com"); + std::string fullJIDString("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(bareJIDString))); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(JID(fullJIDString)); + std::string body("This is a legible message. mjuga3089gm8G(*>M)@*("); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + } + + void testSecondWindow() { + std::string messageJIDString1("testling1@test.com"); + ChatWindow* window1 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1))); + + std::string messageJIDString2("testling2@test.com"); + ChatWindow* window2 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString2))); + } + + /** Complete cycle. + Create unbound window. + Bind it. + Unbind it. + Rebind it. + */ + void testUnbindRebind() { + std::string bareJIDString("testling@test.com"); + std::string fullJIDString1("testling@test.com/resource1"); + std::string fullJIDString2("testling@test.com/resource2"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(bareJIDString))); + + std::shared_ptr<Message> message1(new Message()); + message1->setFrom(JID(fullJIDString1)); + std::string messageBody1("This is a legible message."); + message1->setBody(messageBody1); + manager_->handleIncomingMessage(message1); + CPPUNIT_ASSERT_EQUAL(messageBody1, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + std::shared_ptr<Presence> jid1Online(new Presence()); + jid1Online->setFrom(JID(fullJIDString1)); + std::shared_ptr<Presence> jid1Offline(new Presence()); + jid1Offline->setFrom(JID(fullJIDString1)); + jid1Offline->setType(Presence::Unavailable); + presenceOracle_->onPresenceChange(jid1Offline); + + std::shared_ptr<Message> message2(new Message()); + message2->setFrom(JID(fullJIDString2)); + std::string messageBody2("This is another legible message."); + message2->setBody(messageBody2); + manager_->handleIncomingMessage(message2); + CPPUNIT_ASSERT_EQUAL(messageBody2, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + } + + /** + * Test that MUC PMs get opened in the right windows + */ + void testThreeMUCWindows() { + JID muc("testling@test.com"); + ChatWindow* mucWindow = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc, uiEventStream_).Return(mucWindow); + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(muc, std::string("nick"))); + + + std::string messageJIDString1("testling@test.com/1"); + ChatWindow* window1 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1))); + + std::string messageJIDString2("testling@test.com/2"); + ChatWindow* window2 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString2))); + + std::string messageJIDString3("testling@test.com/3"); + ChatWindow* window3 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString3), uiEventStream_).Return(window3); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString3))); + + /* Refetch an earlier window */ + /* We do not expect a new window to be created */ + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1))); + + } + + /** + Test that a second window isn't unbound where there's already an unbound one. + Bind 1 + Bind 2 + Unbind 1 + Unbind 2 (but it doesn't) + Sent to bound 2 + Rebind 1 + */ + void testNoDuplicateUnbind() { + JID messageJID1("testling@test.com/resource1"); + + MockChatWindow* window1 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); + + std::shared_ptr<Message> message1(new Message()); + message1->setFrom(messageJID1); + message1->setBody("This is a legible message1."); + manager_->handleIncomingMessage(message1); + + JID messageJID2("testling@test.com/resource2"); + + std::shared_ptr<Message> message2(new Message()); + message2->setFrom(messageJID2); + message2->setBody("This is a legible message2."); + manager_->handleIncomingMessage(message2); + + std::shared_ptr<Presence> jid1Online(new Presence()); + jid1Online->setFrom(JID(messageJID1)); + std::shared_ptr<Presence> jid1Offline(new Presence()); + jid1Offline->setFrom(JID(messageJID1)); + jid1Offline->setType(Presence::Unavailable); + presenceOracle_->onPresenceChange(jid1Offline); + + std::shared_ptr<Presence> jid2Online(new Presence()); + jid2Online->setFrom(JID(messageJID2)); + std::shared_ptr<Presence> jid2Offline(new Presence()); + jid2Offline->setFrom(JID(messageJID2)); + jid2Offline->setType(Presence::Unavailable); + presenceOracle_->onPresenceChange(jid2Offline); + + JID messageJID3("testling@test.com/resource3"); + + std::shared_ptr<Message> message3(new Message()); + message3->setFrom(messageJID3); + std::string body3("This is a legible message3."); + message3->setBody(body3); + manager_->handleIncomingMessage(message3); + CPPUNIT_ASSERT_EQUAL(body3, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + + std::shared_ptr<Message> message2b(new Message()); + message2b->setFrom(messageJID2); + std::string body2b("This is a legible message2b."); + message2b->setBody(body2b); + manager_->handleIncomingMessage(message2b); + CPPUNIT_ASSERT_EQUAL(body2b, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + } + + /** + * Test that ChatController doesn't send receipts anymore after removal of the contact from the roster. + */ + void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() { + JID messageJID("testling@test.com/resource1"); + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); + + MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1); + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr); + + xmppRoster_->removeContact(messageJID); + + message->setID("2"); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + } + + /** + * Test that ChatController sends receipts after the contact has been added to the roster. + */ + void testChatControllerPresenceAccessUpdatedOnAddToRoster() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->countSentStanzaOfType<Message>()); + + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); + message->setID("2"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr); + } + + /** + * Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both. + */ + void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() { + testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both); + } + + /** + * Test that ChatController sends receipts if requested after change from subscription state To to subscription state From. + */ + void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() { + testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From); + } + + void testChatControllerFullJIDBindingOnMessageAndNotReceipt() { + JID ownJID("test@test.com/resource"); + JID sender("foo@test.com"); + std::vector<JID> senderResource; + senderResource.push_back(sender.withResource("resourceA")); + senderResource.push_back(sender.withResource("resourceB")); + + // We support delivery receipts. + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender)); + + for (const auto& senderJID : senderResource) { + // The sender supports delivery receipts. + DiscoInfo::ref disco = std::make_shared<DiscoInfo>(); + disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); + entityCapsProvider_->caps[senderJID] = disco; + + // The sender is online. + Presence::ref senderPresence = std::make_shared<Presence>(); + senderPresence->setFrom(senderJID); + senderPresence->setTo(ownJID); + stanzaChannel_->onPresenceReceived(senderPresence); + + entityCapsProvider_->onCapsChanged(senderJID); + } + + // Send first message. + window->onSendMessageRequest("hello there", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref receiptReply = std::make_shared<Message>(); + receiptReply->setFrom(senderJID); + receiptReply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); + receiptReply->addPayload(receipt); + manager_->handleIncomingMessage(receiptReply); + } + + // Send second message. + window->onSendMessageRequest("how are you?", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref receiptReply = std::make_shared<Message>(); + receiptReply->setFrom(senderJID); + receiptReply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); + receiptReply->addPayload(receipt); + manager_->handleIncomingMessage(receiptReply); + } + + // Reply with a message including a body text. + Message::ref reply = std::make_shared<Message>(); + reply->setFrom(senderResource[0]); + reply->setTo(ownJID); + reply->setBody("fine."); + manager_->handleIncomingMessage(reply); + + // Send third message. + window->onSendMessageRequest("great to hear.", false); + + // The chat session is bound to the full JID of the first resource. + CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(2)->getPayload<DeliveryReceiptRequest>()); + + // Receive random receipt from second sender resource. + reply = std::make_shared<Message>(); + reply->setFrom(senderResource[1]); + reply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(2)->getID()); + reply->addPayload(receipt); + manager_->handleIncomingMessage(reply); + + // Send forth message. + window->onSendMessageRequest("what else is new?", false); + + // The chat session is bound to the full JID of the first resource. + CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>()); + + // Reply with a message including a body text from second resource. + reply = std::make_shared<Message>(); + reply->setFrom(senderResource[1]); + reply->setTo(ownJID); + reply->setBody("nothing."); + manager_->handleIncomingMessage(reply); + + // Send fifth message. + window->onSendMessageRequest("okay", false); + + // The chat session is now bound to the full JID of the second resource. + CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(5)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(5)->getPayload<DeliveryReceiptRequest>()); + } + + void testChatControllerFullJIDBindingOnTypingAndNotActive() { + JID ownJID("test@test.com/resource"); + JID sender("foo@test.com"); + std::vector<JID> senderResource; + senderResource.push_back(sender.withResource("resourceA")); + senderResource.push_back(sender.withResource("resourceB")); + + // We support delivery receipts. + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender)); + + for (const auto& senderJID : senderResource) { + // The sender supports delivery receipts. + DiscoInfo::ref disco = std::make_shared<DiscoInfo>(); + disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); + entityCapsProvider_->caps[senderJID] = disco; + + // The sender is online. + Presence::ref senderPresence = std::make_shared<Presence>(); + senderPresence->setFrom(senderJID); + senderPresence->setTo(ownJID); + stanzaChannel_->onPresenceReceived(senderPresence); + + entityCapsProvider_->onCapsChanged(senderJID); + } + + // Send first message. + window->onSendMessageRequest("hello there", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref reply = std::make_shared<Message>(); + reply->setFrom(senderJID); + reply->setTo(ownJID); + + std::shared_ptr<ChatState> csn = std::make_shared<ChatState>(); + csn->setChatState(ChatState::Active); + reply->addPayload(csn); + manager_->handleIncomingMessage(reply); + } + + // Send second message. + window->onSendMessageRequest("how are you?", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref receiptReply = std::make_shared<Message>(); + receiptReply->setFrom(senderJID); + receiptReply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); + receiptReply->addPayload(receipt); + manager_->handleIncomingMessage(receiptReply); + } + + // Reply with a message including a CSN. + Message::ref reply = std::make_shared<Message>(); + reply->setFrom(senderResource[0]); + reply->setTo(ownJID); + + std::shared_ptr<ChatState> csn = std::make_shared<ChatState>(); + csn->setChatState(ChatState::Composing); + reply->addPayload(csn); + manager_->handleIncomingMessage(reply); + + // Send third message. + window->onSendMessageRequest("great to hear.", false); + + // The chat session is now bound to the full JID of the first resource due to its recent composing message. + CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>()); + + // Reply with a message including a CSN from the other resource. + reply = std::make_shared<Message>(); + reply->setFrom(senderResource[1]); + reply->setTo(ownJID); + + csn = std::make_shared<ChatState>(); + csn->setChatState(ChatState::Composing); + reply->addPayload(csn); + manager_->handleIncomingMessage(reply); + + // Send third message. + window->onSendMessageRequest("ping.", false); + + // The chat session is now bound to the full JID of the second resource due to its recent composing message. + CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(4)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(4)->getPayload<DeliveryReceiptRequest>()); + } + + void testChatControllerPMPresenceHandling() { + JID participantA = JID("test@rooms.test.com/participantA"); + JID participantB = JID("test@rooms.test.com/participantB"); + + mucRegistry_->addMUC("test@rooms.test.com"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(participantA))); + + Presence::ref presence = Presence::create(); + presence->setFrom(participantA); + presence->setShow(StatusShow::Online); + stanzaChannel_->onPresenceReceived(presence); + CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); + + presence = Presence::create(); + presence->setFrom(participantB); + presence->setShow(StatusShow::Away); + stanzaChannel_->onPresenceReceived(presence); + + presence = Presence::create(); + presence->setFrom(participantA); + presence->setShow(StatusShow::None); + presence->setType(Presence::Unavailable); + stanzaChannel_->onPresenceReceived(presence); + CPPUNIT_ASSERT_EQUAL(std::string("participantA has gone offline."), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); + } + + void testChatControllerMucPmUnavailableErrorHandling() { + auto mucJID = JID("test@rooms.test.com"); + auto participantA = mucJID.withResource("participantA"); + auto participantB = mucJID.withResource("participantB"); + + auto mucWindow = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(mucWindow); + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, participantB.getResource())); + CPPUNIT_ASSERT_EQUAL(true, mucWindow->mucType_.is_initialized()); + + auto window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(participantA)); + CPPUNIT_ASSERT_EQUAL(false, window->mucType_.is_initialized()); + + Presence::ref presence = Presence::create(); + presence->setFrom(participantA); + presence->setShow(StatusShow::Online); + stanzaChannel_->onPresenceReceived(presence); + CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); + + // send message to participantA + auto messageBody = std::string("message body to send"); + window->onSendMessageRequest(messageBody, false); + auto sendMessageStanza = stanzaChannel_->getStanzaAtIndex<Message>(2); + CPPUNIT_ASSERT_EQUAL(messageBody, *sendMessageStanza->getBody()); + + // receive reply with error + auto messageErrorReply = std::make_shared<Message>(); + messageErrorReply->setID(stanzaChannel_->getNewIQID()); + messageErrorReply->setType(Message::Error); + messageErrorReply->setFrom(participantA); + messageErrorReply->setTo(jid_); + messageErrorReply->addPayload(std::make_shared<ErrorPayload>(ErrorPayload::ItemNotFound, ErrorPayload::Cancel, "Recipient not in room")); + + auto lastMUCWindowErrorMessageBeforeError = MockChatWindow::bodyFromMessage(mucWindow->lastAddedErrorMessage_); + manager_->handleIncomingMessage(messageErrorReply); + + // assert that error is not routed to MUC window + CPPUNIT_ASSERT_EQUAL(lastMUCWindowErrorMessageBeforeError, MockChatWindow::bodyFromMessage(mucWindow->lastAddedErrorMessage_)); + // assert that error is routed to PM + CPPUNIT_ASSERT_EQUAL(std::string("This user could not be found in the room."), MockChatWindow::bodyFromMessage(window->lastAddedErrorMessage_)); + } + + void testLocalMUCServiceDiscoveryResetOnDisconnect() { + JID ownJID("test@test.com/resource"); + JID sender("foo@test.com"); + + manager_->setOnline(true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender)); + + CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_); + + std::shared_ptr<IQ> infoRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]); + std::shared_ptr<IQ> infoResponse = IQ::createResult(infoRequest->getFrom(), infoRequest->getTo(), infoRequest->getID()); + + DiscoInfo info; + info.addIdentity(DiscoInfo::Identity("Shakespearean Chat Service", "conference", "text")); + info.addFeature("http://jabber.org/protocol/muc"); + infoResponse->addPayload(std::make_shared<DiscoInfo>(info)); + stanzaChannel_->onIQReceived(infoResponse); + + CPPUNIT_ASSERT_EQUAL(true, window->impromptuMUCSupported_); + manager_->setOnline(false); + CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_); + } + + void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) { + JID messageJID("testling@test.com/resource1"); + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from); + + MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->countSentStanzaOfType<Message>()); + + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to); + message->setID("2"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr); + } + + void testChatControllerHighlightingNotificationTesting() { + HighlightRule keywordRuleA; + keywordRuleA.setMatchChat(true); + std::vector<std::string> keywordsA; + keywordsA.push_back("Romeo"); + keywordRuleA.setKeywords(keywordsA); + keywordRuleA.getAction().setTextColor("yellow"); + keywordRuleA.getAction().setPlaySound(true); + highlightManager_->insertRule(0, keywordRuleA); + + HighlightRule keywordRuleB; + keywordRuleB.setMatchChat(true); + std::vector<std::string> keywordsB; + keywordsB.push_back("Juliet"); + keywordRuleB.setKeywords(keywordsB); + keywordRuleB.getAction().setTextColor("green"); + keywordRuleB.getAction().setPlaySound(true); + keywordRuleB.getAction().setSoundFile("/tmp/someotherfile.wav"); + highlightManager_->insertRule(0, keywordRuleB); + + JID messageJID = JID("testling@test.com"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This message should cause two sounds: Juliet and Romeo."); + message->setBody(body); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(2, handledHighlightActions_); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.getAction().getSoundFile()) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.getAction().getSoundFile()) != soundsPlayed_.end()); + } + + void testChatControllerHighlightingNotificationDeduplicateSounds() { + HighlightRule keywordRuleA; + keywordRuleA.setMatchChat(true); + std::vector<std::string> keywordsA; + keywordsA.push_back("Romeo"); + keywordRuleA.setKeywords(keywordsA); + keywordRuleA.getAction().setTextColor("yellow"); + keywordRuleA.getAction().setPlaySound(true); + highlightManager_->insertRule(0, keywordRuleA); + + HighlightRule keywordRuleB; + keywordRuleB.setMatchChat(true); + std::vector<std::string> keywordsB; + keywordsB.push_back("Juliet"); + keywordRuleB.setKeywords(keywordsB); + keywordRuleB.getAction().setTextColor("green"); + keywordRuleB.getAction().setPlaySound(true); + highlightManager_->insertRule(0, keywordRuleB); + + JID messageJID = JID("testling@test.com"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This message should cause one sound, because both actions have the same sound: Juliet and Romeo."); + message->setBody(body); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(1, handledHighlightActions_); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.getAction().getSoundFile()) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.getAction().getSoundFile()) != soundsPlayed_.end()); + } + + void testChatControllerMeMessageHandling() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("/me is feeling delighted."); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(std::string("is feeling delighted."), window->bodyFromMessage(window->lastAddedAction_)); + } + + void testRestartingMUCComponentCrash() { + JID mucJID = JID("teaparty@rooms.wonderland.lit"); + JID self = JID("girl@wonderland.lit/rabbithole"); + std::string nick = "aLiCe"; + + MockChatWindow* window; + + auto genRemoteMUCPresence = [=]() { + auto presence = Presence::create(); + presence->setFrom(mucJID.withResource(nick)); + presence->setTo(self); + return presence; + }; + + // User rejoins. + window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + // Join room + { + auto joinRoomEvent = std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nick); + uiEventStream_->send(joinRoomEvent); + } + + { + auto firstPresence = genRemoteMUCPresence(); + firstPresence->setType(Presence::Unavailable); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addItem(MUCItem(MUCOccupant::Owner, MUCOccupant::NoRole)); + firstPresence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(firstPresence); + } + CPPUNIT_ASSERT_EQUAL(std::string("Couldn't enter room: Unable to enter this room."), MockChatWindow::bodyFromMessage(window->lastAddedErrorMessage_)); + + { + auto presence = genRemoteMUCPresence(); + presence->setType(Presence::Unavailable); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addStatusCode(303); + auto item = MUCItem(MUCOccupant::Owner, self, MUCOccupant::Moderator); + item.nick = nick; + userPayload->addItem(item); + userPayload->addStatusCode(110); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + } + + void testChatControllerMeMessageHandlingInMUC() { + JID mucJID("mucroom@rooms.test.com"); + std::string nickname = "toodles"; + + // add highlight rule for 'foo' + HighlightRule fooHighlight; + fooHighlight.setKeywords({"foo"}); + fooHighlight.setMatchMUC(true); + fooHighlight.getAction().setTextBackground("green"); + highlightManager_->insertRule(0, fooHighlight); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname)); + + auto genRemoteMUCPresence = [=]() { + auto presence = Presence::create(); + presence->setFrom(mucJID.withResource(nickname)); + presence->setTo(jid_); + return presence; + }; + + { + auto presence = genRemoteMUCPresence(); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addStatusCode(110); + userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator)); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + + { + auto presence = genRemoteMUCPresence(); + presence->setFrom(mucJID.withResource("someDifferentNickname")); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addItem(MUCItem(MUCOccupant::Member, JID("foo@bar.com"), MUCOccupant::Moderator)); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + + window->onSendMessageRequest("/me sends a test message with foo", false); + + window->resetLastMessages(); + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource(nickname)); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setBody("/me sends a test message with foo"); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("sends a test message with foo"), window->bodyFromMessage(window->lastAddedAction_)); + + window->resetLastMessages(); + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource("someDifferentNickname")); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setBody("/me says hello with a test message with foo and foo"); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("says hello with a test message with foo and foo"), window->bodyFromMessage(window->lastAddedAction_)); + } + + void testPresenceChangeDoesNotReplaceMUCInvite() { + JID messageJID("testling@test.com/resource1"); + + auto generateIncomingPresence = [=](Presence::Type type) { + auto presence = std::make_shared<Presence>(); + presence->setType(type); + presence->setFrom(messageJID); + presence->setTo(jid_); + return presence; + }; + + stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available)); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This is a legible message. >HEH@)oeueu"); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + auto incomingMUCInvite = std::make_shared<Message>(); + incomingMUCInvite->setFrom(messageJID); + + auto invitePayload = std::make_shared<MUCInvitationPayload>(); + invitePayload->setJID("room@muc.service.com"); + incomingMUCInvite->addPayload(invitePayload); + + stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable)); + stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available)); + + window->resetLastMessages(); + + manager_->handleIncomingMessage(incomingMUCInvite); + CPPUNIT_ASSERT_EQUAL(JID("room@muc.service.com"), window->lastMUCInvitationJID_); + + stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable)); + CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); + CPPUNIT_ASSERT_EQUAL(std::string("testling@test.com has gone offline."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); + } + + template <typename CarbonsType> + Message::ref createCarbonsMessage(std::shared_ptr<CarbonsType> carbons, std::shared_ptr<Message> forwardedMessage) { + auto messageWrapper = std::make_shared<Message>(); + messageWrapper->setFrom(jid_.toBare()); + messageWrapper->setTo(jid_); + messageWrapper->setType(Message::Chat); + + messageWrapper->addPayload(carbons); + auto forwarded = std::make_shared<Forwarded>(); + carbons->setForwarded(forwarded); + forwarded->setStanza(forwardedMessage); + return messageWrapper; + } + + void testCarbonsForwardedIncomingMessageToSecondResource() { + JID messageJID("testling@test.com/resource1"); + JID jid2 = jid_.toBare().withResource("someOtherResource"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This is a legible message. >HEH@)oeueu"); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + // incoming carbons message from another resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(messageJID); + originalMessage->setTo(jid2); + originalMessage->setType(Message::Chat); + std::string forwardedBody = "Some further text."; + originalMessage->setBody(forwardedBody); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_); + } + } + + void testCarbonsForwardedOutgoingMessageFromSecondResource() { + JID messageJID("testling@test.com/resource1"); + JID jid2 = jid_.toBare().withResource("someOtherResource"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This is a legible message. >HEH@)oeueu"); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + // incoming carbons message from another resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(jid2); + originalMessage->setTo(messageJID); + originalMessage->setType(Message::Chat); + originalMessage->setID("abcdefg123456"); + std::string forwardedBody = "Some text my other resource sent."; + originalMessage->setBody(forwardedBody); + originalMessage->addPayload(std::make_shared<DeliveryReceiptRequest>()); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsSent>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(true, window->lastAddedMessageSenderIsSelf_); + CPPUNIT_ASSERT_EQUAL(size_t(1), window->receiptChanges_.size()); + CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptRequested, window->receiptChanges_[0].second); + } + + // incoming carbons message for the received delivery receipt to the other resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(messageJID); + originalMessage->setTo(jid2); + originalMessage->setType(Message::Chat); + originalMessage->addPayload(std::make_shared<DeliveryReceipt>("abcdefg123456")); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(size_t(2), window->receiptChanges_.size()); + CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptReceived, window->receiptChanges_[1].second); + } + } private: - boost::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) { - boost::shared_ptr<Message> message = boost::make_shared<Message>(); - message->setFrom(from); - message->setID(id); - message->setBody("This will cause the window to open"); - message->addPayload(boost::make_shared<DeliveryReceiptRequest>()); - return message; - } - - size_t st(int i) { - return static_cast<size_t>(i); - } + std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) { + std::shared_ptr<Message> message = std::make_shared<Message>(); + message->setFrom(from); + message->setID(id); + message->setBody("This will cause the window to open"); + message->addPayload(std::make_shared<DeliveryReceiptRequest>()); + return message; + } + + size_t st(int i) { + return static_cast<size_t>(i); + } + + void handleHighlightAction(const HighlightAction& action) { + handledHighlightActions_++; + if (action.playSound()) { + soundsPlayed_.insert(action.getSoundFile()); + } + } private: - JID jid_; - ChatsManager* manager_; - DummyStanzaChannel* stanzaChannel_; - DummyIQChannel* iqChannel_; - IQRouter* iqRouter_; - EventController* eventController_; - ChatWindowFactory* chatWindowFactory_; - JoinMUCWindowFactory* joinMUCWindowFactory_; - NickResolver* nickResolver_; - PresenceOracle* presenceOracle_; - AvatarManager* avatarManager_; - boost::shared_ptr<DiscoInfo> serverDiscoInfo_; - XMPPRosterImpl* xmppRoster_; - PresenceSender* presenceSender_; - MockRepository* mocks_; - UIEventStream* uiEventStream_; - ChatListWindowFactory* chatListWindowFactory_; - WhiteboardWindowFactory* whiteboardWindowFactory_; - MUCSearchWindowFactory* mucSearchWindowFactory_; - MUCRegistry* mucRegistry_; - DirectedPresenceSender* directedPresenceSender_; - DummyEntityCapsProvider* entityCapsProvider_; - MUCManager* mucManager_; - DummySettingsProvider* settings_; - ProfileSettingsProvider* profileSettings_; - ChatListWindow* chatListWindow_; - FileTransferOverview* ftOverview_; - FileTransferManager* ftManager_; - WhiteboardSessionManager* wbSessionManager_; - WhiteboardManager* wbManager_; - HighlightManager* highlightManager_; - ClientBlockListManager* clientBlockListManager_; - VCardManager* vcardManager_; - CryptoProvider* crypto_; - VCardStorage* vcardStorage_; - std::map<std::string, std::string> emoticons_; + JID jid_; + ChatsManager* manager_; + DummyStanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + EventController* eventController_; + ChatWindowFactory* chatWindowFactory_; + JoinMUCWindowFactory* joinMUCWindowFactory_; + NickResolver* nickResolver_; + PresenceOracle* presenceOracle_; + AvatarManager* avatarManager_; + std::shared_ptr<DiscoInfo> serverDiscoInfo_; + XMPPRosterImpl* xmppRoster_; + PresenceSender* presenceSender_; + MockRepository* mocks_; + UIEventStream* uiEventStream_; + ChatListWindowFactory* chatListWindowFactory_; + WhiteboardWindowFactory* whiteboardWindowFactory_; + MUCSearchWindowFactory* mucSearchWindowFactory_; + MUCRegistry* mucRegistry_; + DirectedPresenceSender* directedPresenceSender_; + DummyEntityCapsProvider* entityCapsProvider_; + MUCManager* mucManager_; + DummySettingsProvider* settings_; + ProfileSettingsProvider* profileSettings_; + ChatListWindow* chatListWindow_; + FileTransferOverview* ftOverview_; + FileTransferManager* ftManager_; + WhiteboardSessionManager* wbSessionManager_; + WhiteboardManager* wbManager_; + HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; + VCardManager* vcardManager_; + CryptoProvider* crypto_; + VCardStorage* vcardStorage_; + std::map<std::string, std::string> emoticons_; + int handledHighlightActions_; + std::set<std::string> soundsPlayed_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index e8fc41d..32639f6 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -11,7 +11,6 @@ #include <hippomocks.h> #include <Swiften/Avatars/NullAvatarManager.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/DummyStanzaChannel.h> #include <Swiften/Client/NickResolver.h> @@ -48,509 +47,508 @@ using namespace Swift; class MUCControllerTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(MUCControllerTest); - CPPUNIT_TEST(testJoinPartStringContructionSimple); - CPPUNIT_TEST(testJoinPartStringContructionMixed); - CPPUNIT_TEST(testAppendToJoinParts); - CPPUNIT_TEST(testAddressedToSelf); - CPPUNIT_TEST(testNotAddressedToSelf); - CPPUNIT_TEST(testAddressedToSelfBySelf); - CPPUNIT_TEST(testMessageWithEmptyLabelItem); - CPPUNIT_TEST(testMessageWithLabelItem); - CPPUNIT_TEST(testCorrectMessageWithLabelItem); - CPPUNIT_TEST(testRoleAffiliationStates); - CPPUNIT_TEST(testSubjectChangeCorrect); - CPPUNIT_TEST(testSubjectChangeIncorrectA); - CPPUNIT_TEST(testSubjectChangeIncorrectB); - CPPUNIT_TEST(testSubjectChangeIncorrectC); - CPPUNIT_TEST_SUITE_END(); + CPPUNIT_TEST_SUITE(MUCControllerTest); + CPPUNIT_TEST(testJoinPartStringContructionSimple); + CPPUNIT_TEST(testJoinPartStringContructionMixed); + CPPUNIT_TEST(testAppendToJoinParts); + CPPUNIT_TEST(testAddressedToSelf); + CPPUNIT_TEST(testNotAddressedToSelf); + CPPUNIT_TEST(testAddressedToSelfBySelf); + CPPUNIT_TEST(testMessageWithEmptyLabelItem); + CPPUNIT_TEST(testMessageWithLabelItem); + CPPUNIT_TEST(testCorrectMessageWithLabelItem); + CPPUNIT_TEST(testRoleAffiliationStates); + CPPUNIT_TEST(testSubjectChangeCorrect); + CPPUNIT_TEST(testSubjectChangeIncorrectA); + CPPUNIT_TEST(testSubjectChangeIncorrectB); + CPPUNIT_TEST(testSubjectChangeIncorrectC); + CPPUNIT_TEST_SUITE_END(); public: - void setUp() { - crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); - self_ = JID("girl@wonderland.lit/rabbithole"); - nick_ = "aLiCe"; - mucJID_ = JID("teaparty@rooms.wonderland.lit"); - mocks_ = new MockRepository(); - stanzaChannel_ = new DummyStanzaChannel(); - iqChannel_ = new DummyIQChannel(); - iqRouter_ = new IQRouter(iqChannel_); - eventController_ = new EventController(); - chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); - userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); - xmppRoster_ = new XMPPRosterImpl(); - presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); - presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); - directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); - uiEventStream_ = new UIEventStream(); - avatarManager_ = new NullAvatarManager(); - TimerFactory* timerFactory = NULL; - window_ = new MockChatWindow(); - mucRegistry_ = new MUCRegistry(); - entityCapsProvider_ = new DummyEntityCapsProvider(); - settings_ = new DummySettingsProvider(); - highlightManager_ = new HighlightManager(settings_); - muc_ = boost::make_shared<MockMUC>(mucJID_); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - chatMessageParser_ = boost::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true); - vcardStorage_ = new VCardMemoryStorage(crypto_.get()); - vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); - clientBlockListManager_ = new ClientBlockListManager(iqRouter_); - mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_); - controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, NULL, vcardManager_, mucBookmarkManager_); - } - - void tearDown() { - delete controller_; - delete mucBookmarkManager_; - delete clientBlockListManager_; - delete vcardManager_; - delete vcardStorage_; - delete highlightManager_; - delete settings_; - delete entityCapsProvider_; - delete eventController_; - delete presenceOracle_; - delete xmppRoster_; - delete mocks_; - delete uiEventStream_; - delete stanzaChannel_; - delete presenceSender_; - delete directedPresenceSender_; - delete iqRouter_; - delete iqChannel_; - delete mucRegistry_; - delete avatarManager_; - } - - void finishJoin() { - Presence::ref presence(new Presence()); - presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); - MUCUserPayload::ref status(new MUCUserPayload()); - MUCUserPayload::StatusCode code; - code.code = 110; - status->addStatusCode(code); - presence->addPayload(status); - stanzaChannel_->onPresenceReceived(presence); - } - - void testAddressedToSelf() { - finishJoin(); - Message::ref message(new Message()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); - message->setBody("basic " + nick_ + " test."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)1, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); - message->setBody(nick_ + ": hi there"); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)2, eventController_->getEvents().size()); - - message->setFrom(JID(muc_->getJID().toString() + "/other")); - message->setBody("Hi there " + nick_); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)3, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other2")); - message->setBody("Hi " + boost::to_lower_copy(nick_) + "."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other3")); - message->setBody("Hi bert."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other2")); - message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); - } - - void testNotAddressedToSelf() { - finishJoin(); - Message::ref message(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other3")); - message->setBody("Hi there Hatter"); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size()); - } - - void testAddressedToSelfBySelf() { - finishJoin(); - Message::ref message(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); - message->setBody("Hi there " + nick_); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size()); - } - - void testMessageWithEmptyLabelItem() { - SecurityLabelsCatalog::Item label; - label.setSelector("Bob"); - window_->label_ = label; - boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); - features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); - controller_->setAvailableServerFeatures(features); - IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; - SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); - labelPayload->addItem(label); - IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); - iqChannel_->onIQReceived(result); - std::string messageBody("agamemnon"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); - CPPUNIT_ASSERT(window_->labelsEnabled_); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); - CPPUNIT_ASSERT(!message->getPayload<SecurityLabel>()); - } - - void testMessageWithLabelItem() { - boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>(); - label->setLabel("a"); - SecurityLabelsCatalog::Item labelItem; - labelItem.setSelector("Bob"); - labelItem.setLabel(label); - window_->label_ = labelItem; - boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); - features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); - controller_->setAvailableServerFeatures(features); - IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; - SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); - labelPayload->addItem(labelItem); - IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); - iqChannel_->onIQReceived(result); - std::string messageBody("agamemnon"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); - CPPUNIT_ASSERT(window_->labelsEnabled_); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); - } - - void testCorrectMessageWithLabelItem() { - boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>(); - label->setLabel("a"); - SecurityLabelsCatalog::Item labelItem; - labelItem.setSelector("Bob"); - labelItem.setLabel(label); - boost::shared_ptr<SecurityLabel> label2 = boost::make_shared<SecurityLabel>(); - label->setLabel("b"); - SecurityLabelsCatalog::Item labelItem2; - labelItem2.setSelector("Charlie"); - labelItem2.setLabel(label2); - window_->label_ = labelItem; - boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); - features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); - controller_->setAvailableServerFeatures(features); - IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; - SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); - labelPayload->addItem(labelItem); - IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); - iqChannel_->onIQReceived(result); - std::string messageBody("agamemnon"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); - CPPUNIT_ASSERT(window_->labelsEnabled_); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); - window_->label_ = labelItem2; - window_->onSendMessageRequest(messageBody, true); - rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); - } - - void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) { - CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size()); - for (size_t i = 0; i < expected.size(); i++) { - CPPUNIT_ASSERT_EQUAL(expected[i].nick, actual[i].nick); - CPPUNIT_ASSERT_EQUAL(expected[i].type, actual[i].type); - } - } - - void testAppendToJoinParts() { - std::vector<NickJoinPart> list; - std::vector<NickJoinPart> gold; - MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); - gold.push_back(NickJoinPart("Kev", Join)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join)); - gold.push_back(NickJoinPart("Remko", Join)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join)); - gold.push_back(NickJoinPart("Bert", Join)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part)); - gold[2].type = JoinThenPart; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part)); - gold[0].type = JoinThenPart; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part)); - gold[1].type = JoinThenPart; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); - gold.push_back(NickJoinPart("Ernie", Part)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join)); - gold[3].type = PartThenJoin; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); - gold[0].type = Join; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); - gold[3].type = Part; - checkEqual(gold, list); - - } - - void testJoinPartStringContructionSimple() { - std::vector<NickJoinPart> list; - list.push_back(NickJoinPart("Kev", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Bert", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Ernie", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); - } - - void testJoinPartStringContructionMixed() { - std::vector<NickJoinPart> list; - list.push_back(NickJoinPart("Kev", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Bert", PartThenJoin)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Ernie", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); - } - - JID jidFromOccupant(const MUCOccupant& occupant) { - return JID(mucJID_.toString()+"/"+occupant.getNick()); - } - - void testRoleAffiliationStates() { - - typedef std::map<std::string, MUCOccupant> occupant_map; - occupant_map occupants; - occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner))); - occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner))); - occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner))); - occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner))); - - /* populate the MUC with fake users */ - typedef const std::pair<std::string,MUCOccupant> occupantIterator; - foreach(occupantIterator &occupant, occupants) { - muc_->insertOccupant(occupant.second); - } - - std::vector<MUCOccupant> alterations; - alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin)); - alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member)); - alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast)); - alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member)); - alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner)); - alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast)); - alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation)); - alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation)); - alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast)); - - foreach(const MUCOccupant& alteration, alterations) { - /* perform an alteration to a user's role and affiliation */ - occupant_map::iterator occupant = occupants.find(alteration.getNick()); - CPPUNIT_ASSERT(occupant != occupants.end()); - const JID jid = jidFromOccupant(occupant->second); - /* change the affiliation, leave the role in place */ - muc_->changeAffiliation(jid, alteration.getAffiliation()); - occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation()); - testRoleAffiliationStatesVerify(occupants); - /* change the role, leave the affiliation in place */ - muc_->changeOccupantRole(jid, alteration.getRole()); - occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation()); - testRoleAffiliationStatesVerify(occupants); - } - } - - void testSubjectChangeCorrect() { - std::string messageBody("test message"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); - - { - Message::ref message = boost::make_shared<Message>(); - message->setType(Message::Groupchat); - message->setTo(self_); - message->setFrom(mucJID_.withResource("SomeNickname")); - message->setID(iqChannel_->getNewIQID()); - message->setSubject("New Room Subject"); - - controller_->handleIncomingMessage(boost::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("The room subject is now: New Room Subject"), boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } - - /* - * Test that message stanzas with subject element and non-empty body element do not cause a subject change. - */ - void testSubjectChangeIncorrectA() { - std::string messageBody("test message"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); - - { - Message::ref message = boost::make_shared<Message>(); - message->setType(Message::Groupchat); - message->setTo(self_); - message->setFrom(mucJID_.withResource("SomeNickname")); - message->setID(iqChannel_->getNewIQID()); - message->setSubject("New Room Subject"); - message->setBody("Some body text that prevents this stanza from being a subject change."); - - controller_->handleIncomingMessage(boost::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } - - /* - * Test that message stanzas with subject element and thread element do not cause a subject change. - */ - void testSubjectChangeIncorrectB() { - std::string messageBody("test message"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); - - { - Message::ref message = boost::make_shared<Message>(); - message->setType(Message::Groupchat); - message->setTo(self_); - message->setFrom(mucJID_.withResource("SomeNickname")); - message->setID(iqChannel_->getNewIQID()); - message->setSubject("New Room Subject"); - message->addPayload(boost::make_shared<Thread>("Thread that prevents the subject change.")); - - controller_->handleIncomingMessage(boost::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } - - /* - * Test that message stanzas with subject element and empty body element do not cause a subject change. - */ - void testSubjectChangeIncorrectC() { - std::string messageBody("test message"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); - - { - Message::ref message = boost::make_shared<Message>(); - message->setType(Message::Groupchat); - message->setTo(self_); - message->setFrom(mucJID_.withResource("SomeNickname")); - message->setID(iqChannel_->getNewIQID()); - message->setSubject("New Room Subject"); - message->setBody(""); - - controller_->handleIncomingMessage(boost::make_shared<MessageEvent>(message)); - CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); - } - } - - void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) { - /* verify that the roster is in sync */ - GroupRosterItem* group = window_->getRosterModel()->getRoot(); - foreach(RosterItem* rosterItem, group->getChildren()) { - GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem); - CPPUNIT_ASSERT(child); - foreach(RosterItem* childItem, child->getChildren()) { - ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); - CPPUNIT_ASSERT(item); - std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); - CPPUNIT_ASSERT(occupant != occupants.end()); - CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole()); - CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation()); - } - } - } + void setUp() { + crypto_ = std::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); + self_ = JID("girl@wonderland.lit/rabbithole"); + nick_ = "aLiCe"; + mucJID_ = JID("teaparty@rooms.wonderland.lit"); + mocks_ = new MockRepository(); + stanzaChannel_ = new DummyStanzaChannel(); + iqChannel_ = new DummyIQChannel(); + iqRouter_ = new IQRouter(iqChannel_); + eventController_ = new EventController(); + chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); + userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); + xmppRoster_ = new XMPPRosterImpl(); + presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); + presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); + directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); + uiEventStream_ = new UIEventStream(); + avatarManager_ = new NullAvatarManager(); + TimerFactory* timerFactory = nullptr; + window_ = new MockChatWindow(); + mucRegistry_ = new MUCRegistry(); + entityCapsProvider_ = new DummyEntityCapsProvider(); + settings_ = new DummySettingsProvider(); + highlightManager_ = new HighlightManager(settings_); + muc_ = std::make_shared<MockMUC>(mucJID_); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); + chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true); + vcardStorage_ = new VCardMemoryStorage(crypto_.get()); + vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_); + controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_); + } + + void tearDown() { + delete controller_; + delete mucBookmarkManager_; + delete clientBlockListManager_; + delete vcardManager_; + delete vcardStorage_; + delete highlightManager_; + delete settings_; + delete entityCapsProvider_; + delete eventController_; + delete presenceOracle_; + delete xmppRoster_; + delete mocks_; + delete uiEventStream_; + delete stanzaChannel_; + delete presenceSender_; + delete directedPresenceSender_; + delete iqRouter_; + delete iqChannel_; + delete mucRegistry_; + delete avatarManager_; + } + + void finishJoin() { + Presence::ref presence(new Presence()); + presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); + MUCUserPayload::ref status(new MUCUserPayload()); + MUCUserPayload::StatusCode code; + code.code = 110; + status->addStatusCode(code); + presence->addPayload(status); + stanzaChannel_->onPresenceReceived(presence); + } + + void testAddressedToSelf() { + finishJoin(); + Message::ref message(new Message()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); + message->setBody("basic " + nick_ + " test."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)1, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); + message->setBody(nick_ + ": hi there"); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)2, eventController_->getEvents().size()); + + message->setFrom(JID(muc_->getJID().toString() + "/other")); + message->setBody("Hi there " + nick_); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)3, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other2")); + message->setBody("Hi " + boost::to_lower_copy(nick_) + "."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other3")); + message->setBody("Hi bert."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other2")); + message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); + } + + void testNotAddressedToSelf() { + finishJoin(); + Message::ref message(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other3")); + message->setBody("Hi there Hatter"); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size()); + } + + void testAddressedToSelfBySelf() { + finishJoin(); + Message::ref message(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); + message->setBody("Hi there " + nick_); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size()); + } + + void testMessageWithEmptyLabelItem() { + SecurityLabelsCatalog::Item label; + label.setSelector("Bob"); + window_->label_ = label; + std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(label); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); + CPPUNIT_ASSERT(window_->labelsEnabled_); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); + CPPUNIT_ASSERT(!message->getPayload<SecurityLabel>()); + } + + void testMessageWithLabelItem() { + std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>(); + label->setLabel("a"); + SecurityLabelsCatalog::Item labelItem; + labelItem.setSelector("Bob"); + labelItem.setLabel(label); + window_->label_ = labelItem; + std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(labelItem); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); + CPPUNIT_ASSERT(window_->labelsEnabled_); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); + CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + } + + void testCorrectMessageWithLabelItem() { + std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>(); + label->setLabel("a"); + SecurityLabelsCatalog::Item labelItem; + labelItem.setSelector("Bob"); + labelItem.setLabel(label); + std::shared_ptr<SecurityLabel> label2 = std::make_shared<SecurityLabel>(); + label->setLabel("b"); + SecurityLabelsCatalog::Item labelItem2; + labelItem2.setSelector("Charlie"); + labelItem2.setLabel(label2); + window_->label_ = labelItem; + std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(labelItem); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); + CPPUNIT_ASSERT(window_->labelsEnabled_); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); + CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + window_->label_ = labelItem2; + window_->onSendMessageRequest(messageBody, true); + rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get()); + CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + } + + void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) { + CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size()); + for (size_t i = 0; i < expected.size(); i++) { + CPPUNIT_ASSERT_EQUAL(expected[i].nick, actual[i].nick); + CPPUNIT_ASSERT_EQUAL(expected[i].type, actual[i].type); + } + } + + void testAppendToJoinParts() { + std::vector<NickJoinPart> list; + std::vector<NickJoinPart> gold; + MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); + gold.push_back(NickJoinPart("Kev", Join)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join)); + gold.push_back(NickJoinPart("Remko", Join)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join)); + gold.push_back(NickJoinPart("Bert", Join)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part)); + gold[2].type = JoinThenPart; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part)); + gold[0].type = JoinThenPart; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part)); + gold[1].type = JoinThenPart; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); + gold.push_back(NickJoinPart("Ernie", Part)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join)); + gold[3].type = PartThenJoin; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); + gold[0].type = Join; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); + gold[3].type = Part; + checkEqual(gold, list); + + } + + void testJoinPartStringContructionSimple() { + std::vector<NickJoinPart> list; + list.push_back(NickJoinPart("Kev", Join)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Remko", Part)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Bert", Join)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Ernie", Join)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); + } + + void testJoinPartStringContructionMixed() { + std::vector<NickJoinPart> list; + list.push_back(NickJoinPart("Kev", JoinThenPart)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Remko", Part)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Bert", PartThenJoin)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Ernie", JoinThenPart)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); + } + + JID jidFromOccupant(const MUCOccupant& occupant) { + return JID(mucJID_.toString()+"/"+occupant.getNick()); + } + + void testRoleAffiliationStates() { + + typedef std::map<std::string, MUCOccupant> occupant_map; + occupant_map occupants; + occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner))); + + /* populate the MUC with fake users */ + for (auto&& occupant : occupants) { + muc_->insertOccupant(occupant.second); + } + + std::vector<MUCOccupant> alterations; + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner)); + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast)); + + for (const auto& alteration : alterations) { + /* perform an alteration to a user's role and affiliation */ + occupant_map::iterator occupant = occupants.find(alteration.getNick()); + CPPUNIT_ASSERT(occupant != occupants.end()); + const JID jid = jidFromOccupant(occupant->second); + /* change the affiliation, leave the role in place */ + muc_->changeAffiliation(jid, alteration.getAffiliation()); + occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + /* change the role, leave the affiliation in place */ + muc_->changeOccupantRole(jid, alteration.getRole()); + occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + } + } + + void testSubjectChangeCorrect() { + std::string messageBody("test message"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("New Room Subject"); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + CPPUNIT_ASSERT_EQUAL(std::string("The room subject is now: New Room Subject"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } + } + + /* + * Test that message stanzas with subject element and non-empty body element do not cause a subject change. + */ + void testSubjectChangeIncorrectA() { + std::string messageBody("test message"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("New Room Subject"); + message->setBody("Some body text that prevents this stanza from being a subject change."); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } + } + + /* + * Test that message stanzas with subject element and thread element do not cause a subject change. + */ + void testSubjectChangeIncorrectB() { + std::string messageBody("test message"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("New Room Subject"); + message->addPayload(std::make_shared<Thread>("Thread that prevents the subject change.")); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } + } + + /* + * Test that message stanzas with subject element and empty body element do not cause a subject change. + */ + void testSubjectChangeIncorrectC() { + std::string messageBody("test message"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or("")); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("New Room Subject"); + message->setBody(""); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } + } + + void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) { + /* verify that the roster is in sync */ + GroupRosterItem* group = window_->getRosterModel()->getRoot(); + for (auto rosterItem : group->getChildren()) { + GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem); + CPPUNIT_ASSERT(child); + for (auto childItem : child->getChildren()) { + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); + CPPUNIT_ASSERT(item); + std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); + CPPUNIT_ASSERT(occupant != occupants.end()); + CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole()); + CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation()); + } + } + } private: - JID self_; - JID mucJID_; - MockMUC::ref muc_; - std::string nick_; - DummyStanzaChannel* stanzaChannel_; - DummyIQChannel* iqChannel_; - IQRouter* iqRouter_; - EventController* eventController_; - ChatWindowFactory* chatWindowFactory_; - UserSearchWindowFactory* userSearchWindowFactory_; - MUCController* controller_; -// NickResolver* nickResolver_; - PresenceOracle* presenceOracle_; - AvatarManager* avatarManager_; - StanzaChannelPresenceSender* presenceSender_; - DirectedPresenceSender* directedPresenceSender_; - MockRepository* mocks_; - UIEventStream* uiEventStream_; - MockChatWindow* window_; - MUCRegistry* mucRegistry_; - DummyEntityCapsProvider* entityCapsProvider_; - DummySettingsProvider* settings_; - HighlightManager* highlightManager_; - boost::shared_ptr<ChatMessageParser> chatMessageParser_; - boost::shared_ptr<CryptoProvider> crypto_; - VCardManager* vcardManager_; - VCardMemoryStorage* vcardStorage_; - ClientBlockListManager* clientBlockListManager_; - MUCBookmarkManager* mucBookmarkManager_; - XMPPRoster* xmppRoster_; + JID self_; + JID mucJID_; + MockMUC::ref muc_; + std::string nick_; + DummyStanzaChannel* stanzaChannel_; + DummyIQChannel* iqChannel_; + IQRouter* iqRouter_; + EventController* eventController_; + ChatWindowFactory* chatWindowFactory_; + UserSearchWindowFactory* userSearchWindowFactory_; + MUCController* controller_; +// NickResolver* nickResolver_; + PresenceOracle* presenceOracle_; + AvatarManager* avatarManager_; + StanzaChannelPresenceSender* presenceSender_; + DirectedPresenceSender* directedPresenceSender_; + MockRepository* mocks_; + UIEventStream* uiEventStream_; + MockChatWindow* window_; + MUCRegistry* mucRegistry_; + DummyEntityCapsProvider* entityCapsProvider_; + DummySettingsProvider* settings_; + HighlightManager* highlightManager_; + std::shared_ptr<ChatMessageParser> chatMessageParser_; + std::shared_ptr<CryptoProvider> crypto_; + VCardManager* vcardManager_; + VCardMemoryStorage* vcardStorage_; + ClientBlockListManager* clientBlockListManager_; + MUCBookmarkManager* mucBookmarkManager_; + XMPPRoster* xmppRoster_; }; CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest); diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h index c1410f3..395b050 100644 --- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h +++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h @@ -1,28 +1,28 @@ /* - * Copyright (c) 2011-2014 Isode Limited. + * Copyright (c) 2011-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once -#include "Swift/Controllers/UIInterfaces/ChatListWindow.h" +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> namespace Swift { - class MockChatListWindow : public ChatListWindow { - public: - MockChatListWindow() {} - virtual ~MockChatListWindow() {} - void addMUCBookmark(const MUCBookmark& /*bookmark*/) {} - void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {} - void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {} - void removeWhiteboardSession(const JID& /*jid*/) {} - void setBookmarksEnabled(bool /*enabled*/) {} - void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} - void setUnreadCount(int /*unread*/) {} - void clearBookmarks() {} - void setOnline(bool /*isOnline*/) {} - }; + class MockChatListWindow : public ChatListWindow { + public: + MockChatListWindow() {} + virtual ~MockChatListWindow() {} + void addMUCBookmark(const MUCBookmark& /*bookmark*/) {} + void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {} + void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {} + void removeWhiteboardSession(const JID& /*jid*/) {} + void setBookmarksEnabled(bool /*enabled*/) {} + void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} + void setUnreadCount(int /*unread*/) {} + void clearBookmarks() {} + void setOnline(bool /*isOnline*/) {} + }; } diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp index e08912a..91de670 100644 --- a/Swift/Controllers/Chat/UserSearchController.cpp +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -1,18 +1,17 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swift/Controllers/Chat/UserSearchController.h> +#include <memory> + #include <boost/bind.hpp> -#include <boost/shared_ptr.hpp> -#include <boost/smart_ptr/make_shared.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/String.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Disco/DiscoServiceWalker.h> #include <Swiften/Disco/GetDiscoInfoRequest.h> #include <Swiften/Disco/GetDiscoItemsRequest.h> @@ -36,357 +35,359 @@ namespace Swift { static const std::string SEARCHED_DIRECTORIES = "searchedDirectories"; UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle, ProfileSettingsProvider* settings) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController), contactSuggester_(contactSuggester), avatarManager_(avatarManager), presenceOracle_(presenceOracle), settings_(settings) { - uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); - vcardManager_->onVCardChanged.connect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); - avatarManager_->onAvatarChanged.connect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); - presenceOracle_->onPresenceChange.connect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); - window_ = NULL; - discoWalker_ = NULL; - loadSavedDirectories(); + uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); + vcardManager_->onVCardChanged.connect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); + avatarManager_->onAvatarChanged.connect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); + presenceOracle_->onPresenceChange.connect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); + window_ = nullptr; + discoWalker_ = nullptr; + loadSavedDirectories(); } UserSearchController::~UserSearchController() { - endDiscoWalker(); - delete discoWalker_; - if (window_) { - window_->onNameSuggestionRequested.disconnect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); - window_->onFormRequested.disconnect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); - window_->onSearchRequested.disconnect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); - window_->onContactSuggestionsRequested.disconnect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1)); - window_->onJIDUpdateRequested.disconnect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); - window_->onJIDAddRequested.disconnect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1)); - window_->onJIDEditFieldChanged.disconnect(boost::bind(&UserSearchController::handleJIDEditingFinished, this, _1)); - delete window_; - } - presenceOracle_->onPresenceChange.disconnect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); - avatarManager_->onAvatarChanged.disconnect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); - vcardManager_->onVCardChanged.disconnect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); - uiEventStream_->onUIEvent.disconnect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); + endDiscoWalker(); + delete discoWalker_; + if (window_) { + window_->onNameSuggestionRequested.disconnect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); + window_->onFormRequested.disconnect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); + window_->onSearchRequested.disconnect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); + window_->onContactSuggestionsRequested.disconnect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1)); + window_->onJIDUpdateRequested.disconnect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); + window_->onJIDAddRequested.disconnect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1)); + window_->onJIDEditFieldChanged.disconnect(boost::bind(&UserSearchController::handleJIDEditingFinished, this, _1)); + delete window_; + } + presenceOracle_->onPresenceChange.disconnect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); + avatarManager_->onAvatarChanged.disconnect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); + vcardManager_->onVCardChanged.disconnect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); + uiEventStream_->onUIEvent.disconnect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); } UserSearchWindow* UserSearchController::getUserSearchWindow() { - initializeUserWindow(); - assert(window_); - return window_; + initializeUserWindow(); + assert(window_); + return window_; } void UserSearchController::setCanInitiateImpromptuMUC(bool supportsImpromptu) { - if (!window_) { - initializeUserWindow(); - } - if (window_) { - window_->setCanStartImpromptuChats(supportsImpromptu); - } // Else doesn't support search + if (!window_) { + initializeUserWindow(); + } + if (window_) { + window_->setCanStartImpromptuChats(supportsImpromptu); + } // Else doesn't support search } -void UserSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) { - bool handle = false; - boost::shared_ptr<RequestAddUserDialogUIEvent> addUserRequest = boost::shared_ptr<RequestAddUserDialogUIEvent>(); - RequestInviteToMUCUIEvent::ref inviteToMUCRequest = RequestInviteToMUCUIEvent::ref(); - switch (type_) { - case AddContact: - if ((addUserRequest = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) { - handle = true; - } - break; - case StartChat: - if (boost::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) { - handle = true; - } - break; - case InviteToChat: - if ((inviteToMUCRequest = boost::dynamic_pointer_cast<RequestInviteToMUCUIEvent>(event))) { - handle = true; - } - break; - } - if (handle) { - initializeUserWindow(); - window_->show(); - window_->addSavedServices(savedDirectories_); - if (addUserRequest) { - const std::string& name = addUserRequest->getPredefinedName(); - const JID& jid = addUserRequest->getPredefinedJID(); - if (!name.empty() && jid.isValid()) { - window_->prepopulateJIDAndName(jid, name); - } - } else if (inviteToMUCRequest) { - window_->setCanSupplyDescription(!inviteToMUCRequest->isImpromptu()); - window_->setJIDs(inviteToMUCRequest->getInvites()); - window_->setRoomJID(inviteToMUCRequest->getRoom()); - } - return; - } +void UserSearchController::handleUIEvent(std::shared_ptr<UIEvent> event) { + bool handle = false; + std::shared_ptr<RequestAddUserDialogUIEvent> addUserRequest = std::shared_ptr<RequestAddUserDialogUIEvent>(); + auto inviteToMUCRequest = RequestInviteToMUCUIEvent::ref(); + switch (type_) { + case AddContact: + if ((addUserRequest = std::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) { + handle = true; + } + break; + case StartChat: + if (std::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) { + handle = true; + } + break; + case InviteToChat: + if ((inviteToMUCRequest = std::dynamic_pointer_cast<RequestInviteToMUCUIEvent>(event))) { + handle = true; + } + break; + } + if (handle) { + initializeUserWindow(); + window_->show(); + window_->addSavedServices(savedDirectories_); + if (addUserRequest) { + const std::string& name = addUserRequest->getPredefinedName(); + const JID& jid = addUserRequest->getPredefinedJID(); + if (!name.empty() && jid.isValid()) { + window_->prepopulateJIDAndName(jid, name); + } + } + else if (inviteToMUCRequest) { + window_->setCanSupplyDescription(!inviteToMUCRequest->isImpromptu()); + window_->setJIDs(inviteToMUCRequest->getInvites()); + window_->setOriginator(inviteToMUCRequest->getOriginator()); + } + return; + } } void UserSearchController::handleFormRequested(const JID& service) { - window_->setSearchError(false); - window_->setServerSupportsSearch(true); - - //Abort a previous search if is active - endDiscoWalker(); - delete discoWalker_; - discoWalker_ = new DiscoServiceWalker(service, iqRouter_); - discoWalker_->onServiceFound.connect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2)); - discoWalker_->onWalkComplete.connect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this)); - discoWalker_->beginWalk(); + window_->setSearchError(false); + window_->setServerSupportsSearch(true); + + //Abort a previous search if is active + endDiscoWalker(); + delete discoWalker_; + discoWalker_ = new DiscoServiceWalker(service, iqRouter_); + discoWalker_->onServiceFound.connect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2)); + discoWalker_->onWalkComplete.connect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this)); + discoWalker_->beginWalk(); } void UserSearchController::endDiscoWalker() { - if (discoWalker_) { - discoWalker_->endWalk(); - discoWalker_->onServiceFound.disconnect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2)); - discoWalker_->onWalkComplete.disconnect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this)); - } + if (discoWalker_) { + discoWalker_->endWalk(); + discoWalker_->onServiceFound.disconnect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2)); + discoWalker_->onWalkComplete.disconnect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this)); + } } -void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { - //bool isUserDirectory = false; - bool supports55 = false; - foreach (DiscoInfo::Identity identity, info->getIdentities()) { - if ((identity.getCategory() == "directory" - && identity.getType() == "user")) { - //isUserDirectory = true; - } - } - std::vector<std::string> features = info->getFeatures(); - supports55 = std::find(features.begin(), features.end(), DiscoInfo::JabberSearchFeature) != features.end(); - if (/*isUserDirectory && */supports55) { //FIXME: once M-Link correctly advertises directoryness. - /* Abort further searches.*/ - endDiscoWalker(); - boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Get, jid, boost::make_shared<SearchPayload>(), iqRouter_)); - searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleFormResponse, this, _1, _2)); - searchRequest->send(); - } +void UserSearchController::handleDiscoServiceFound(const JID& jid, std::shared_ptr<DiscoInfo> info) { + //bool isUserDirectory = false; + bool supports55 = false; + // TODO: Cleanup code + for (const auto& identity : info->getIdentities()) { + if ((identity.getCategory() == "directory" + && identity.getType() == "user")) { + //isUserDirectory = true; + } + } + std::vector<std::string> features = info->getFeatures(); + supports55 = std::find(features.begin(), features.end(), DiscoInfo::JabberSearchFeature) != features.end(); + if (/*isUserDirectory && */supports55) { //FIXME: once M-Link correctly advertises directoryness. + /* Abort further searches.*/ + endDiscoWalker(); + std::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Get, jid, std::make_shared<SearchPayload>(), iqRouter_)); + searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleFormResponse, this, _1, _2)); + searchRequest->send(); + } } -void UserSearchController::handleFormResponse(boost::shared_ptr<SearchPayload> fields, ErrorPayload::ref error) { - if (error || !fields) { - window_->setServerSupportsSearch(false); - return; - } - window_->setSearchFields(fields); +void UserSearchController::handleFormResponse(std::shared_ptr<SearchPayload> fields, ErrorPayload::ref error) { + if (error || !fields) { + window_->setServerSupportsSearch(false); + return; + } + window_->setSearchFields(fields); } -void UserSearchController::handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid) { - addToSavedDirectories(jid); - boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Set, jid, fields, iqRouter_)); - searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleSearchResponse, this, _1, _2)); - searchRequest->send(); +void UserSearchController::handleSearch(std::shared_ptr<SearchPayload> fields, const JID& jid) { + addToSavedDirectories(jid); + std::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Set, jid, fields, iqRouter_)); + searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleSearchResponse, this, _1, _2)); + searchRequest->send(); } -void UserSearchController::handleSearchResponse(boost::shared_ptr<SearchPayload> resultsPayload, ErrorPayload::ref error) { - if (error || !resultsPayload) { - window_->setSearchError(true); - return; - } - - std::vector<UserSearchResult> results; - - if (resultsPayload->getForm()) { - window_->setResultsForm(resultsPayload->getForm()); - } else { - foreach (SearchPayload::Item item, resultsPayload->getItems()) { - JID jid(item.jid); - std::map<std::string, std::string> fields; - fields["first"] = item.first; - fields["last"] = item.last; - fields["nick"] = item.nick; - fields["email"] = item.email; - UserSearchResult result(jid, fields); - results.push_back(result); - } - window_->setResults(results); - } +void UserSearchController::handleSearchResponse(std::shared_ptr<SearchPayload> resultsPayload, ErrorPayload::ref error) { + if (error || !resultsPayload) { + window_->setSearchError(true); + return; + } + + std::vector<UserSearchResult> results; + + if (resultsPayload->getForm()) { + window_->setResultsForm(resultsPayload->getForm()); + } else { + for (auto&& item : resultsPayload->getItems()) { + JID jid(item.jid); + std::map<std::string, std::string> fields; + fields["first"] = item.first; + fields["last"] = item.last; + fields["nick"] = item.nick; + fields["email"] = item.email; + UserSearchResult result(jid, fields); + results.push_back(result); + } + window_->setResults(results); + } } void UserSearchController::handleNameSuggestionRequest(const JID &jid) { - suggestionsJID_= jid; - VCard::ref vcard = vcardManager_->getVCardAndRequestWhenNeeded(jid); - if (vcard) { - handleVCardChanged(jid, vcard); - } + suggestionsJID_= jid; + VCard::ref vcard = vcardManager_->getVCardAndRequestWhenNeeded(jid); + if (vcard) { + handleVCardChanged(jid, vcard); + } } void UserSearchController::handleJIDEditingFinished(const JID& jid) { - if (jid.isValid()) { - if (rosterController_->getItem(jid)) { - window_->setWarning(QT_TRANSLATE_NOOP("", "This contact is already on your contact list.")); - } - else if (jid.getNode().empty()) { - window_->setWarning(QT_TRANSLATE_NOOP("", "Part of the address you have entered is missing. An address has a structure of 'user@example.com'.")); - } - else { - window_->setWarning(boost::optional<std::string>()); - } - } - else { - window_->setWarning(QT_TRANSLATE_NOOP("", "The address you have entered is invalid.")); - } + if (jid.isValid()) { + if (rosterController_->getItem(jid)) { + window_->setWarning(QT_TRANSLATE_NOOP("", "This contact is already on your contact list.")); + } + else if (jid.getNode().empty()) { + window_->setWarning(QT_TRANSLATE_NOOP("", "Part of the address you have entered is missing. An address has a structure of 'user@example.com'.")); + } + else { + window_->setWarning(boost::optional<std::string>()); + } + } + else { + window_->setWarning(QT_TRANSLATE_NOOP("", "The address you have entered is invalid.")); + } } void UserSearchController::handleContactSuggestionsRequested(std::string text) { - const std::vector<JID> existingJIDs = window_->getJIDs(); - std::vector<Contact::ref> suggestions = contactSuggester_->getSuggestions(text, false); - /* do not suggest contacts that have already been added to the chat list */ - std::vector<Contact::ref>::iterator i = suggestions.begin(); - while (i != suggestions.end()) { - bool found = false; - foreach (const JID& jid, existingJIDs) { - if ((*i)->jid == jid) { - found = true; - break; - } - } - - // remove contact suggestions which are already on the contact list in add-contact-mode - if (type_ == AddContact) { - if (!found && !!rosterController_->getItem((*i)->jid)) { - found = true; - } - } - - if (found) { - i = suggestions.erase(i); - } else { - i++; - } - } - window_->setContactSuggestions(suggestions); + const std::vector<JID> existingJIDs = window_->getJIDs(); + std::vector<Contact::ref> suggestions = contactSuggester_->getSuggestions(text, false); + /* do not suggest contacts that have already been added to the chat list */ + std::vector<Contact::ref>::iterator i = suggestions.begin(); + while (i != suggestions.end()) { + bool found = false; + for (const auto& jid : existingJIDs) { + if ((*i)->jid == jid) { + found = true; + break; + } + } + + // remove contact suggestions which are already on the contact list in add-contact-mode + if (type_ == AddContact) { + if (!found && !!rosterController_->getItem((*i)->jid)) { + found = true; + } + } + + if (found) { + i = suggestions.erase(i); + } else { + i++; + } + } + window_->setContactSuggestions(suggestions); } void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref vcard) { - if (jid == suggestionsJID_) { - window_->setNameSuggestions(ContactEditController::nameSuggestionsFromVCard(vcard)); - suggestionsJID_ = JID(); - } - handleJIDUpdateRequested(std::vector<JID>(1, jid)); + if (jid == suggestionsJID_) { + window_->setNameSuggestions(ContactEditController::nameSuggestionsFromVCard(vcard)); + suggestionsJID_ = JID(); + } + handleJIDUpdateRequested(std::vector<JID>(1, jid)); } void UserSearchController::handleAvatarChanged(const JID& jid) { - handleJIDUpdateRequested(std::vector<JID>(1, jid)); + handleJIDUpdateRequested(std::vector<JID>(1, jid)); } void UserSearchController::handlePresenceChanged(Presence::ref presence) { - handleJIDUpdateRequested(std::vector<JID>(1, presence->getFrom().toBare())); + handleJIDUpdateRequested(std::vector<JID>(1, presence->getFrom().toBare())); } void UserSearchController::handleJIDUpdateRequested(const std::vector<JID>& jids) { - if (window_) { - std::vector<Contact::ref> updates; - foreach(const JID& jid, jids) { - updates.push_back(convertJIDtoContact(jid)); - } - window_->updateContacts(updates); - } + if (window_) { + std::vector<Contact::ref> updates; + for (const auto& jid : jids) { + updates.push_back(convertJIDtoContact(jid)); + } + window_->updateContacts(updates); + } } void UserSearchController::handleJIDAddRequested(const std::vector<JID>& jids) { - std::vector<Contact::ref> contacts; - foreach(const JID& jid, jids) { - contacts.push_back(convertJIDtoContact(jid)); - } - window_->addContacts(contacts); + std::vector<Contact::ref> contacts; + for (const auto& jid : jids) { + contacts.push_back(convertJIDtoContact(jid)); + } + window_->addContacts(contacts); } Contact::ref UserSearchController::convertJIDtoContact(const JID& jid) { - Contact::ref contact = boost::make_shared<Contact>(); - contact->jid = jid; - - // name lookup - boost::optional<XMPPRosterItem> rosterItem = rosterController_->getItem(jid); - if (rosterItem && !rosterItem->getName().empty()) { - contact->name = rosterItem->getName(); - } else { - VCard::ref vcard = vcardManager_->getVCard(jid); - if (vcard && !vcard->getFullName().empty()) { - contact->name = vcard->getFullName(); - } else { - contact->name = jid.toString(); - } - } - - // presence lookup - Presence::ref presence = presenceOracle_->getAccountPresence(jid); - if (presence) { - contact->statusType = presence->getShow(); - } else { - contact->statusType = StatusShow::None; - } - - // avatar lookup - contact->avatarPath = avatarManager_->getAvatarPath(jid); - return contact; + Contact::ref contact = std::make_shared<Contact>(); + contact->jid = jid; + + // name lookup + boost::optional<XMPPRosterItem> rosterItem = rosterController_->getItem(jid); + if (rosterItem && !rosterItem->getName().empty()) { + contact->name = rosterItem->getName(); + } else { + VCard::ref vcard = vcardManager_->getVCard(jid); + if (vcard && !vcard->getFullName().empty()) { + contact->name = vcard->getFullName(); + } else { + contact->name = jid.toString(); + } + } + + // presence lookup + Presence::ref presence = presenceOracle_->getAccountPresence(jid); + if (presence) { + contact->statusType = presence->getShow(); + } else { + contact->statusType = StatusShow::None; + } + + // avatar lookup + contact->avatarPath = avatarManager_->getAvatarPath(jid); + return contact; } void UserSearchController::handleDiscoWalkFinished() { - window_->setServerSupportsSearch(false); - endDiscoWalker(); + window_->setServerSupportsSearch(false); + endDiscoWalker(); } void UserSearchController::initializeUserWindow() { - if (!window_) { - UserSearchWindow::Type windowType = UserSearchWindow::AddContact; - switch(type_) { - case AddContact: - windowType = UserSearchWindow::AddContact; - break; - case StartChat: - windowType = UserSearchWindow::ChatToContact; - break; - case InviteToChat: - windowType = UserSearchWindow::InviteToChat; - break; - } - - window_ = factory_->createUserSearchWindow(windowType, uiEventStream_, rosterController_->getGroups()); - if (!window_) { - // UI Doesn't support user search - return; - } - window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); - window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); - window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); - window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1)); - window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); - window_->onJIDAddRequested.connect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1)); - window_->onJIDEditFieldChanged.connect(boost::bind(&UserSearchController::handleJIDEditingFinished, this, _1)); - window_->setSelectedService(JID(jid_.getDomain())); - window_->clear(); - } + if (!window_) { + UserSearchWindow::Type windowType = UserSearchWindow::AddContact; + switch(type_) { + case AddContact: + windowType = UserSearchWindow::AddContact; + break; + case StartChat: + windowType = UserSearchWindow::ChatToContact; + break; + case InviteToChat: + windowType = UserSearchWindow::InviteToChat; + break; + } + + window_ = factory_->createUserSearchWindow(windowType, uiEventStream_, rosterController_->getGroups()); + if (!window_) { + // UI Doesn't support user search + return; + } + window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); + window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); + window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); + window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1)); + window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); + window_->onJIDAddRequested.connect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1)); + window_->onJIDEditFieldChanged.connect(boost::bind(&UserSearchController::handleJIDEditingFinished, this, _1)); + window_->setSelectedService(JID(jid_.getDomain())); + window_->clear(); + } } void UserSearchController::loadSavedDirectories() { - savedDirectories_.clear(); - foreach (std::string stringItem, String::split(settings_->getStringSetting(SEARCHED_DIRECTORIES), '\n')) { - if(!stringItem.empty()) { - savedDirectories_.push_back(JID(stringItem)); - } - } + savedDirectories_.clear(); + for (auto&& stringItem : String::split(settings_->getStringSetting(SEARCHED_DIRECTORIES), '\n')) { + if(!stringItem.empty()) { + savedDirectories_.push_back(JID(stringItem)); + } + } } void UserSearchController::addToSavedDirectories(const JID& jid) { - if (!jid.isValid()) { - return; - } - - savedDirectories_.erase(std::remove(savedDirectories_.begin(), savedDirectories_.end(), jid), savedDirectories_.end()); - savedDirectories_.insert(savedDirectories_.begin(), jid); - - std::string collapsed; - int i = 0; - foreach (JID jidItem, savedDirectories_) { - if (i >= 15) { - break; - } - if (!collapsed.empty()) { - collapsed += "\n"; - } - collapsed += jidItem.toString(); - ++i; - } - settings_->storeString(SEARCHED_DIRECTORIES, collapsed); - window_->addSavedServices(savedDirectories_); + if (!jid.isValid()) { + return; + } + + savedDirectories_.erase(std::remove(savedDirectories_.begin(), savedDirectories_.end(), jid), savedDirectories_.end()); + savedDirectories_.insert(savedDirectories_.begin(), jid); + + std::string collapsed; + int i = 0; + for (const auto& jidItem : savedDirectories_) { + if (i >= 15) { + break; + } + if (!collapsed.empty()) { + collapsed += "\n"; + } + collapsed += jidItem.toString(); + ++i; + } + settings_->storeString(SEARCHED_DIRECTORIES, collapsed); + window_->addSavedServices(savedDirectories_); } } diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h index 0423a65..4658301 100644 --- a/Swift/Controllers/Chat/UserSearchController.h +++ b/Swift/Controllers/Chat/UserSearchController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -7,12 +7,12 @@ #pragma once #include <map> +#include <memory> #include <string> #include <vector> -#include <boost/shared_ptr.hpp> +#include <boost/signals2.hpp> -#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/DiscoItems.h> #include <Swiften/Elements/ErrorPayload.h> @@ -24,76 +24,76 @@ #include <Swift/Controllers/Contact.h> namespace Swift { - class UIEventStream; - class UIEvent; - class UserSearchWindow; - class UserSearchWindowFactory; - class IQRouter; - class DiscoServiceWalker; - class RosterController; - class VCardManager; - class ContactSuggester; - class AvatarManager; - class PresenceOracle; - class ProfileSettingsProvider; + class UIEventStream; + class UIEvent; + class UserSearchWindow; + class UserSearchWindowFactory; + class IQRouter; + class DiscoServiceWalker; + class RosterController; + class VCardManager; + class ContactSuggester; + class AvatarManager; + class PresenceOracle; + class ProfileSettingsProvider; - class UserSearchResult { - public: - UserSearchResult(const JID& jid, const std::map<std::string, std::string>& fields) : jid_(jid), fields_(fields) {} - const JID& getJID() const {return jid_;} - const std::map<std::string, std::string>& getFields() const {return fields_;} - private: - JID jid_; - std::map<std::string, std::string> fields_; - }; + class UserSearchResult { + public: + UserSearchResult(const JID& jid, const std::map<std::string, std::string>& fields) : jid_(jid), fields_(fields) {} + const JID& getJID() const {return jid_;} + const std::map<std::string, std::string>& getFields() const {return fields_;} + private: + JID jid_; + std::map<std::string, std::string> fields_; + }; - class UserSearchController { - public: - enum Type {AddContact, StartChat, InviteToChat}; - UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle, ProfileSettingsProvider* settings); - ~UserSearchController(); + class UserSearchController { + public: + enum Type {AddContact, StartChat, InviteToChat}; + UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle, ProfileSettingsProvider* settings); + ~UserSearchController(); - UserSearchWindow* getUserSearchWindow(); - void setCanInitiateImpromptuMUC(bool supportsImpromptu); + UserSearchWindow* getUserSearchWindow(); + void setCanInitiateImpromptuMUC(bool supportsImpromptu); - private: - void handleUIEvent(boost::shared_ptr<UIEvent> event); - void handleFormRequested(const JID& service); - void handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info); - void handleDiscoWalkFinished(); - void handleFormResponse(boost::shared_ptr<SearchPayload> items, ErrorPayload::ref error); - void handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid); - void handleSearchResponse(boost::shared_ptr<SearchPayload> results, ErrorPayload::ref error); - void handleNameSuggestionRequest(const JID& jid); - void handleContactSuggestionsRequested(std::string text); - void handleVCardChanged(const JID& jid, VCard::ref vcard); - void handleAvatarChanged(const JID& jid); - void handlePresenceChanged(Presence::ref presence); - void handleJIDUpdateRequested(const std::vector<JID>& jids); - void handleJIDAddRequested(const std::vector<JID>& jids); - void handleJIDEditingFinished(const JID& jid); - Contact::ref convertJIDtoContact(const JID& jid); - void endDiscoWalker(); - void initializeUserWindow(); + private: + void handleUIEvent(std::shared_ptr<UIEvent> event); + void handleFormRequested(const JID& service); + void handleDiscoServiceFound(const JID& jid, std::shared_ptr<DiscoInfo> info); + void handleDiscoWalkFinished(); + void handleFormResponse(std::shared_ptr<SearchPayload> items, ErrorPayload::ref error); + void handleSearch(std::shared_ptr<SearchPayload> fields, const JID& jid); + void handleSearchResponse(std::shared_ptr<SearchPayload> results, ErrorPayload::ref error); + void handleNameSuggestionRequest(const JID& jid); + void handleContactSuggestionsRequested(std::string text); + void handleVCardChanged(const JID& jid, VCard::ref vcard); + void handleAvatarChanged(const JID& jid); + void handlePresenceChanged(Presence::ref presence); + void handleJIDUpdateRequested(const std::vector<JID>& jids); + void handleJIDAddRequested(const std::vector<JID>& jids); + void handleJIDEditingFinished(const JID& jid); + Contact::ref convertJIDtoContact(const JID& jid); + void endDiscoWalker(); + void initializeUserWindow(); - void loadSavedDirectories(); - void addToSavedDirectories(const JID& jid); + void loadSavedDirectories(); + void addToSavedDirectories(const JID& jid); - private: - Type type_; - JID jid_; - JID suggestionsJID_; - UIEventStream* uiEventStream_; - VCardManager* vcardManager_; - UserSearchWindowFactory* factory_; - IQRouter* iqRouter_; - RosterController* rosterController_; - UserSearchWindow* window_; - DiscoServiceWalker* discoWalker_; - ContactSuggester* contactSuggester_; - AvatarManager* avatarManager_; - PresenceOracle* presenceOracle_; - std::vector<JID> savedDirectories_; - ProfileSettingsProvider* settings_; - }; + private: + Type type_; + JID jid_; + JID suggestionsJID_; + UIEventStream* uiEventStream_; + VCardManager* vcardManager_; + UserSearchWindowFactory* factory_; + IQRouter* iqRouter_; + RosterController* rosterController_; + UserSearchWindow* window_; + DiscoServiceWalker* discoWalker_; + ContactSuggester* contactSuggester_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; + std::vector<JID> savedDirectories_; + ProfileSettingsProvider* settings_; + }; } |