diff options
author | Tobias Markmann <tm@ayena.de> | 2011-11-11 15:01:32 (GMT) |
---|---|---|
committer | Kevin Smith <git@kismith.co.uk> | 2011-11-28 16:44:22 (GMT) |
commit | 86aad702d1f2e831c8e27bbe4ca1402626e4c542 (patch) | |
tree | 3be5a8ed23aef3877c9b313d0ee0f58afb54f57a /Swift/Controllers | |
parent | 81a7776d5ab523894a7c4745baee3988ad9f1ef9 (diff) | |
download | swift-contrib-86aad702d1f2e831c8e27bbe4ca1402626e4c542.zip swift-contrib-86aad702d1f2e831c8e27bbe4ca1402626e4c542.tar.bz2 |
Message Receipts (XEP-0184) support for 1-to-1 conversations (including 1-to-1 MUC).
Warn icon from already existing theme. Check icon from Wikipedia. See Swift/resources/icons/license_info.txt for details.
License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php
Diffstat (limited to 'Swift/Controllers')
-rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 66 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatController.h | 13 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.h | 2 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatsManager.cpp | 57 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatsManager.h | 11 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 107 | ||||
-rw-r--r-- | Swift/Controllers/MainController.cpp | 13 | ||||
-rw-r--r-- | Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h | 19 | ||||
-rw-r--r-- | Swift/Controllers/UIInterfaces/ChatWindow.h | 4 | ||||
-rw-r--r-- | Swift/Controllers/UnitTest/MockChatWindow.h | 2 |
10 files changed, 282 insertions, 12 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index a3d9fb5..9a56300 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -10,37 +10,40 @@ #include <stdio.h> #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Chat/ChatStateNotifier.h> #include <Swiften/Chat/ChatStateTracker.h> #include <Swiften/Client/StanzaChannel.h> -#include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swiften/Client/NickResolver.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/StatusUtil.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Base/foreach.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h> +#include <Swiften/Base/Log.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) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream) { +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) + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts) { 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)); @@ -64,44 +67,53 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ startMessage += "."; chatWindow_->addSystemMessage(startMessage); 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)); handleBareJIDCapsChanged(toJID_); - + 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(jid)); } } ChatController::~ChatController() { + eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1)); nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); delete chatStateNotifier_; delete chatStateTracker_; } void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) { DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_); if (disco) { if (disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) { chatWindow_->setCorrectionEnabled(ChatWindow::Yes); } else { chatWindow_->setCorrectionEnabled(ChatWindow::No); } + if (disco->hasFeature(DiscoInfo::MessageDeliveryReceiptsFeature)) { + contactSupportsReceipts_ = ChatWindow::Yes; + } else { + contactSupportsReceipts_ = ChatWindow::No; + } } else { + SWIFT_LOG(debug) << "No disco info :(" << std::endl; chatWindow_->setCorrectionEnabled(ChatWindow::Maybe); + contactSupportsReceipts_ = ChatWindow::Maybe; } + checkForDisplayingDisplayReceiptsAlert(); } void ChatController::setToJID(const JID& jid) { chatStateNotifier_->setContact(jid); ChatControllerBase::setToJID(jid); Presence::ref presence; if (isInMUC_) { presence = presenceOracle_->getLastPresence(jid); } else { @@ -123,41 +135,87 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me 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()){ setToJID(from); } } chatStateTracker_->handleMessageReceived(message); chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>()); + + 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()); + } + } 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) { eventController_->handleIncomingEvent(messageEvent); } void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) { chatStateNotifier_->addChatStateRequest(message); + if (userWantsReceipts_ && (contactSupportsReceipts_ != ChatWindow::No) && message) { + message->addPayload(boost::make_shared<DeliveryReceiptRequest>()); + } +} + +void ChatController::setContactIsReceivingPresence(bool isReceivingPresence) { + receivingPresenceFromUs_ = isReceivingPresence; +} + +void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) { + if (boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> toggleAllowReceipts = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event)) { + userWantsReceipts_ = toggleAllowReceipts->getEnabled(); + checkForDisplayingDisplayReceiptsAlert(); + } +} + +void ChatController::checkForDisplayingDisplayReceiptsAlert() { + if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) { + chatWindow_->setAlert("This chat doesn't support delivery receipts."); + } else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) { + chatWindow_->setAlert("This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent."); + } else { + chatWindow_->cancelAlert(); + } } 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_)); chatWindow_->replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time()); } else { myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time()); } + 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); + } + 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); diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 2531adb..9c01923 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -5,33 +5,37 @@ */ #pragma once #include "Swift/Controllers/Chat/ChatControllerBase.h" #include <map> #include <string> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + namespace Swift { class AvatarManager; class ChatStateNotifier; class ChatStateTracker; class NickResolver; class EntityCapsProvider; class FileTransferController; + 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); + 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); virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setOnline(bool online); virtual void handleNewFileTransferController(FileTransferController* ftc); + virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/); private: void handlePresenceChange(boost::shared_ptr<Presence> newPresence); std::string getStatusChangeString(boost::shared_ptr<Presence> presence); bool isIncomingMessageFromMe(boost::shared_ptr<Message> message); void postSendMessage(const std::string &body, boost::shared_ptr<Stanza> sentStanza); void preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent); void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent); void preSendMessageRequest(boost::shared_ptr<Message>); @@ -41,25 +45,32 @@ namespace Swift { void dayTicked() {lastWasPresence_ = false;} void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); void handleBareJIDCapsChanged(const JID& jid); 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 handleUIEvent(boost::shared_ptr<UIEvent> event); + void checkForDisplayingDisplayReceiptsAlert(); + 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_; + ChatWindow::Tristate contactSupportsReceipts_; + bool receivingPresenceFromUs_; + bool userWantsReceipts_; std::map<std::string, FileTransferController*> ftControllers; }; } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index a857f3d..f1ecfe8 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -48,35 +48,37 @@ namespace Swift { virtual void setOnline(bool online); virtual 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; int getUnreadCount(); const JID& getToJID() {return toJID_;} void handleCapsChanged(const JID& jid); + 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); /** * 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 bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0; virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}; virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}; 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 */) {} 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(); diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index e648f20..8111c2b 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -15,31 +15,35 @@ #include <Swift/Controllers/Chat/MUCSearchController.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> #include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h> #include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h> #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> #include <Swiften/Presence/PresenceSender.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/MUC/MUCManager.h> #include <Swiften/Elements/ChatState.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> #include <Swiften/MUC/MUCBookmarkManager.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Elements/MUCInvitationPayload.h> +#include <Swiften/Roster/XMPPRoster.h> namespace Swift { typedef std::pair<JID, ChatController*> JIDChatControllerPair; typedef std::pair<JID, MUCController*> JIDMUCControllerPair; #define RECENT_CHATS "recent_chats" ChatsManager::ChatsManager( @@ -55,26 +59,28 @@ ChatsManager::ChatsManager( ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings, FileTransferOverview* ftOverview, + XMPPRoster* roster, bool eagleMode) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), mucRegistry_(mucRegistry), entityCapsProvider_(entityCapsProvider), mucManager(mucManager), ftOverview_(ftOverview), + roster_(roster), eagleMode_(eagleMode) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; iqRouter_ = iqRouter; chatWindowFactory_ = chatWindowFactory; nickResolver_ = nickResolver; presenceOracle_ = presenceOracle; avatarManager_ = NULL; @@ -89,23 +95,31 @@ ChatsManager::ChatsManager( 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, settings); mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); + 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)); setupBookmarks(); loadRecents(); } ChatsManager::~ChatsManager() { + 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_; @@ -130,18 +144,51 @@ void ChatsManager::saveRecents() { profileSettings_->storeString(RECENT_CHATS, recents); } void ChatsManager::handleClearRecentsRequested() { recentChats_.clear(); saveRecents(); handleUnreadCountChanged(NULL); } +void ChatsManager::handleJIDAddedToRoster(const JID &jid) { + updatePresenceReceivingStateOnChatController(jid); +} + +void ChatsManager::handleJIDRemovedFromRoster(const JID &jid) { + updatePresenceReceivingStateOnChatController(jid); +} + +void ChatsManager::handleJIDUpdatedInRoster(const JID &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); + } +} + +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); + } + } +} + void ChatsManager::loadRecents() { std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS)); 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; } @@ -310,18 +357,23 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> 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<ToggleRequestDeliveryReceiptsUIEvent> toggleRequestDeliveryReceipsEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event); + if (toggleRequestDeliveryReceipsEvent) { + userWantsReceipts_ = toggleRequestDeliveryReceipsEvent->getEnabled(); + return; + } 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()); mucControllers_[joinEvent->getJID()]->activateChatWindow(); } @@ -430,23 +482,24 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) break; } } } return controller ? controller : createNewChatController(contact); } ChatController* ChatsManager::createNewChatController(const JID& contact) { assert(chatControllers_.find(contact) == chatControllers_.end()); - ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_); + ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_); chatControllers_[contact] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); + updatePresenceReceivingStateOnChatController(contact); return controller; } ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) { ChatController* controller = getChatControllerIfExists(contact); return controller ? controller : createNewChatController(contact); } ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) { @@ -516,19 +569,19 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional void ChatsManager::handleSearchMUCRequest() { mucSearchController_->openSearchWindow(); } 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>(); - if (!event->isReadable() && !message->getPayload<ChatState>() && !isInvite && !message->hasSubject()) { + if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !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; diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 5d8d555..0c7f492 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -39,22 +39,23 @@ namespace Swift { class ChatListWindowFactory; class TimerFactory; class EntityCapsProvider; class DirectedPresenceSender; class MUCSearchWindowFactory; class ProfileSettingsProvider; class MUCSearchController; class FileTransferOverview; class FileTransferController; + class XMPPRoster; class ChatsManager { 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* settings, FileTransferOverview* ftOverview, bool eagleMode); + 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* settings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); void setOnline(bool enabled); void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<Message> message); private: ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity); void handleChatRequest(const std::string& contact); @@ -75,18 +76,24 @@ namespace Swift { 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 updatePresenceReceivingStateOnChatController(const JID&); 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_; @@ -109,12 +116,14 @@ namespace Swift { 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_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index df519e8..edb431a 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -37,38 +37,45 @@ #include "Swift/Controllers/UnitTest/MockChatWindow.h" #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" #include "Swiften/Presence/PresenceOracle.h" #include "Swiften/Jingle/JingleSessionManager.h" #include "Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" +#include "Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h" #include <Swift/Controllers/ProfileSettingsProvider.h> #include "Swift/Controllers/FileTransfer/FileTransferOverview.h" +#include "Swiften/Elements/DeliveryReceiptRequest.h" +#include "Swiften/Elements/DeliveryReceipt.h" #include <Swiften/Base/Algorithm.h> using namespace Swift; class DummyCapsProvider : public CapsProvider { DiscoInfo::ref getCaps(const std::string&) const {return DiscoInfo::ref(new DiscoInfo());} }; 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_SUITE_END(); public: void setUp() { mocks_ = new MockRepository(); jid_ = JID("test@test.com/resource"); stanzaChannel_ = new DummyStanzaChannel(); iqChannel_ = new DummyIQChannel(); iqRouter_ = new IQRouter(iqChannel_); @@ -87,20 +94,21 @@ public: uiEventStream_ = new UIEventStream(); entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); 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_); + mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, false); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false); avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); }; void tearDown() { //delete chatListWindowFactory delete settings_; delete profileSettings_; @@ -329,23 +337,116 @@ public: 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); + uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true))); + + boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); + CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size()); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); + + xmppRoster_->removeContact(messageJID); + + message->setID("2"); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL((size_t)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); + uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true))); + + boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL((size_t)0, stanzaChannel_->sentStanzas.size()); + + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); + message->setID("2"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL((size_t)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 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); + uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true))); + + boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL((size_t)0, stanzaChannel_->sentStanzas.size()); + + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to); + message->setID("2"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size()); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); + } + +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->addPayload(boost::make_shared<DeliveryReceiptRequest>()); + return message; + } + private: JID jid_; ChatsManager* manager_; - StanzaChannel* stanzaChannel_; + DummyStanzaChannel* stanzaChannel_; IQChannel* iqChannel_; IQRouter* iqRouter_; EventController* eventController_; ChatWindowFactory* chatWindowFactory_; JoinMUCWindowFactory* joinMUCWindowFactory_; NickResolver* nickResolver_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; boost::shared_ptr<DiscoInfo> serverDiscoInfo_; diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index ce347ab..d485abc 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -54,18 +54,19 @@ #include "Swiften/Elements/DiscoInfo.h" #include "Swiften/Disco/CapsInfoGenerator.h" #include "Swiften/Disco/GetDiscoInfoRequest.h" #include "Swiften/Disco/ClientDiscoManager.h" #include "Swiften/VCards/GetVCardRequest.h" #include "Swiften/StringCodecs/SHA1.h" #include "Swiften/StringCodecs/Hexify.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h" +#include "Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" #include "Swift/Controllers/Storages/CertificateStorageFactory.h" #include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h" #include "Swiften/Network/NetworkFactories.h" #include <Swift/Controllers/ProfileController.h> #include <Swift/Controllers/ContactEditController.h> #include <Swift/Controllers/XMPPURIController.h> #include "Swift/Controllers/AdHocManager.h" #include <SwifTools/Idle/IdleDetector.h> @@ -73,18 +74,19 @@ #include <Swiften/FileTransfer/FileTransferManager.h> #include <Swiften/Client/ClientXMLTracer.h> namespace Swift { static const std::string CLIENT_NAME = "Swift"; static const std::string CLIENT_NODE = "http://swift.im"; static const std::string SHOW_NOTIFICATIONS = "showNotifications"; +static const std::string REQUEST_DELIVERYRECEIPTS = "requestDeliveryReceipts"; MainController::MainController( EventLoop* eventLoop, NetworkFactories* networkFactories, UIFactory* uiFactories, SettingsProvider *settings, SystemTray* systemTray, SoundPlayer* soundPlayer, StoragesFactory* storagesFactory, @@ -243,18 +245,23 @@ void MainController::resetClient() { } void MainController::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<ToggleNotificationsUIEvent> notificationsEvent = boost::dynamic_pointer_cast<ToggleNotificationsUIEvent>(event); if (notificationsEvent) { bool enabled = notificationsEvent->getEnabled(); notifier_->setPersistentEnabled(enabled); settings_->storeBool(SHOW_NOTIFICATIONS, enabled); } + boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> deliveryReceiptEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event); + if (deliveryReceiptEvent) { + settings_->storeBool(REQUEST_DELIVERYRECEIPTS, deliveryReceiptEvent->getEnabled()); + } + } void MainController::resetPendingReconnects() { timeBeforeNextReconnect_ = -1; if (reconnectTimer_) { reconnectTimer_->stop(); reconnectTimer_.reset(); } resetCurrentError(); @@ -285,19 +292,19 @@ void MainController::handleConnected() { client_->getFileTransferManager()->startListeningOnPort(randomPort); ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); fileTransferListController_->setFileTransferOverview(ftOverview_); rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); contactEditController_ = new ContactEditController(rosterController_, uiFactory_, uiEventStream_); - chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, eagleMode_); + chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), eagleMode_); client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(client_->getAvatarManager()); eventWindowController_ = new EventWindowController(eventController_, uiFactory_); loginWindow_->morphInto(rosterController_->getWindow()); DiscoInfo discoInfo; @@ -305,18 +312,19 @@ void MainController::handleConnected() { discoInfo.addFeature(DiscoInfo::ChatStatesFeature); discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature); discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature); #ifdef SWIFT_EXPERIMENTAL_FT discoInfo.addFeature(DiscoInfo::JingleFeature); discoInfo.addFeature(DiscoInfo::JingleFTFeature); discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature); discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature); #endif + discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); client_->getDiscoManager()->setCapsNode(CLIENT_NODE); client_->getDiscoManager()->setDiscoInfo(discoInfo); userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_); userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_); adHocManager_ = new AdHocManager(boundJID_, uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow()); } loginWindow_->setIsLoggingIn(false); @@ -329,18 +337,21 @@ void MainController::handleConnected() { client_->getVCardManager()->requestOwnVCard(); rosterController_->setEnabled(true); profileController_->setAvailable(true); contactEditController_->setAvailable(true); /* Send presence later to catch all the incoming presences. */ sendPresence(statusTracker_->getNextPresence()); /* Enable chats last of all, so rejoining MUCs has the right sent presence */ chatsManager_->setOnline(true); + + // notify world about current delivey receipt request setting state. + uiEventStream_->send(boost::make_shared<ToggleRequestDeliveryReceiptsUIEvent>(settings_->getBoolSetting(REQUEST_DELIVERYRECEIPTS, false))); } void MainController::handleEventQueueLengthChange(int count) { dock_->setNumberOfPendingMessages(count); } void MainController::reconnectAfterError() { if (reconnectTimer_) { reconnectTimer_->stop(); diff --git a/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h b/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h new file mode 100644 index 0000000..1ea2db5 --- /dev/null +++ b/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the BSD license. + * See http://www.opensource.org/licenses/bsd-license.php for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIEvents/UIEvent.h" + +namespace Swift { + class ToggleRequestDeliveryReceiptsUIEvent : public UIEvent { + public: + ToggleRequestDeliveryReceiptsUIEvent(bool enable) : enabled_(enable) {} + bool getEnabled() {return enabled_;} + private: + bool enabled_; + }; +} diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index 6fce7a0..fd99514 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -26,18 +26,19 @@ namespace Swift { class Roster; class TabComplete; class RosterItem; class ContactRosterItem; class FileTransferController; class ChatWindow { public: enum AckState {Pending, Received, Failed}; + enum ReceiptState {ReceiptRequested, ReceiptReceived}; enum Tristate {Yes, No, Maybe}; enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor}; enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed}; ChatWindow() {} virtual ~ChatWindow() {}; /** Add message to window. * @return id of added message (for acks). */ @@ -51,18 +52,21 @@ namespace Swift { virtual void addErrorMessage(const std::string& message) = 0; virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; // File transfer related stuff virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0; virtual void setFileTransferProgress(std::string, const int percentageDone) = 0; virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0; virtual void addMUCInvitation(const JID& jid, const std::string& reason, const std::string& password) = 0; + // message receipts + virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0; + virtual void setContactChatState(ChatState::ChatStateType state) = 0; virtual void setName(const std::string& name) = 0; virtual void show() = 0; virtual void activate() = 0; virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) = 0; virtual void setSecurityLabelsEnabled(bool enabled) = 0; virtual void setCorrectionEnabled(Tristate enabled) = 0; virtual void setUnreadMessageCount(int count) = 0; virtual void convertToMUC() = 0; diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 7e31179..5e43549 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -19,18 +19,20 @@ namespace Swift { virtual void addSystemMessage(const std::string& /*message*/) {}; virtual void addErrorMessage(const std::string& /*message*/) {}; virtual void addPresenceMessage(const std::string& /*message*/) {}; // File transfer related stuff virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; }; virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { }; virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { }; + virtual void setMessageReceiptState(const std::string &/* id */, ReceiptState /* state */) { } + virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}; virtual void setName(const std::string& name) {name_ = name;}; virtual void show() {}; virtual void activate() {}; virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;}; virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;}; virtual void setUnreadMessageCount(int /*count*/) {}; virtual void convertToMUC() {}; virtual void setSecurityLabelsError() {}; |