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 | |
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
44 files changed, 779 insertions, 20 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() {}; diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp index 47cb1a0..a2e4651 100644 --- a/Swift/QtUI/MessageSnippet.cpp +++ b/Swift/QtUI/MessageSnippet.cpp @@ -26,19 +26,19 @@ MessageSnippet::MessageSnippet(const QString& message, const QString& sender, co else { if (appendToPrevious) { content_ = theme->getOutgoingNextContent(); } else { content_ = theme->getOutgoingContent(); } } - content_.replace("%message%", "<span class='swift_message'>" + escape(message) + "</span><span class='swift_ack'></span>"); + content_.replace("%message%", "<span class='swift_message'>" + escape(message) + "</span><span class='swift_ack'></span><span class='swift_receipt'></span>"); content_.replace("%sender%", escape(sender)); content_.replace("%time%", "<span class='swift_time'>" + timeToEscapedString(time) + "</span>"); content_.replace("%userIconPath%", escape(iconURI)); content_ = "<div id='" + id + "'>" + content_ + "</div>"; } MessageSnippet::~MessageSnippet() { } diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 1c3dd37..db00ba0 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -190,18 +190,34 @@ void QtChatView::copySelectionToClipboard() { void QtChatView::setAckXML(const QString& id, const QString& xml) { QWebElement message = document_.findFirst("#" + id); /* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */ if (message.isNull()) return; QWebElement ackElement = message.findFirst("span.swift_ack"); assert(!ackElement.isNull()); ackElement.setInnerXml(xml); } +void QtChatView::setReceiptXML(const QString& id, const QString& xml) { + QWebElement message = document_.findFirst("#" + id); + if (message.isNull()) return; + QWebElement receiptElement = message.findFirst("span.swift_receipt"); + assert(!receiptElement.isNull()); + receiptElement.setInnerXml(xml); +} + +void QtChatView::displayReceiptInfo(const QString& id, bool showIt) { + QWebElement message = document_.findFirst("#" + id); + if (message.isNull()) return; + QWebElement receiptElement = message.findFirst("span.swift_receipt"); + assert(!receiptElement.isNull()); + receiptElement.setStyleProperty("display", showIt ? "inline" : "none"); +} + void QtChatView::rememberScrolledToBottom() { isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); } void QtChatView::scrollToBottom() { isAtBottom_ = true; webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)); webView_->update(); /* Work around redraw bug in some versions of Qt. */ } diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index cf47029..0cc521a 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -29,18 +29,21 @@ namespace Swift { public: QtChatView(QtChatTheme* theme, QWidget* parent); void addMessage(boost::shared_ptr<ChatSnippet> snippet); void addLastSeenLine(); void replaceLastMessage(const QString& newMessage); void replaceLastMessage(const QString& newMessage, const QString& note); void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time); void rememberScrolledToBottom(); void setAckXML(const QString& id, const QString& xml); + void setReceiptXML(const QString& id, const QString& xml); + void displayReceiptInfo(const QString& id, bool showIt); + QString getLastSentMessage(); void addToJSEnvironment(const QString&, QObject*); void setFileTransferProgress(QString id, const int percentageDone); void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg); signals: void gotFocus(); void fontResized(int); diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index f0f268d..88f326f 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -469,25 +469,44 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri } void QtChatWindow::flash() { emit requestFlash(); } void QtChatWindow::setAckState(std::string const& id, ChatWindow::AckState state) { QString xml; switch (state) { - case ChatWindow::Pending: xml = "<img src='qrc:/icons/throbber.gif' alt='" + tr("This message has not been received by your server yet.") + "'/>"; break; - case ChatWindow::Received: xml = ""; break; + case ChatWindow::Pending: + xml = "<img src='qrc:/icons/throbber.gif' alt='" + tr("This message has not been received by your server yet.") + "'/>"; + messageLog_->displayReceiptInfo(P2QSTRING(id), false); + break; + case ChatWindow::Received: + xml = ""; + messageLog_->displayReceiptInfo(P2QSTRING(id), true); + break; case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' alt='" + tr("This message may not have been transmitted.") + "'/>"; break; } messageLog_->setAckXML(P2QSTRING(id), xml); } +void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) { + QString xml; + switch (state) { + case ChatWindow::ReceiptReceived: + xml = "<img src='qrc:/icons/check.png' alt'" + tr("The receipt for this message has been received.") + "'/>"; + break; + case ChatWindow::ReceiptRequested: + xml = "<img src='qrc:/icons/warn.png' alt='" + tr("The receipt for this message has not yet been received. The receipient(s) might not have received this message.") + "'/>"; + break; + } + messageLog_->setReceiptXML(P2QSTRING(id), xml); +} + int QtChatWindow::getCount() { return unreadCount_; } std::string QtChatWindow::addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { return addMessage(" *" + message + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time); } std::string formatSize(const boost::uintmax_t bytes) { diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index 233f2bc..55e929d 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -62,18 +62,22 @@ namespace Swift { void setName(const std::string& name); void setInputEnabled(bool enabled); QtTabbable::AlertType getWidgetAlertState(); void setContactChatState(ChatState::ChatStateType state); void setRosterModel(Roster* roster); void setTabComplete(TabComplete* completer); int getCount(); void replaceLastMessage(const std::string& message); void setAckState(const std::string& id, AckState state); + + // message receipts + void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state); + void flash(); QByteArray getSplitterState(); virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions); void setSubject(const std::string& subject); void showRoomConfigurationForm(Form::ref); void addMUCInvitation(const JID& jid, const std::string& reason, const std::string& password); void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&); public slots: diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index e339d79..5a8b9ab 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -449,18 +449,22 @@ void QtLoginWindow::bringToFront() { window()->hide(); #endif } } void QtLoginWindow::hide() { window()->hide(); } +QtLoginWindow::QtMenus QtLoginWindow::getMenus() const { + return QtMenus(swiftMenu_, generalMenu_); +} + void QtLoginWindow::resizeEvent(QResizeEvent*) { emit geometryChanged(); } void QtLoginWindow::moveEvent(QMoveEvent*) { emit geometryChanged(); } bool QtLoginWindow::askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate) { diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index acf327f..a830af5 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -21,31 +21,38 @@ class QLabel; class QToolButton; class QComboBox; namespace Swift { class QtLoginWindow : public QMainWindow, public LoginWindow { Q_OBJECT public: + struct QtMenus { + QtMenus(QMenu* swiftMenu, QMenu* generalMenu) : swiftMenu(swiftMenu), generalMenu(generalMenu) {} + QMenu* swiftMenu; + QMenu* generalMenu; + }; + + public: QtLoginWindow(UIEventStream* uiEventStream, bool eagleMode); void morphInto(MainWindow *mainWindow); virtual void loggedOut(); virtual void setMessage(const std::string& message); virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate); virtual void removeAvailableAccount(const std::string& jid); virtual void setLoginAutomatically(bool loginAutomatically); virtual void setIsLoggingIn(bool loggingIn); void selectUser(const std::string& user); bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate); void hide(); - + QtMenus getMenus() const; virtual void quit(); signals: void geometryChanged(); private slots: void loginClicked(); void handleCertficateChecked(bool); void handleQuit(); diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index a4ce98e..199e388 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -19,32 +19,34 @@ #include <QMenuBar> #include <QToolBar> #include <QAction> #include <QTabWidget> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtTabWidget.h> #include <Swift/QtUI/QtSettingsProvider.h> #include <Swift/QtUI/QtUIPreferences.h> +#include <Swift/QtUI/QtLoginWindow.h> #include <Roster/QtRosterWidget.h> #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> +#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h> namespace Swift { #define CURRENT_ROSTER_TAB "current_roster_tab" -QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventStream, QtUIPreferences* uiPreferences) : QWidget(), MainWindow(false) { +QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventStream, QtUIPreferences* uiPreferences, QtLoginWindow::QtMenus loginMenus) : QWidget(), MainWindow(false), loginMenus_(loginMenus) { uiEventStream_ = uiEventStream; uiPreferences_ = uiPreferences; settings_ = settings; setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); mainLayout->setContentsMargins(0,0,0,0); mainLayout->setSpacing(0); meView_ = new QtRosterHeader(settings, this); mainLayout->addWidget(meView_); @@ -117,18 +119,24 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool))); actionsMenu->addAction(chatUserAction_); serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this); actionsMenu->addMenu(serverAdHocMenu_); actionsMenu->addSeparator(); QAction* signOutAction = new QAction(tr("&Sign Out"), this); connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction())); actionsMenu->addAction(signOutAction); + toggleRequestDeliveryReceipts_ = new QAction(tr("&Request Delivery Receipts"), this); + toggleRequestDeliveryReceipts_->setCheckable(true); + toggleRequestDeliveryReceipts_->setChecked(false); + connect(toggleRequestDeliveryReceipts_, SIGNAL(toggled(bool)), SLOT(handleToggleRequestDeliveryReceipts(bool))); + loginMenus_.generalMenu->addAction(toggleRequestDeliveryReceipts_); + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QAction::setEnabled, editUserAction_, _1)); setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); QAction* adHocAction = new QAction(tr("Collecting commands..."), this); adHocAction->setEnabled(false); serverAdHocMenu_->addAction(adHocAction); serverAdHocCommandActions_.append(adHocAction); lastOfflineState_ = false; @@ -137,18 +145,22 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS QtMainWindow::~QtMainWindow() { uiEventStream_->onUIEvent.disconnect(boost::bind(&QtMainWindow::handleUIEvent, this, _1)); } void QtMainWindow::handleTabChanged(int index) { settings_->storeInt(CURRENT_ROSTER_TAB, index); } +void QtMainWindow::handleToggleRequestDeliveryReceipts(bool enabled) { + uiEventStream_->send(boost::make_shared<ToggleRequestDeliveryReceiptsUIEvent>(enabled)); +} + QtEventWindow* QtMainWindow::getEventWindow() { return eventWindow_; } QtChatListWindow* QtMainWindow::getChatListWindow() { return chatListWindow_; } void QtMainWindow::setRosterModel(Roster* roster) { @@ -186,18 +198,19 @@ void QtMainWindow::handleAddUserActionTriggered(bool /*checked*/) { uiEventStream_->send(event); } void QtMainWindow::handleChatUserActionTriggered(bool /*checked*/) { boost::shared_ptr<UIEvent> event(new RequestChatWithUserDialogUIEvent()); uiEventStream_->send(event); } void QtMainWindow::handleSignOutAction() { + loginMenus_.generalMenu->removeAction(toggleRequestDeliveryReceipts_); onSignOutRequest(); } void QtMainWindow::handleEditProfileAction() { uiEventStream_->send(boost::make_shared<RequestProfileEditorUIEvent>()); } void QtMainWindow::handleJoinMUCAction() { uiEventStream_->send(boost::make_shared<RequestJoinMUCUIEvent>()); @@ -206,18 +219,22 @@ void QtMainWindow::handleJoinMUCAction() { void QtMainWindow::handleStatusChanged(StatusShow::Type showType, const QString &statusMessage) { onChangeStatusRequest(showType, Q2PSTRING(statusMessage)); } void QtMainWindow::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<ToggleShowOfflineUIEvent> toggleEvent = boost::dynamic_pointer_cast<ToggleShowOfflineUIEvent>(event); if (toggleEvent) { handleShowOfflineToggled(toggleEvent->getShow()); } + boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> deliveryReceiptEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event); + if (deliveryReceiptEvent) { + toggleRequestDeliveryReceipts_->setChecked(deliveryReceiptEvent->getEnabled()); + } } void QtMainWindow::handleShowOfflineToggled(bool state) { if (state != lastOfflineState_) { lastOfflineState_ = state; showOfflineAction_->setChecked(state); uiEventStream_->onUIEvent(boost::shared_ptr<UIEvent>(new ToggleShowOfflineUIEvent(state))); } } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index afcb57c..3c8bdec 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -7,18 +7,19 @@ #pragma once #include <QWidget> #include <QMenu> #include <QList> #include "Swift/Controllers/UIInterfaces/MainWindow.h" #include "Swift/QtUI/QtRosterHeader.h" #include "Swift/QtUI/EventViewer/QtEventWindow.h" #include "Swift/QtUI/ChatList/QtChatListWindow.h" +#include "Swift/QtUI/QtLoginWindow.h" #include <vector> class QComboBox; class QLineEdit; class QPushButton; class QToolBar; class QAction; class QMenu; @@ -29,19 +30,19 @@ namespace Swift { class TreeWidget; class UIEventStream; class QtTabWidget; class QtSettingsProvider; class QtUIPreferences; class QtMainWindow : public QWidget, public MainWindow { Q_OBJECT public: - QtMainWindow(QtSettingsProvider*, UIEventStream* eventStream, QtUIPreferences* uiPreferences); + QtMainWindow(QtSettingsProvider*, UIEventStream* eventStream, QtUIPreferences* uiPreferences, QtLoginWindow::QtMenus loginMenus); virtual ~QtMainWindow(); std::vector<QMenu*> getMenus() {return menus_;} void setMyNick(const std::string& name); void setMyJID(const JID& jid); void setMyAvatarPath(const std::string& path); void setMyStatusText(const std::string& status); void setMyStatusType(StatusShow::Type type); void setConnecting(); QtEventWindow* getEventWindow(); @@ -56,28 +57,31 @@ namespace Swift { void handleSignOutAction(); void handleEditProfileAction(); void handleAddUserActionTriggered(bool checked); void handleChatUserActionTriggered(bool checked); void handleAdHocActionTriggered(bool checked); void handleEventCountUpdated(int count); void handleChatCountUpdated(int count); void handleEditProfileRequest(); void handleTabChanged(int index); + void handleToggleRequestDeliveryReceipts(bool enabled); private: QtSettingsProvider* settings_; + QtLoginWindow::QtMenus loginMenus_; std::vector<QMenu*> menus_; QtRosterWidget* treeWidget_; QtRosterHeader* meView_; QAction* addUserAction_; QAction* editUserAction_; QAction* chatUserAction_; QAction* showOfflineAction_; + QAction* toggleRequestDeliveryReceipts_; QMenu* serverAdHocMenu_; QtTabWidget* tabs_; QWidget* contactsTabWidget_; QWidget* eventsTabWidget_; QtEventWindow* eventWindow_; QtChatListWindow* chatListWindow_; UIEventStream* uiEventStream_; bool lastOfflineState_; std::vector<DiscoItems::Item> serverAdHocCommands_; diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 8a026f2..88da781 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -49,19 +49,19 @@ FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { tabs->addTab(widget); if (!tabs->isVisible()) { tabs->show(); } widget->show(); return widget; } MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) { - lastMainWindow = new QtMainWindow(settings, eventStream, uiPreferences); + lastMainWindow = new QtMainWindow(settings, eventStream, uiPreferences, loginWindow->getMenus()); return lastMainWindow; } LoginWindow* QtUIFactory::createLoginWindow(UIEventStream* eventStream) { loginWindow = new QtLoginWindow(eventStream, eagleMode); if (netbookSplitter) { netbookSplitter->insertWidget(0, loginWindow); } connect(systemTray, SIGNAL(clicked()), loginWindow, SLOT(bringToFront())); diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc index d2798e4..61d8cc0 100644 --- a/Swift/QtUI/Swift.qrc +++ b/Swift/QtUI/Swift.qrc @@ -5,18 +5,20 @@ <file alias="logo-chat-16.png">../resources/logo/logo-chat-16.png</file> <file alias="logo-shaded-text.256.png">../resources/logo/logo-shaded-text.256.png</file> <file alias="icons/online.png">../resources/icons/online.png</file> <file alias="icons/connecting.mng">../resources/icons/connecting.mng</file> <file alias="icons/away.png">../resources/icons/away.png</file> <file alias="icons/dnd.png">../resources/icons/dnd.png</file> <file alias="icons/offline.png">../resources/icons/offline.png</file> <file alias="icons/certificate.png">../resources/icons/certificate.png</file> <file alias="icons/error.png">../resources/icons/error.png</file> + <file alias="icons/warn.png">../resources/icons/warn.png</file> + <file alias="icons/check.png">../resources/icons/check.png</file> <file alias="icons/throbber.gif">../resources/icons/throbber.gif</file> <file alias="icons/avatar.png">../resources/icons/avatar.png</file> <file alias="icons/no-avatar.png">../resources/icons/no-avatar.png</file> <file alias="icons/tray-standard.png">../resources/icons/tray-standard.png</file> <file alias="icons/new-chat.png">../resources/icons/new-chat.png</file> <file alias="icons/actions.png">../resources/icons/actions.png</file> <file alias="COPYING">../../COPYING</file> </qresource> </RCC> diff --git a/Swift/resources/icons/check.png b/Swift/resources/icons/check.png Binary files differnew file mode 100644 index 0000000..6e29fd8 --- /dev/null +++ b/Swift/resources/icons/check.png diff --git a/Swift/resources/icons/license_info.txt b/Swift/resources/icons/license_info.txt new file mode 100644 index 0000000..fffbf1d --- /dev/null +++ b/Swift/resources/icons/license_info.txt @@ -0,0 +1,5 @@ +Resources: + +Filename Source License +warn.png Swift/resources/themes/Default/images/warn.png BSD +check.png http://en.wikipedia.org/wiki/File:Green_check.svg Public Domain
\ No newline at end of file diff --git a/Swift/resources/icons/warn.png b/Swift/resources/icons/warn.png Binary files differnew file mode 100644 index 0000000..357337e --- /dev/null +++ b/Swift/resources/icons/warn.png diff --git a/Swiften/Elements/DeliveryReceipt.h b/Swiften/Elements/DeliveryReceipt.h new file mode 100644 index 0000000..927da73 --- /dev/null +++ b/Swiften/Elements/DeliveryReceipt.h @@ -0,0 +1,34 @@ +/* + * 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 <Swiften/Elements/Payload.h> + +namespace Swift { + +class DeliveryReceipt : public Payload { + public: + typedef boost::shared_ptr<DeliveryReceipt> ref; + + public: + DeliveryReceipt() {} + + DeliveryReceipt(const std::string& msgId) : receivedID_(msgId) {} + + void setReceivedID(const std::string& msgId) { + receivedID_ = msgId; + } + + std::string getReceivedID() const { + return receivedID_; + } + + private: + std::string receivedID_; +}; + +} diff --git a/Swiften/Elements/DeliveryReceiptRequest.h b/Swiften/Elements/DeliveryReceiptRequest.h new file mode 100644 index 0000000..3998785 --- /dev/null +++ b/Swiften/Elements/DeliveryReceiptRequest.h @@ -0,0 +1,21 @@ +/* + * 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 <Swiften/Elements/Payload.h> + +namespace Swift { + +class DeliveryReceiptRequest : public Payload { + public: + typedef boost::shared_ptr<DeliveryReceiptRequest> ref; + + public: + DeliveryReceiptRequest() {} +}; + +} diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp index 35d4d04..a4ce079 100644 --- a/Swiften/Elements/DiscoInfo.cpp +++ b/Swiften/Elements/DiscoInfo.cpp @@ -15,19 +15,19 @@ const std::string DiscoInfo::SecurityLabelsFeature = std::string("urn:xmpp:sec-l const std::string DiscoInfo::SecurityLabelsCatalogFeature = std::string("urn:xmpp:sec-label:catalog:2"); const std::string DiscoInfo::JabberSearchFeature = std::string("jabber:iq:search"); const std::string DiscoInfo::CommandsFeature = std::string("http://jabber.org/protocol/commands"); const std::string DiscoInfo::MessageCorrectionFeature = std::string("urn:xmpp:message-correct:0"); const std::string DiscoInfo::JingleFeature = std::string("urn:xmpp:jingle:1"); const std::string DiscoInfo::JingleFTFeature = std::string("urn:xmpp:jingle:apps:file-transfer:3"); const std::string DiscoInfo::JingleTransportsIBBFeature = std::string("urn:xmpp:jingle:transports:ibb:1"); const std::string DiscoInfo::JingleTransportsS5BFeature = std::string("urn:xmpp:jingle:transports:s5b:1"); const std::string DiscoInfo::Bytestream = std::string("http://jabber.org/protocol/bytestreams"); - +const std::string DiscoInfo::MessageDeliveryReceiptsFeature = std::string("urn:xmpp:receipts"); bool DiscoInfo::Identity::operator<(const Identity& other) const { if (category_ == other.category_) { if (type_ == other.type_) { if (lang_ == other.lang_) { return name_ < other.name_; } else { return lang_ < other.lang_; diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h index 6d6e722..3173ee6 100644 --- a/Swiften/Elements/DiscoInfo.h +++ b/Swiften/Elements/DiscoInfo.h @@ -22,18 +22,19 @@ namespace Swift { static const std::string SecurityLabelsCatalogFeature; static const std::string JabberSearchFeature; static const std::string CommandsFeature; static const std::string MessageCorrectionFeature; static const std::string JingleFeature; static const std::string JingleFTFeature; static const std::string JingleTransportsIBBFeature; static const std::string JingleTransportsS5BFeature; static const std::string Bytestream; + static const std::string MessageDeliveryReceiptsFeature; class Identity { public: Identity(const std::string& name, const std::string& category = "client", const std::string& type = "pc", const std::string& lang = "") : name_(name), category_(category), type_(type), lang_(lang) { } const std::string& getCategory() const { return category_; } diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.cpp b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.cpp new file mode 100644 index 0000000..347200d --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the BSD license. + * See http://www.opensource.org/licenses/bsd-license.php for more information. + */ + +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h> + +#include <boost/optional.hpp> + +namespace Swift { + +DeliveryReceiptParser::DeliveryReceiptParser() : level_(0) { +} + +void DeliveryReceiptParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributeMap) { + if (level_ == 0) { + if (element == "received") { + if (attributeMap.getAttributeValue("id").is_initialized()) { + getPayloadInternal()->setReceivedID(attributeMap.getAttributeValue("id").get()); + } + } + } + ++level_; +} + +void DeliveryReceiptParser::handleEndElement(const std::string&, const std::string&) { + --level_; +} + +void DeliveryReceiptParser::handleCharacterData(const std::string&) { + +} + +} diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h new file mode 100644 index 0000000..90a0921 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h @@ -0,0 +1,24 @@ +/* + * 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 <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + class DeliveryReceiptParser : public GenericPayloadParser<DeliveryReceipt> { + public: + DeliveryReceiptParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributeMap); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + int level_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h new file mode 100644 index 0000000..259e04a --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h @@ -0,0 +1,29 @@ +/* + * 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 <Swiften/Parser/PayloadParserFactory.h> +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h> + +namespace Swift { + class PayloadParserFactoryCollection; + + class DeliveryReceiptParserFactory : public PayloadParserFactory { + public: + DeliveryReceiptParserFactory() { + } + + virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const { + return ns == "urn:xmpp:receipts" && element == "received"; + } + + virtual PayloadParser* createPayloadParser() { + return new DeliveryReceiptParser(); + } + + }; +} diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.cpp b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.cpp new file mode 100644 index 0000000..f10cbc9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the BSD license. + * See http://www.opensource.org/licenses/bsd-license.php for more information. + */ + +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h> + +#include <boost/optional.hpp> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +DeliveryReceiptRequestParser::DeliveryReceiptRequestParser() { +} + +void DeliveryReceiptRequestParser::handleStartElement(const std::string&, const std::string&, const AttributeMap&) { + +} + +void DeliveryReceiptRequestParser::handleEndElement(const std::string&, const std::string&) { + +} + +void DeliveryReceiptRequestParser::handleCharacterData(const std::string&) { + +} + +} diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h new file mode 100644 index 0000000..55f7997 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h @@ -0,0 +1,21 @@ +/* + * 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 <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + class DeliveryReceiptRequestParser : public GenericPayloadParser<DeliveryReceiptRequest> { + public: + DeliveryReceiptRequestParser(); + + virtual void handleStartElement(const std::string&, const std::string&, const AttributeMap&); + virtual void handleEndElement(const std::string&, const std::string&); + virtual void handleCharacterData(const std::string& data); + }; +} diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h new file mode 100644 index 0000000..9bd98c2 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h @@ -0,0 +1,29 @@ +/* + * 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 <Swiften/Parser/PayloadParserFactory.h> +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h> + +namespace Swift { + class PayloadParserFactoryCollection; + + class DeliveryReceiptRequestParserFactory : public PayloadParserFactory { + public: + DeliveryReceiptRequestParserFactory() { + } + + virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const { + return ns == "urn:xmpp:receipts" && element == "request"; + } + + virtual PayloadParser* createPayloadParser() { + return new DeliveryReceiptRequestParser(); + } + + }; +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index 04e9fa6..01addf5 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -58,18 +58,20 @@ #include <Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.h> #include <Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.h> #include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h> #include <Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h> #include <Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.h> #include <Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h> #include <Swiften/Parser/PayloadParsers/S5BProxyRequestParser.h> #include <Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.h> #include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h> +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h> +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h> using namespace boost; namespace Swift { FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(boost::make_shared<GenericPayloadParserFactory<IBBParser> >("", "http://jabber.org/protocol/ibb")); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<StatusShowParser> >("show")); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<StatusParser> >("status")); @@ -115,18 +117,20 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleReasonParser> >("reason", "urn:xmpp:jingle:1")); factories_.push_back(boost::make_shared<JingleContentPayloadParserFactory>(this)); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleIBBTransportMethodPayloadParser> >("transport", "urn:xmpp:jingle:transports:ibb:1")); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleS5BTransportMethodPayloadParser> >("transport", "urn:xmpp:jingle:transports:s5b:1")); factories_.push_back(boost::make_shared<JingleFileTransferDescriptionParserFactory>(this)); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<StreamInitiationFileInfoParser> >("file", "http://jabber.org/protocol/si/profile/file-transfer")); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferReceivedParser> >("received", "urn:xmpp:jingle:apps:file-transfer:3")); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferHashParser> >("checksum")); factories_.push_back(boost::make_shared<GenericPayloadParserFactory<S5BProxyRequestParser> >("query", "http://jabber.org/protocol/bytestreams")); + factories_.push_back(boost::make_shared<DeliveryReceiptParserFactory>()); + factories_.push_back(boost::make_shared<DeliveryReceiptRequestParserFactory>()); foreach(shared_ptr<PayloadParserFactory> factory, factories_) { addFactory(factory.get()); } defaultFactory_ = new RawXMLPayloadParserFactory(); setDefaultFactory(defaultFactory_); } FullPayloadParserFactoryCollection::~FullPayloadParserFactoryCollection() { diff --git a/Swiften/Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp new file mode 100644 index 0000000..919c342 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the BSD license. + * See http://www.opensource.org/licenses/bsd-license.php for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swiften/Elements/Message.h> +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h> +#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h> +#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h> + +using namespace Swift; + +class DeliveryReceiptParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(DeliveryReceiptParserTest); + CPPUNIT_TEST(testParseXEP0184Example3); + CPPUNIT_TEST(testParseXEP0184Example4); + CPPUNIT_TEST_SUITE_END(); + + public: + void testParseXEP0184Example3() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse("<request xmlns='urn:xmpp:receipts'/>")); + + DeliveryReceiptRequest::ref request = boost::dynamic_pointer_cast<DeliveryReceiptRequest>(parser.getPayload()); + + CPPUNIT_ASSERT(request); + } + + void testParseXEP0184Example4() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse("<received xmlns='urn:xmpp:receipts' id='richard2-4.1.247'/>")); + + DeliveryReceipt::ref receipt = boost::dynamic_pointer_cast<DeliveryReceipt>(parser.getPayload()); + + CPPUNIT_ASSERT_EQUAL(std::string("richard2-4.1.247"), receipt->getReceivedID()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DeliveryReceiptParserTest); + diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index e1ec1d8..e4c2778 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -64,18 +64,20 @@ sources = [ "PayloadParsers/MUCAdminPayloadParser.cpp", "PayloadParsers/MUCOwnerPayloadParser.cpp", "PayloadParsers/MUCDestroyPayloadParser.cpp", "PayloadParsers/MUCInvitationPayloadParser.cpp", "PayloadParsers/MUCItemParser.cpp", "PayloadParsers/NicknameParser.cpp", "PayloadParsers/ReplaceParser.cpp", "PayloadParsers/LastParser.cpp", "PayloadParsers/S5BProxyRequestParser.cpp", + "PayloadParsers/DeliveryReceiptParser.cpp", + "PayloadParsers/DeliveryReceiptRequestParser.cpp", "PlatformXMLParserFactory.cpp", "PresenceParser.cpp", "SerializingParser.cpp", "StanzaParser.cpp", "StreamErrorParser.cpp", "StreamFeaturesParser.cpp", "StreamManagementEnabledParser.cpp", "StreamResumeParser.cpp", "StreamResumedParser.cpp", diff --git a/Swiften/SConscript b/Swiften/SConscript index ee21ba8..8c3ad42 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -172,18 +172,20 @@ if env["SCONS_STAGE"] == "build" : "Serializer/PayloadSerializers/NicknameSerializer.cpp", "Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp", "Serializer/PayloadSerializers/JinglePayloadSerializer.cpp", "Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp", "Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp", "Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.cpp", "Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.cpp", "Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.cpp", "Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.cpp", + "Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp", + "Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp", "Serializer/PresenceSerializer.cpp", "Serializer/StanzaSerializer.cpp", "Serializer/StreamErrorSerializer.cpp", "Serializer/StreamFeaturesSerializer.cpp", "Serializer/XML/XMLElement.cpp", "Serializer/XML/XMLNode.cpp", "Serializer/XMPPSerializer.cpp", "Session/Session.cpp", "Session/SessionTracer.cpp", @@ -303,18 +305,19 @@ if env["SCONS_STAGE"] == "build" : File("Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/VCardParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/StorageParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/ReplaceTest.cpp"), File("Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/MUCUserPayloadParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp"), File("Parser/UnitTest/BOSHBodyExtractorTest.cpp"), File("Parser/UnitTest/AttributeMapTest.cpp"), File("Parser/UnitTest/IQParserTest.cpp"), File("Parser/UnitTest/GenericPayloadTreeParserTest.cpp"), File("Parser/UnitTest/MessageParserTest.cpp"), File("Parser/UnitTest/PayloadParserFactoryCollectionTest.cpp"), File("Parser/UnitTest/PresenceParserTest.cpp"), File("Parser/UnitTest/StanzaAckParserTest.cpp"), File("Parser/UnitTest/SerializingParserTest.cpp"), @@ -351,18 +354,19 @@ if env["SCONS_STAGE"] == "build" : File("Serializer/PayloadSerializers/UnitTest/StreamInitiationSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/InBandRegistrationPayloadSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/VCardUpdateSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/VCardSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp"), File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"), File("Serializer/UnitTest/AuthSuccessSerializerTest.cpp"), File("Serializer/UnitTest/AuthChallengeSerializerTest.cpp"), File("Serializer/UnitTest/AuthRequestSerializerTest.cpp"), File("Serializer/UnitTest/AuthResponseSerializerTest.cpp"), File("Serializer/UnitTest/XMPPSerializerTest.cpp"), File("Serializer/XML/UnitTest/XMLElementTest.cpp"), File("StreamManagement/UnitTest/StanzaAckRequesterTest.cpp"), File("StreamManagement/UnitTest/StanzaAckResponderTest.cpp"), diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp new file mode 100644 index 0000000..eeb0d90 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the BSD license. + * See http://www.opensource.org/licenses/bsd-license.php for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h> +#include <Swiften/Serializer/XML/XMLElement.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +DeliveryReceiptRequestSerializer::DeliveryReceiptRequestSerializer() : GenericPayloadSerializer<DeliveryReceiptRequest>() { +} + +std::string DeliveryReceiptRequestSerializer::serializePayload(boost::shared_ptr<DeliveryReceiptRequest> /* request*/) const { + XMLElement requestXML("request", "urn:xmpp:receipts"); + return requestXML.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h new file mode 100644 index 0000000..aa3c315 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.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 <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> + +namespace Swift { + class DeliveryReceiptRequestSerializer : public GenericPayloadSerializer<DeliveryReceiptRequest> { + public: + DeliveryReceiptRequestSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<DeliveryReceiptRequest> request) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp new file mode 100644 index 0000000..a9033b2 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the BSD license. + * See http://www.opensource.org/licenses/bsd-license.php for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h> +#include <Swiften/Serializer/XML/XMLElement.h> + +namespace Swift { + +DeliveryReceiptSerializer::DeliveryReceiptSerializer() : GenericPayloadSerializer<DeliveryReceipt>() { +} + +std::string DeliveryReceiptSerializer::serializePayload(boost::shared_ptr<DeliveryReceipt> receipt) const { + XMLElement received("received", "urn:xmpp:receipts"); + received.setAttribute("id", receipt->getReceivedID()); + return received.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h new file mode 100644 index 0000000..5fda3ea --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.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 <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/DeliveryReceipt.h> + +namespace Swift { + class DeliveryReceiptSerializer : public GenericPayloadSerializer<DeliveryReceipt> { + public: + DeliveryReceiptSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<DeliveryReceipt> receipt) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index 87dfbc3..499a185 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -54,18 +54,20 @@ #include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h> #include <Swiften/Serializer/PayloadSerializers/S5BProxyRequestSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h> namespace Swift { FullPayloadSerializerCollection::FullPayloadSerializerCollection() { serializers_.push_back(new IBBSerializer()); serializers_.push_back(new BodySerializer()); serializers_.push_back(new SubjectSerializer()); serializers_.push_back(new ChatStateSerializer()); serializers_.push_back(new PrioritySerializer()); @@ -110,18 +112,20 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() { serializers_.push_back(new StreamInitiationFileInfoSerializer()); serializers_.push_back(new JingleContentPayloadSerializer()); serializers_.push_back(new JingleFileTransferDescriptionSerializer()); serializers_.push_back(new JingleFileTransferHashSerializer()); serializers_.push_back(new JingleFileTransferReceivedSerializer()); serializers_.push_back(new JingleIBBTransportPayloadSerializer()); serializers_.push_back(new JingleS5BTransportPayloadSerializer()); serializers_.push_back(new JinglePayloadSerializer(this)); serializers_.push_back(new S5BProxyRequestSerializer()); + serializers_.push_back(new DeliveryReceiptSerializer()); + serializers_.push_back(new DeliveryReceiptRequestSerializer()); foreach(PayloadSerializer* serializer, serializers_) { addSerializer(serializer); } } FullPayloadSerializerCollection::~FullPayloadSerializerCollection() { foreach(PayloadSerializer* serializer, serializers_) { removeSerializer(serializer); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp new file mode 100644 index 0000000..9282db4 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h> + +using namespace Swift; + +class DeliveryReceiptSerializerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(DeliveryReceiptSerializerTest); + CPPUNIT_TEST(testSerialize_XEP0184Example3); + CPPUNIT_TEST(testSerialize_XEP0184Example4); + CPPUNIT_TEST_SUITE_END(); + + public: + void testSerialize_XEP0184Example3() { + std::string expected = "<request xmlns=\"urn:xmpp:receipts\"/>"; + + DeliveryReceiptRequest::ref receipt = boost::make_shared<DeliveryReceiptRequest>(); + + boost::shared_ptr<DeliveryReceiptRequestSerializer> serializer = boost::make_shared<DeliveryReceiptRequestSerializer>(); + CPPUNIT_ASSERT_EQUAL(expected, serializer->serializePayload(receipt)); + } + + void testSerialize_XEP0184Example4() { + std::string expected = "<received id=\"richard2-4.1.247\" xmlns=\"urn:xmpp:receipts\"/>"; + + DeliveryReceipt::ref receipt = boost::make_shared<DeliveryReceipt>("richard2-4.1.247"); + + boost::shared_ptr<DeliveryReceiptSerializer> serializer = boost::make_shared<DeliveryReceiptSerializer>(); + CPPUNIT_ASSERT_EQUAL(expected, serializer->serializePayload(receipt)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DeliveryReceiptSerializerTest); + |