summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Swift/Controllers/Chat')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp62
-rw-r--r--Swift/Controllers/Chat/ChatController.h2
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp47
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h22
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp272
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h44
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp92
-rw-r--r--Swift/Controllers/Chat/MUCController.h25
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.cpp5
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.h8
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp18
-rw-r--r--Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp6
-rw-r--r--Swift/Controllers/Chat/UnitTest/MockChatListWindow.h25
-rw-r--r--Swift/Controllers/Chat/UserSearchController.cpp6
14 files changed, 504 insertions, 130 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 22ef68d..f4aa745 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -11,16 +11,17 @@
#include <Swift/Controllers/Intl.h>
#include <Swiften/Base/format.h>
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Chat/ChatStateNotifier.h"
-#include "Swiften/Chat/ChatStateTracker.h"
-#include "Swiften/Client/StanzaChannel.h"
-#include "Swiften/Disco/EntityCapsProvider.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 <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/StatusUtil.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
namespace Swift {
@@ -28,7 +29,7 @@ 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) {
+ : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider) {
isInMUC_ = isInMUC;
lastWasPresence_ = false;
chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -59,6 +60,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
chatWindow_->addSystemMessage(startMessage);
chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+ handleBareJIDCapsChanged(toJID_);
}
@@ -74,6 +76,19 @@ ChatController::~ChatController() {
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);
+ }
+ } else {
+ chatWindow_->setCorrectionEnabled(ChatWindow::Maybe);
+ }
+}
+
void ChatController::setToJID(const JID& jid) {
chatStateNotifier_->setContact(jid);
ChatControllerBase::setToJID(jid);
@@ -84,6 +99,7 @@ void ChatController::setToJID(const JID& jid) {
presence = jid.isBare() ? presenceOracle_->getHighestPriorityPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid);
}
chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available);
+ handleBareJIDCapsChanged(toJID_);
}
bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
@@ -102,8 +118,8 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me
setToJID(from);
}
}
- chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
chatStateTracker_->handleMessageReceived(message);
+ chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
}
void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
@@ -116,27 +132,33 @@ void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
}
void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
- std::string id = 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()) {
- chatWindow_->setAckState(id, ChatWindow::Pending);
- unackedStanzas_[sentStanza] = id;
+ 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_;
}
lastWasPresence_ = false;
chatStateNotifier_->userSentMessage();
}
void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
- std::string id = unackedStanzas_[stanza];
- if (id != "") {
- chatWindow_->setAckState(id, ChatWindow::Received);
+ 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);
}
- unackedStanzas_.erase(unackedStanzas_.find(stanza));
}
void ChatController::setOnline(bool online) {
if (!online) {
std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin();
- for ( ; it != unackedStanzas_.end(); it++) {
+ for ( ; it != unackedStanzas_.end(); ++it) {
chatWindow_->setAckState(it->second, ChatWindow::Failed);
}
unackedStanzas_.clear();
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index dd4bf90..f6b8763 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -35,11 +35,13 @@ namespace Swift {
void handleStanzaAcked(boost::shared_ptr<Stanza> stanza);
void dayTicked() {lastWasPresence_ = false;}
void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/);
+ void handleBareJIDCapsChanged(const JID& jid);
private:
NickResolver* nickResolver_;
ChatStateNotifier* chatStateNotifier_;
ChatStateTracker* chatStateTracker_;
+ std::string myLastMessageUIID_;
bool isInMUC_;
bool lastWasPresence_;
std::string lastStatusChangeString_;
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 281d968..802a7cb 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -7,6 +7,7 @@
#include "Swift/Controllers/Chat/ChatControllerBase.h"
#include <sstream>
+#include <map>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
@@ -20,6 +21,7 @@
#include "Swiften/Elements/Delay.h"
#include "Swiften/Base/foreach.h"
#include "Swift/Controllers/XMPPEvents/EventController.h"
+#include "Swiften/Disco/EntityCapsProvider.h"
#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
#include "Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h"
@@ -27,10 +29,11 @@
namespace Swift {
-ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory) {
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider) {
chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
- chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1));
+ chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
+ entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1));
setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
createDayChangeTimer();
}
@@ -39,6 +42,12 @@ ChatControllerBase::~ChatControllerBase() {
delete chatWindow_;
}
+void ChatControllerBase::handleCapsChanged(const JID& jid) {
+ if (jid.compare(toJID_, JID::WithoutResource) == 0) {
+ handleBareJIDCapsChanged(jid);
+ }
+}
+
void ChatControllerBase::createDayChangeTimer() {
if (timerFactory_) {
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
@@ -86,9 +95,14 @@ void ChatControllerBase::handleAllMessagesRead() {
}
unreadMessages_.clear();
chatWindow_->setUnreadMessageCount(0);
+ onUnreadCountChanged();
+}
+
+int ChatControllerBase::getUnreadCount() {
+ return unreadMessages_.size();
}
-void ChatControllerBase::handleSendMessageRequest(const std::string &body) {
+void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) {
if (!stanzaChannel_->isAvailable() || body.empty()) {
return;
}
@@ -104,8 +118,13 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body) {
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
message->addPayload(boost::shared_ptr<Delay>(new Delay(now, selfJID_)));
}
+ if (isCorrectionMessage) {
+ message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_)));
+ }
+ message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID());
stanzaChannel_->sendMessage(message);
postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message));
+ onActivity(message->getBody());
}
void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
@@ -152,7 +171,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
boost::shared_ptr<Message> message = messageEvent->getStanza();
std::string body = message->getBody();
if (message->isError()) {
- std::string errorMessage = getErrorMessage(message->getPayload<ErrorPayload>());
+ std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
chatWindow_->addErrorMessage(errorMessage);
}
else {
@@ -163,7 +182,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
JID from = message->getFrom();
std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
- if (!delayPayloads[i]->getFrom()) {
+ if (!delayPayloads[i]->getFrom()) {
continue;
}
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
@@ -179,11 +198,25 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
if (messageTimeStamp) {
timeStamp = *messageTimeStamp;
}
+ onActivity(body);
- addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
+ boost::shared_ptr<Replace> replace = message->getPayload<Replace>();
+ if (replace) {
+ std::string body = message->getBody();
+ // Should check if the user has a previous message
+ std::map<JID, std::string>::iterator lastMessage;
+ lastMessage = lastMessagesUIID_.find(from);
+ if (lastMessage != lastMessagesUIID_.end()) {
+ chatWindow_->replaceMessage(body, lastMessagesUIID_[from], timeStamp);
+ }
+ }
+ else {
+ lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
+ }
}
chatWindow_->show();
chatWindow_->setUnreadMessageCount(unreadMessages_.size());
+ onUnreadCountChanged();
postHandleIncomingMessage(messageEvent);
}
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 9573b1b..79d376c 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -4,8 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#ifndef SWIFTEN_ChatControllerBase_H
-#define SWIFTEN_ChatControllerBase_H
+#pragma once
#include <map>
#include <vector>
@@ -26,6 +25,7 @@
#include "Swiften/Elements/ErrorPayload.h"
#include "Swiften/Presence/PresenceOracle.h"
#include "Swiften/Queries/IQRouter.h"
+#include "Swiften/Base/IDGenerator.h"
namespace Swift {
class IQRouter;
@@ -35,6 +35,7 @@ namespace Swift {
class AvatarManager;
class UIEventStream;
class EventController;
+ class EntityCapsProvider;
class ChatControllerBase : public boost::bsignals::trackable {
public:
@@ -47,8 +48,14 @@ 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);
+ 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.
@@ -62,10 +69,13 @@ namespace Swift {
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;
private:
+ IDGenerator idGenerator_;
+ std::string lastSentMessageStanzaID_;
void createDayChangeTimer();
- void handleSendMessageRequest(const std::string &body);
+ void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
void handleAllMessagesRead();
void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error);
std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
@@ -80,13 +90,13 @@ namespace Swift {
ChatWindow* chatWindow_;
JID toJID_;
bool labelsEnabled_;
+ std::map<JID, std::string> lastMessagesUIID_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
bool useDelayForLatency_;
EventController* eventController_;
boost::shared_ptr<Timer> dateChangeTimer_;
TimerFactory* timerFactory_;
+ EntityCapsProvider* entityCapsProvider_;
};
}
-
-#endif
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 94d4b9a..56f7a4c 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -7,31 +7,38 @@
#include "Swift/Controllers/Chat/ChatsManager.h"
#include <boost/bind.hpp>
-
-#include "Swift/Controllers/Chat/ChatController.h"
-#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/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/MUC/MUCBookmarkManager.h"
+#include <boost/algorithm/string.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swift/Controllers/Chat/ChatController.h>
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
+#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/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/MUC/MUCBookmarkManager.h>
+#include <Swift/Controllers/ProfileSettingsProvider.h>
+#include <Swiften/Avatars/AvatarManager.h>
namespace Swift {
typedef std::pair<JID, ChatController*> JIDChatControllerPair;
typedef std::pair<JID, MUCController*> JIDMUCControllerPair;
+#define RECENT_CHATS "recent_chats"
+
ChatsManager::ChatsManager(
JID jid, StanzaChannel* stanzaChannel,
IQRouter* iqRouter,
@@ -49,7 +56,7 @@ ChatsManager::ChatsManager(
EntityCapsProvider* entityCapsProvider,
MUCManager* mucManager,
MUCSearchWindowFactory* mucSearchWindowFactory,
- SettingsProvider* settings) :
+ ProfileSettingsProvider* settings) :
jid_(jid),
joinMUCWindowFactory_(joinMUCWindowFactory),
useDelayForLatency_(useDelayForLatency),
@@ -68,13 +75,19 @@ ChatsManager::ChatsManager(
presenceSender_ = presenceSender;
uiEventStream_ = uiEventStream;
mucBookmarkManager_ = NULL;
+ profileSettings_ = settings;
presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));
uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1));
+
chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_);
+ chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1));
+ chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1));
+
joinMUCWindow_ = NULL;
mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, settings);
mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1));
setupBookmarks();
+ loadRecents();
}
ChatsManager::~ChatsManager() {
@@ -89,6 +102,62 @@ ChatsManager::~ChatsManager() {
delete mucSearchController_;
}
+void ChatsManager::saveRecents() {
+ std::string recents;
+ int i = 1;
+ foreach (ChatListWindow::Chat chat, recentChats_) {
+ std::vector<std::string> activity;
+ boost::split(activity, chat.activity, boost::is_any_of("\t\n"));
+ std::string recent = chat.jid.toString() + "\t" + activity[0] + "\t" + (chat.isMUC ? "true" : "false") + "\t" + chat.nick;
+ recents += recent + "\n";
+ if (i++ > 25) {
+ break;
+ }
+ }
+ profileSettings_->storeString(RECENT_CHATS, recents);
+}
+
+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;
+ }
+ std::vector<std::string> recent;
+ boost::split(recent, recentString, boost::is_any_of("\t"));
+ if (recent.size() < 4) {
+ continue;
+ }
+ JID jid(recent[0]);
+ if (!jid.isValid()) {
+ continue;
+ }
+ std::string activity(recent[1]);
+ bool isMUC = recent[2] == "true";
+ std::string nick(recent[3]);
+ StatusShow::Type type = StatusShow::None;
+ boost::filesystem::path path;
+ if (isMUC) {
+ if (mucControllers_.find(jid.toBare()) != mucControllers_.end()) {
+ type = StatusShow::Online;
+ }
+ } else {
+ if (avatarManager_) {
+ path = avatarManager_->getAvatarPath(jid);
+ }
+ Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid.toBare());
+ type = presence ? presence->getShow() : StatusShow::None;
+ }
+
+ ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick);
+ prependRecent(chat);
+ }
+ handleUnreadCountChanged(NULL);
+}
+
void ChatsManager::setupBookmarks() {
if (!mucBookmarkManager_) {
mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
@@ -98,7 +167,7 @@ void ChatsManager::setupBookmarks() {
if (chatListWindow_) {
chatListWindow_->setBookmarksEnabled(false);
- chatListWindow_->clear();
+ chatListWindow_->clearBookmarks();
}
}
}
@@ -122,10 +191,90 @@ void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) {
chatListWindow_->removeMUCBookmark(bookmark);
}
+ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity) {
+ int unreadCount = 0;
+ if (mucRegistry_->isMUC(jid)) {
+ MUCController* controller = mucControllers_[jid.toBare()];
+ StatusShow::Type type = StatusShow::None;
+ std::string nick = "";
+ if (controller) {
+ unreadCount = controller->getUnreadCount();
+ if (controller->isJoined()) {
+ type = StatusShow::Online;
+ }
+ nick = controller->getNick();
+ }
+ return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick);
+
+ } else {
+ ChatController* controller = getChatControllerIfExists(jid, false);
+ if (controller) {
+ unreadCount = controller->getUnreadCount();
+ }
+ JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare();
+ Presence::ref presence = presenceOracle_->getHighestPriorityPresence(bareishJID);
+ StatusShow::Type type = presence ? presence->getShow() : StatusShow::None;
+ boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path();
+ return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false);
+ }
+}
+
+void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) {
+ if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) {
+ /* Don't include PMs in MUC rooms.*/
+ return;
+ }
+ ChatListWindow::Chat chat = createChatListChatItem(jid, activity);
+ /* FIXME: handle nick changes */
+ appendRecent(chat);
+ handleUnreadCountChanged(NULL);
+ saveRecents();
+}
+
+void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) {
+ int unreadTotal = 0;
+ bool controllerIsMUC = dynamic_cast<MUCController*>(controller);
+ bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare());
+ foreach (ChatListWindow::Chat& chatItem, recentChats_) {
+ bool match = false;
+ if (controller) {
+ /* Matching MUC item */
+ match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
+ /* Matching PM */
+ match |= isPM && chatItem.jid == controller->getToJID();
+ /* Matching non-PM */
+ match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
+ }
+ if (match) {
+ chatItem.setUnreadCount(controller->getUnreadCount());
+ }
+ unreadTotal += chatItem.unreadCount;
+ }
+ chatListWindow_->setRecents(recentChats_);
+ chatListWindow_->setUnreadCount(unreadTotal);
+}
+
+void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) {
+ recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
+ recentChats_.push_front(chat);
+}
+
+void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) {
+ recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
+ recentChats_.push_back(chat);
+}
+
void ChatsManager::handleUserLeftMUC(MUCController* mucController) {
std::map<JID, MUCController*>::iterator it;
- for (it = mucControllers_.begin(); it != mucControllers_.end(); it++) {
+ for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) {
if ((*it).second == mucController) {
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (chat.isMUC && chat.jid == (*it).first) {
+ chat.statusType = StatusShow::None;
+ chatListWindow_->setRecents(recentChats_);
+ break;
+ }
+ }
mucControllers_.erase(it);
delete mucController;
return;
@@ -155,15 +304,15 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark());
}
else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
- handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), false);
+ handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically());
+ mucControllers_[joinEvent->getJID()]->activateChatWindow();
}
- else if (boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
+ else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
if (!joinMUCWindow_) {
- joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow();
- joinMUCWindow_->onJoinMUC.connect(boost::bind(&ChatsManager::handleJoinMUCRequest, this, _1, _2, _3));
+ joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_);
joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));
}
- joinMUCWindow_->setMUC("");
+ joinMUCWindow_->setMUC(joinEvent->getRoom());
joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));
joinMUCWindow_->show();
}
@@ -174,6 +323,16 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
*/
void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return;
+
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (newPresence->getFrom().toBare() == chat.jid.toBare() && !chat.isMUC) {
+ Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare());
+ chat.setStatusType(presence ? presence->getShow() : StatusShow::None);
+ chatListWindow_->setRecents(recentChats_);
+ break;
+ }
+ }
+
//if (newPresence->getType() != Presence::Unavailable) return;
JID fullJID(newPresence->getFrom());
std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID);
@@ -185,7 +344,25 @@ void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence)
}
void ChatsManager::setAvatarManager(AvatarManager* avatarManager) {
+ if (avatarManager_) {
+ avatarManager_->onAvatarChanged.disconnect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
+ }
avatarManager_ = avatarManager;
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (!chat.isMUC) {
+ chat.setAvatarPath(avatarManager_->getAvatarPath(chat.jid));
+ }
+ }
+ avatarManager_->onAvatarChanged.connect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
+}
+
+void ChatsManager::handleAvatarChanged(const JID& jid) {
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (!chat.isMUC && jid.toBare() == chat.jid.toBare()) {
+ chat.setAvatarPath(avatarManager_->getAvatarPath(jid));
+ break;
+ }
+ }
}
void ChatsManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) {
@@ -244,6 +421,8 @@ ChatController* ChatsManager::createNewChatController(const JID& contact) {
ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_);
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));
return controller;
}
@@ -252,7 +431,7 @@ ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) {
return controller ? controller : createNewChatController(contact);
}
-ChatController* ChatsManager::getChatControllerIfExists(const JID &contact) {
+ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) {
if (chatControllers_.find(contact) == chatControllers_.end()) {
if (mucRegistry_->isMUC(contact.toBare())) {
return NULL;
@@ -264,8 +443,13 @@ ChatController* ChatsManager::getChatControllerIfExists(const JID &contact) {
} else {
foreach (JIDChatControllerPair pair, chatControllers_) {
if (pair.first.toBare() == contact.toBare()) {
- rebindControllerJID(pair.first, contact);
- return chatControllers_[contact];
+ if (rebindIfNeeded) {
+ rebindControllerJID(pair.first, contact);
+ return chatControllers_[contact];
+ } else {
+ return pair.second;
+ }
+
}
}
return NULL;
@@ -280,8 +464,8 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& to) {
chatControllers_[to]->setToJID(to);
}
-void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& nickMaybe, bool autoJoin) {
- if (autoJoin) {
+void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& nickMaybe, bool addAutoJoin) {
+ if (addAutoJoin) {
MUCBookmark bookmark(mucJID, mucJID.getNode());
bookmark.setAutojoin(true);
if (nickMaybe) {
@@ -296,12 +480,17 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
} else {
std::string nick = nickMaybe ? nickMaybe.get() : jid_.getNode();
MUC::ref muc = mucManager->createMUC(mucJID);
- MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_);
+ MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_);
mucControllers_[mucJID] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
+ controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true));
+ controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true));
+ controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
+ handleChatActivity(mucJID.toBare(), "", true);
}
- mucControllers_[mucJID]->activateChatWindow();
+
+ mucControllers_[mucJID]->showChatWindow();
}
void ChatsManager::handleSearchMUCRequest() {
@@ -338,5 +527,18 @@ void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) {
}
}
+void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) {
+ uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getNick()));
+}
+
+void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) {
+ if (chat.isMUC) {
+ uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, chat.nick));
+ }
+ else {
+ uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(chat.jid));
+ }
+}
+
}
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 3740186..b4db523 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -11,17 +11,19 @@
#include <boost/shared_ptr.hpp>
#include <string>
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/Elements/Message.h"
-#include "Swiften/Elements/Presence.h"
-#include "Swiften/JID/JID.h"
-#include "Swiften/MUC/MUCRegistry.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swiften/MUC/MUCBookmark.h"
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/MUC/MUCRegistry.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
+#include <Swiften/MUC/MUCBookmark.h>
namespace Swift {
class EventController;
class ChatController;
+ class ChatControllerBase;
class MUCController;
class MUCManager;
class ChatWindowFactory;
@@ -34,26 +36,27 @@ namespace Swift {
class IQRouter;
class PresenceSender;
class MUCBookmarkManager;
- class ChatListWindow;
class ChatListWindowFactory;
class TimerFactory;
class EntityCapsProvider;
class DirectedPresenceSender;
class MUCSearchWindowFactory;
- class SettingsProvider;
+ class ProfileSettingsProvider;
class MUCSearchController;
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, SettingsProvider* settings);
+ 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);
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);
- void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& nick, bool autoJoin);
+ void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& nick, bool addAutoJoin);
void handleSearchMUCRequest();
void handleMUCSelectedAfterSearch(const JID&);
void rebindControllerJID(const JID& from, const JID& to);
@@ -63,11 +66,24 @@ namespace Swift {
void handleMUCBookmarkRemoved(const MUCBookmark& bookmark);
void handleUserLeftMUC(MUCController* mucController);
void handleBookmarksReady();
+ void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC);
+ void appendRecent(const ChatListWindow::Chat& chat);
+ void prependRecent(const ChatListWindow::Chat& chat);
void setupBookmarks();
+ void loadRecents();
+ void saveRecents();
+ void handleChatMadeRecent();
+ void handleMUCBookmarkActivated(const MUCBookmark&);
+ void handleRecentActivated(const ChatListWindow::Chat&);
+ void handleUnreadCountChanged(ChatControllerBase* controller);
+ void handleAvatarChanged(const JID& jid);
+
ChatController* getChatControllerOrFindAnother(const JID &contact);
ChatController* createNewChatController(const JID &contact);
ChatController* getChatControllerOrCreate(const JID &contact);
- ChatController* getChatControllerIfExists(const JID &contact);
+ ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true);
+
+ private:
std::map<JID, MUCController*> mucControllers_;
std::map<JID, ChatController*> chatControllers_;
EventController* eventController_;
@@ -92,5 +108,7 @@ namespace Swift {
EntityCapsProvider* entityCapsProvider_;
MUCManager* mucManager;
MUCSearchController* mucSearchController_;
+ std::list<ChatListWindow::Chat> recentChats_;
+ ProfileSettingsProvider* profileSettings_;
};
}
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 2914116..aa0a1e7 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "Swift/Controllers/Chat/MUCController.h"
+#include <Swift/Controllers/Chat/MUCController.h>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
@@ -12,23 +12,25 @@
#include <Swift/Controllers/Intl.h>
#include <Swiften/Base/format.h>
-#include "Swiften/Network/Timer.h"
-#include "Swiften/Network/TimerFactory.h"
-#include "Swiften/Base/foreach.h"
-#include "SwifTools/TabComplete.h"
-#include "Swiften/Base/foreach.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Elements/Delay.h"
-#include "Swiften/MUC/MUC.h"
-#include "Swiften/Client/StanzaChannel.h"
-#include "Swift/Controllers/Roster/Roster.h"
-#include "Swift/Controllers/Roster/SetAvatar.h"
-#include "Swift/Controllers/Roster/SetPresence.h"
+#include <Swiften/Network/Timer.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Base/foreach.h>
+#include <SwifTools/TabComplete.h>
+#include <Swiften/Base/foreach.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Elements/Delay.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/SetAvatar.h>
+#include <Swift/Controllers/Roster/SetPresence.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
#define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
@@ -50,8 +52,9 @@ MUCController::MUCController (
UIEventStream* uiEventStream,
bool useDelayForLatency,
TimerFactory* timerFactory,
- EventController* eventController) :
- ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory), muc_(muc), nick_(nick), desiredNick_(nick) {
+ EventController* eventController,
+ EntityCapsProvider* entityCapsProvider) :
+ ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider), muc_(muc), nick_(nick), desiredNick_(nick) {
parting_ = true;
joined_ = false;
lastWasPresence_ = false;
@@ -81,6 +84,7 @@ MUCController::MUCController (
if (avatarManager_ != NULL) {
avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
}
+ handleBareJIDCapsChanged(muc->getJID());
}
MUCController::~MUCController() {
@@ -93,6 +97,23 @@ MUCController::~MUCController() {
delete completer_;
}
+void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) {
+ ChatWindow::Tristate support = ChatWindow::Yes;
+ bool any = false;
+ foreach (const std::string& nick, currentOccupants_) {
+ DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick);
+ if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
+ any = true;
+ } else {
+ support = ChatWindow::Maybe;
+ }
+ }
+ if (!any) {
+ support = ChatWindow::No;
+ }
+ chatWindow_->setCorrectionEnabled(support);
+}
+
/**
* Join the MUC if not already in it.
*/
@@ -109,6 +130,14 @@ void MUCController::rejoin() {
}
}
+bool MUCController::isJoined() {
+ return joined_;
+}
+
+const std::string& MUCController::getNick() {
+ return nick_;
+}
+
void MUCController::handleJoinTimeoutTick() {
receivedActivity();
chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString()));
@@ -158,7 +187,7 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
default: break;
}
}
- errorMessage += ".";
+ errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage);
chatWindow_->addErrorMessage(errorMessage);
if (!rejoinNick.empty()) {
nick_ = rejoinNick;
@@ -176,6 +205,7 @@ void MUCController::handleJoinComplete(const std::string& nick) {
clearPresenceQueue();
shouldJoinOnReconnect_ = true;
setEnabled(true);
+ onUserJoined();
}
void MUCController::handleAvatarChanged(const JID& jid) {
@@ -206,7 +236,9 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
currentOccupants_.insert(occupant.getNick());
NickJoinPart event(occupant.getNick(), Join);
appendToJoinParts(joinParts_, event);
- roster_->addContact(jid, realJID, occupant.getNick(), roleToGroupName(occupant.getRole()), avatarManager_->getAvatarPath(jid).string());
+ std::string groupName(roleToGroupName(occupant.getRole()));
+ roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid).string());
+ roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole()));
if (joined_) {
std::string joinString;
MUCOccupant::Role role = occupant.getRole();
@@ -248,6 +280,16 @@ std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) {
return "";
}
+std::string MUCController::roleToSortName(MUCOccupant::Role role) {
+ switch (role) {
+ case MUCOccupant::Moderator: return "1";
+ case MUCOccupant::Participant: return "2";
+ case MUCOccupant::Visitor: return "3";
+ case MUCOccupant::NoRole: return "4";
+ }
+ return "5";
+}
+
JID MUCController::nickToJID(const std::string& nick) {
return JID(toJID_.getNode(), toJID_.getDomain(), nick);
}
@@ -309,7 +351,9 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC
if (occupant.getRealJID()) {
realJID = occupant.getRealJID().get();
}
- roster_->addContact(jid, realJID, nick, roleToGroupName(occupant.getRole()), avatarManager_->getAvatarPath(jid).string());
+ std::string group(roleToGroupName(occupant.getRole()));
+ roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid).string());
+ roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole())));
}
@@ -411,7 +455,7 @@ void MUCController::updateJoinParts() {
void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) {
std::vector<NickJoinPart>::iterator it = joinParts.begin();
bool matched = false;
- for (; it != joinParts.end(); it++) {
+ for (; it != joinParts.end(); ++it) {
if ((*it).nick == newEvent.nick) {
matched = true;
JoinPart type = (*it).type;
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index ebdd6cd..3a79920 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -7,18 +7,18 @@
#pragma once
#include <boost/shared_ptr.hpp>
-#include "Swiften/Base/boost_bsignals.h"
+#include <Swiften/Base/boost_bsignals.h>
#include <boost/signals/connection.hpp>
-#include <set>
+#include <set>
#include <string>
-#include "Swiften/Network/Timer.h"
-#include "Swift/Controllers/Chat/ChatControllerBase.h"
-#include "Swiften/Elements/Message.h"
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/JID/JID.h"
-#include "Swiften/MUC/MUC.h"
-#include "Swiften/Elements/MUCOccupant.h"
+#include <Swiften/Network/Timer.h>
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/Elements/MUCOccupant.h>
namespace Swift {
class StanzaChannel;
@@ -41,14 +41,17 @@ namespace Swift {
class MUCController : public ChatControllerBase {
public:
- MUCController(const JID& self, MUC::ref muc, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController);
+ MUCController(const JID& self, MUC::ref muc, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider);
~MUCController();
boost::signal<void ()> onUserLeft;
+ boost::signal<void ()> onUserJoined;
virtual void setOnline(bool online);
void rejoin();
static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent);
static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts);
static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts);
+ bool isJoined();
+ const std::string& getNick();
protected:
void preSendMessageRequest(boost::shared_ptr<Message> message);
@@ -71,6 +74,7 @@ namespace Swift {
void handleJoinFailed(boost::shared_ptr<ErrorPayload> error);
void handleJoinTimeoutTick();
std::string roleToGroupName(MUCOccupant::Role role);
+ std::string roleToSortName(MUCOccupant::Role role);
JID nickToJID(const std::string& nick);
std::string roleToFriendlyName(MUCOccupant::Role role);
void receivedActivity();
@@ -79,6 +83,7 @@ namespace Swift {
bool shouldUpdateJoinParts();
void dayTicked() {lastWasPresence_ = false;}
void processUserPart();
+ void handleBareJIDCapsChanged(const JID& jid);
private:
MUC::ref muc_;
diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp
index 743aabb..2cb89b4 100644
--- a/Swift/Controllers/Chat/MUCSearchController.cpp
+++ b/Swift/Controllers/Chat/MUCSearchController.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -11,6 +11,7 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
+#include <Swiften/Base/foreach.h>
#include <Swiften/Disco/GetDiscoItemsRequest.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/String.h>
@@ -23,7 +24,7 @@ namespace Swift {
static const std::string SEARCHED_SERVICES = "searchedServices";
-MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, SettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) {
+MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, ProfileSettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) {
itemsInProgress_ = 0;
loadSavedServices();
}
diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h
index c8040ed..f90e4a7 100644
--- a/Swift/Controllers/Chat/MUCSearchController.h
+++ b/Swift/Controllers/Chat/MUCSearchController.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -16,7 +16,7 @@
#include "Swiften/JID/JID.h"
#include "Swift/Controllers/UIEvents/UIEvent.h"
-#include "Swift/Controllers/Settings/SettingsProvider.h"
+#include "Swift/Controllers/ProfileSettingsProvider.h"
#include "Swiften/Elements/DiscoInfo.h"
#include "Swiften/Elements/DiscoItems.h"
#include "Swiften/Elements/ErrorPayload.h"
@@ -87,7 +87,7 @@ namespace Swift {
class MUCSearchController {
public:
- MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, SettingsProvider* settings);
+ MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, ProfileSettingsProvider* settings);
~MUCSearchController();
void openSearchWindow();
@@ -112,7 +112,7 @@ namespace Swift {
JID jid_;
MUCSearchWindowFactory* factory_;
IQRouter* iqRouter_;
- SettingsProvider* settings_;
+ ProfileSettingsProvider* settings_;
MUCSearchWindow* window_;
DiscoServiceWalker* walker_;
std::list<JID> services_;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 40f7445..b8cb368 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -10,6 +10,7 @@
#include "Swift/Controllers/Chat/ChatsManager.h"
+#include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h"
#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
#include "Swift/Controllers/Settings/DummySettingsProvider.h"
#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
@@ -38,6 +39,7 @@
#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include <Swift/Controllers/ProfileSettingsProvider.h>
using namespace Swift;
@@ -59,7 +61,7 @@ class ChatsManagerTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE_END();
public:
- void setUp() {
+ void setUp() {
mocks_ = new MockRepository();
jid_ = JID("test@test.com/resource");
stanzaChannel_ = new DummyStanzaChannel();
@@ -82,8 +84,10 @@ public:
chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>();
settings_ = new DummySettingsProvider();
- mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(NULL);
- manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, settings_);
+ profileSettings_ = new ProfileSettingsProvider("a", settings_);
+ chatListWindow_ = new MockChatListWindow();
+ 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_);
avatarManager_ = new NullAvatarManager();
manager_->setAvatarManager(avatarManager_);
@@ -92,7 +96,7 @@ public:
void tearDown() {
//delete chatListWindowFactory_;
delete settings_;
- delete mocks_;
+ delete profileSettings_;
delete avatarManager_;
delete manager_;
delete directedPresenceSender_;
@@ -109,6 +113,8 @@ public:
delete xmppRoster_;
delete entityCapsManager_;
delete capsProvider_;
+ delete chatListWindow_;
+ delete mocks_;
}
void testFirstOpenWindowIncoming() {
@@ -346,6 +352,8 @@ private:
CapsProvider* capsProvider_;
MUCManager* mucManager_;
DummySettingsProvider* settings_;
+ ProfileSettingsProvider* profileSettings_;
+ ChatListWindow* chatListWindow_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 5f5e44d..ad5ceac 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -25,6 +25,7 @@
#include "Swiften/Presence/PresenceOracle.h"
#include "Swiften/Network/TimerFactory.h"
#include "Swiften/Elements/MUCUserPayload.h"
+#include "Swiften/Disco/DummyEntityCapsProvider.h"
using namespace Swift;
@@ -56,12 +57,14 @@ public:
TimerFactory* timerFactory = NULL;
window_ = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
mucRegistry_ = new MUCRegistry();
+ entityCapsProvider_ = new DummyEntityCapsProvider();
muc_ = MUC::ref(new MUC(stanzaChannel_, iqRouter_, directedPresenceSender_, JID("teaparty@rooms.wonderland.lit"), mucRegistry_));
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
- controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_);
+ controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_);
};
void tearDown() {
+ delete entityCapsProvider_;
delete controller_;
delete eventController_;
delete presenceOracle_;
@@ -240,6 +243,7 @@ private:
UIEventStream* uiEventStream_;
MockChatWindow* window_;
MUCRegistry* mucRegistry_;
+ DummyEntityCapsProvider* entityCapsProvider_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
new file mode 100644
index 0000000..6ac8d4a
--- /dev/null
+++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/Controllers/UIInterfaces/ChatListWindow.h"
+
+namespace Swift {
+
+ class MockChatListWindow : public ChatListWindow {
+ public:
+ MockChatListWindow() {};
+ virtual ~MockChatListWindow() {};
+ void addMUCBookmark(const MUCBookmark& /*bookmark*/) {}
+ void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {}
+ void setBookmarksEnabled(bool /*enabled*/) {}
+ void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {}
+ void setUnreadCount(int /*unread*/) {}
+ void clearBookmarks() {}
+ };
+
+}
diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp
index b3403d7..5a76c5d 100644
--- a/Swift/Controllers/Chat/UserSearchController.cpp
+++ b/Swift/Controllers/Chat/UserSearchController.cpp
@@ -9,9 +9,9 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
+#include <Swiften/Base/foreach.h>
#include <Swiften/Disco/GetDiscoInfoRequest.h>
#include <Swiften/Disco/GetDiscoItemsRequest.h>
-
#include <Swift/Controllers/DiscoServiceWalker.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
@@ -85,12 +85,12 @@ void UserSearchController::endDiscoWalker() {
void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) {
- bool isUserDirectory = false;
+ //bool isUserDirectory = false;
bool supports55 = false;
foreach (DiscoInfo::Identity identity, info->getIdentities()) {
if ((identity.getCategory() == "directory"
&& identity.getType() == "user")) {
- isUserDirectory = true;
+ //isUserDirectory = true;
}
}
std::vector<std::string> features = info->getFeatures();