summaryrefslogtreecommitdiffstats
path: root/Swift
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2011-11-11 15:01:32 (GMT)
committerKevin Smith <git@kismith.co.uk>2011-11-28 16:44:22 (GMT)
commit86aad702d1f2e831c8e27bbe4ca1402626e4c542 (patch)
tree3be5a8ed23aef3877c9b313d0ee0f58afb54f57a /Swift
parent81a7776d5ab523894a7c4745baee3988ad9f1ef9 (diff)
downloadswift-86aad702d1f2e831c8e27bbe4ca1402626e4c542.zip
swift-86aad702d1f2e831c8e27bbe4ca1402626e4c542.tar.bz2
Message Receipts (XEP-0184) support for 1-to-1 conversations (including 1-to-1 MUC).
Warn icon from already existing theme. Check icon from Wikipedia. See Swift/resources/icons/license_info.txt for details. License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php
Diffstat (limited to 'Swift')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp66
-rw-r--r--Swift/Controllers/Chat/ChatController.h13
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h2
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp57
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h11
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp107
-rw-r--r--Swift/Controllers/MainController.cpp13
-rw-r--r--Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h19
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h4
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h2
-rw-r--r--Swift/QtUI/MessageSnippet.cpp2
-rw-r--r--Swift/QtUI/QtChatView.cpp16
-rw-r--r--Swift/QtUI/QtChatView.h3
-rw-r--r--Swift/QtUI/QtChatWindow.cpp23
-rw-r--r--Swift/QtUI/QtChatWindow.h4
-rw-r--r--Swift/QtUI/QtLoginWindow.cpp4
-rw-r--r--Swift/QtUI/QtLoginWindow.h9
-rw-r--r--Swift/QtUI/QtMainWindow.cpp19
-rw-r--r--Swift/QtUI/QtMainWindow.h6
-rw-r--r--Swift/QtUI/QtUIFactory.cpp2
-rw-r--r--Swift/QtUI/Swift.qrc2
-rw-r--r--Swift/resources/icons/check.pngbin0 -> 490 bytes
-rw-r--r--Swift/resources/icons/license_info.txt5
-rw-r--r--Swift/resources/icons/warn.pngbin0 -> 527 bytes
24 files changed, 370 insertions, 19 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
@@ -16,7 +16,6 @@
#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>
@@ -26,15 +25,19 @@
#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);
@@ -70,7 +73,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
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*/) {
@@ -80,6 +83,7 @@ void ChatController::handleContactNickChanged(const JID& jid, const std::string&
}
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_;
@@ -93,9 +97,17 @@ void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) {
} 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) {
@@ -129,6 +141,21 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me
}
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) {
@@ -138,6 +165,30 @@ void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> m
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) {
@@ -148,10 +199,17 @@ void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<
} 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();
}
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
@@ -11,6 +11,8 @@
#include <map>
#include <string>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+
namespace Swift {
class AvatarManager;
class ChatStateNotifier;
@@ -18,14 +20,16 @@ namespace Swift {
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);
@@ -47,6 +51,9 @@ namespace Swift {
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_;
@@ -56,9 +63,13 @@ namespace Swift {
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
@@ -54,6 +54,7 @@ namespace Swift {
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);
@@ -71,6 +72,7 @@ namespace Swift {
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_;
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
@@ -21,6 +21,7 @@
#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>
@@ -28,12 +29,15 @@
#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 {
@@ -61,6 +65,7 @@ ChatsManager::ChatsManager(
MUCSearchWindowFactory* mucSearchWindowFactory,
ProfileSettingsProvider* settings,
FileTransferOverview* ftOverview,
+ XMPPRoster* roster,
bool eagleMode) :
jid_(jid),
joinMUCWindowFactory_(joinMUCWindowFactory),
@@ -69,6 +74,7 @@ ChatsManager::ChatsManager(
entityCapsProvider_(entityCapsProvider),
mucManager(mucManager),
ftOverview_(ftOverview),
+ roster_(roster),
eagleMode_(eagleMode) {
timerFactory_ = timerFactory;
eventController_ = eventController;
@@ -95,11 +101,19 @@ ChatsManager::ChatsManager(
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;
@@ -136,6 +150,39 @@ void ChatsManager::handleClearRecentsRequested() {
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;
@@ -316,6 +363,11 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
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) {
@@ -436,11 +488,12 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &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;
}
@@ -522,7 +575,7 @@ 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;
}
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
@@ -45,10 +45,11 @@ namespace Swift {
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);
@@ -81,6 +82,12 @@ namespace Swift {
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);
@@ -115,6 +122,8 @@ namespace Swift {
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
@@ -43,8 +43,11 @@
#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;
@@ -63,6 +66,10 @@ class ChatsManagerTest : public CppUnit::TestFixture {
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:
@@ -93,8 +100,9 @@ public:
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_);
@@ -335,11 +343,104 @@ public:
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_;
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
@@ -60,6 +60,7 @@
#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"
@@ -79,6 +80,7 @@ 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,
@@ -249,6 +251,11 @@ void MainController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
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() {
@@ -291,7 +298,7 @@ void MainController::handleConnected() {
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());
@@ -311,6 +318,7 @@ void MainController::handleConnected() {
discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature);
discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature);
#endif
+ discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature);
client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
client_->getDiscoManager()->setDiscoInfo(discoInfo);
@@ -335,6 +343,9 @@ void MainController::handleConnected() {
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) {
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
@@ -32,6 +32,7 @@ namespace Swift {
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};
@@ -57,6 +58,9 @@ namespace Swift {
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;
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
@@ -25,6 +25,8 @@ namespace Swift {
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() {};
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
@@ -32,7 +32,7 @@ MessageSnippet::MessageSnippet(const QString& message, const QString& sender, co
}
}
- 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));
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
@@ -196,6 +196,22 @@ void QtChatView::setAckXML(const QString& id, const QString& xml) {
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);
}
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
@@ -35,6 +35,9 @@ namespace Swift {
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);
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
@@ -475,13 +475,32 @@ void QtChatWindow::flash() {
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_;
}
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
@@ -68,6 +68,10 @@ namespace Swift {
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);
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
@@ -455,6 +455,10 @@ void QtLoginWindow::hide() {
window()->hide();
}
+QtLoginWindow::QtMenus QtLoginWindow::getMenus() const {
+ return QtMenus(swiftMenu_, generalMenu_);
+}
+
void QtLoginWindow::resizeEvent(QResizeEvent*) {
emit geometryChanged();
}
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
@@ -27,6 +27,13 @@ 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);
@@ -39,7 +46,7 @@ namespace Swift {
void selectUser(const std::string& user);
bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate);
void hide();
-
+ QtMenus getMenus() const;
virtual void quit();
signals:
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
@@ -25,6 +25,7 @@
#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>
@@ -33,12 +34,13 @@
#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;
@@ -123,6 +125,12 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
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>());
@@ -143,6 +151,10 @@ 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_;
}
@@ -192,6 +204,7 @@ void QtMainWindow::handleChatUserActionTriggered(bool /*checked*/) {
}
void QtMainWindow::handleSignOutAction() {
+ loginMenus_.generalMenu->removeAction(toggleRequestDeliveryReceipts_);
onSignOutRequest();
}
@@ -212,6 +225,10 @@ void QtMainWindow::handleUIEvent(boost::shared_ptr<UIEvent> 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) {
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
@@ -13,6 +13,7 @@
#include "Swift/QtUI/QtRosterHeader.h"
#include "Swift/QtUI/EventViewer/QtEventWindow.h"
#include "Swift/QtUI/ChatList/QtChatListWindow.h"
+#include "Swift/QtUI/QtLoginWindow.h"
#include <vector>
@@ -35,7 +36,7 @@ namespace Swift {
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);
@@ -62,9 +63,11 @@ namespace Swift {
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_;
@@ -72,6 +75,7 @@ namespace Swift {
QAction* editUserAction_;
QAction* chatUserAction_;
QAction* showOfflineAction_;
+ QAction* toggleRequestDeliveryReceipts_;
QMenu* serverAdHocMenu_;
QtTabWidget* tabs_;
QWidget* contactsTabWidget_;
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
@@ -55,7 +55,7 @@ FileTransferListWidget* QtUIFactory::createFileTransferListWidget() {
}
MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) {
- lastMainWindow = new QtMainWindow(settings, eventStream, uiPreferences);
+ lastMainWindow = new QtMainWindow(settings, eventStream, uiPreferences, loginWindow->getMenus());
return lastMainWindow;
}
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
@@ -11,6 +11,8 @@
<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>
diff --git a/Swift/resources/icons/check.png b/Swift/resources/icons/check.png
new file mode 100644
index 0000000..6e29fd8
--- /dev/null
+++ b/Swift/resources/icons/check.png
Binary files differ
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
new file mode 100644
index 0000000..357337e
--- /dev/null
+++ b/Swift/resources/icons/warn.png
Binary files differ