summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Swift/Controllers/Chat')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp5
-rw-r--r--Swift/Controllers/Chat/ChatController.h4
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp4
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h2
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp56
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h4
6 files changed, 59 insertions, 16 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 9df7708..9b65c9f 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -1,514 +1,519 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/Controllers/Chat/ChatController.h>
#include <boost/bind.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <stdio.h>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/Algorithm.h>
#include <Swiften/Base/DateTime.h>
#include <Swiften/Base/foreach.h>
#include <Swiften/Base/format.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Chat/ChatStateNotifier.h>
#include <Swiften/Chat/ChatStateTracker.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swiften/Elements/Idle.h>
#include <Swiften/FileTransfer/FileTransferManager.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/FileTransfer/FileTransferController.h>
#include <Swift/Controllers/StatusUtil.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h>
#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>
#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/Highlighter.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
namespace Swift {
/**
* The controller does not gain ownership of the stanzaChannel, nor the factory.
*/
ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider)
: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
isInMUC_ = isInMUC;
lastWasPresence_ = false;
chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
chatStateTracker_ = new ChatStateTracker();
nickResolver_ = nickResolver;
presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1));
chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1));
stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1));
nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
std::string nick = nickResolver_->jidToNick(toJID_);
chatWindow_->setName(nick);
std::string startMessage;
Presence::ref theirPresence;
if (isInMUC) {
startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString());
theirPresence = presenceOracle->getLastPresence(contact);
} else {
startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString());
theirPresence = contact.isBare() ? presenceOracle->getHighestPriorityPresence(contact.toBare()) : presenceOracle->getLastPresence(contact);
}
Idle::ref idle;
if (theirPresence && (idle = theirPresence->getPayload<Idle>())) {
startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % dateTimeToLocalString(idle->getSince()));
}
startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None);
if (theirPresence && !theirPresence->getStatus().empty()) {
startMessage += " (" + theirPresence->getStatus() + ")";
}
lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None;
chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available);
startMessage += ".";
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection);
chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2));
chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2));
chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1));
chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1));
chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this));
chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this));
chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this));
chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this));
chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this));
chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1));
+ chatWindow_->onClosed.connect(boost::bind(&ChatController::handleWindowClosed, this));
handleBareJIDCapsChanged(toJID_);
settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1));
eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1));
}
void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) {
if (jid.toBare() == toJID_.toBare()) {
chatWindow_->setName(nickResolver_->jidToNick(jid));
}
}
ChatController::~ChatController() {
eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1));
settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1));
nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
delete chatStateNotifier_;
delete chatStateTracker_;
}
JID ChatController::getBaseJID() {
return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID();
}
void ChatController::cancelReplaces() {
lastWasPresence_ = false;
}
void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) {
DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_);
if (disco) {
if (disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
chatWindow_->setCorrectionEnabled(ChatWindow::Yes);
} else {
chatWindow_->setCorrectionEnabled(ChatWindow::No);
}
if (disco->hasFeature(DiscoInfo::MessageDeliveryReceiptsFeature)) {
contactSupportsReceipts_ = ChatWindow::Yes;
} else {
contactSupportsReceipts_ = ChatWindow::No;
}
if (FileTransferManager::isSupportedBy(disco)) {
chatWindow_->setFileTransferEnabled(ChatWindow::Yes);
} else {
chatWindow_->setFileTransferEnabled(ChatWindow::No);
}
} else {
SWIFT_LOG(debug) << "No disco info :(" << std::endl;
chatWindow_->setCorrectionEnabled(ChatWindow::Maybe);
contactSupportsReceipts_ = ChatWindow::Maybe;
}
checkForDisplayingDisplayReceiptsAlert();
}
void ChatController::setToJID(const JID& jid) {
chatStateNotifier_->setContact(jid);
ChatControllerBase::setToJID(jid);
Presence::ref presence;
if (isInMUC_) {
presence = presenceOracle_->getLastPresence(jid);
} else {
presence = jid.isBare() ? presenceOracle_->getHighestPriorityPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid);
}
chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available);
handleBareJIDCapsChanged(toJID_);
}
void ChatController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
ChatControllerBase::setAvailableServerFeatures(info);
if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this));
blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingItemAdded, this, _1));
blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingItemRemoved, this, _1));
handleBlockingStateChanged();
}
}
bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
return false;
}
void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
if (messageEvent->isReadable()) {
chatWindow_->flash();
lastWasPresence_ = false;
}
boost::shared_ptr<Message> message = messageEvent->getStanza();
JID from = message->getFrom();
if (!from.equals(toJID_, JID::WithResource)) {
if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){
setToJID(from);
}
}
chatStateTracker_->handleMessageReceived(message);
chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>());
// handle XEP-0184 Message Receipts
// incomming receipts
if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) {
SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl;
if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) {
chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived);
requestedReceipts_.erase(receipt->getReceivedID());
}
// incomming errors in response to send out receipts
} else if (message->getPayload<DeliveryReceiptRequest>() && (message->getType() == Message::Error)) {
if (requestedReceipts_.find(message->getID()) != requestedReceipts_.end()) {
chatWindow_->setMessageReceiptState(requestedReceipts_[message->getID()], ChatWindow::ReceiptFailed);
requestedReceipts_.erase(message->getID());
}
// incoming receipt requests
} else if (message->getPayload<DeliveryReceiptRequest>()) {
if (receivingPresenceFromUs_) {
boost::shared_ptr<Message> receiptMessage = boost::make_shared<Message>();
receiptMessage->setTo(toJID_);
receiptMessage->addPayload(boost::make_shared<DeliveryReceipt>(message->getID()));
stanzaChannel_->sendMessage(receiptMessage);
}
}
}
void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) {
eventController_->handleIncomingEvent(messageEvent);
if (!messageEvent->getConcluded()) {
highlighter_->handleHighlightAction(highlight);
}
}
void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
chatStateNotifier_->addChatStateRequest(message);
if (userWantsReceipts_ && (contactSupportsReceipts_ != ChatWindow::No) && message) {
message->addPayload(boost::make_shared<DeliveryReceiptRequest>());
}
}
void ChatController::setContactIsReceivingPresence(bool isReceivingPresence) {
receivingPresenceFromUs_ = isReceivingPresence;
}
void ChatController::handleSettingChanged(const std::string& settingPath) {
if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) {
userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
checkForDisplayingDisplayReceiptsAlert();
}
}
void ChatController::checkForDisplayingDisplayReceiptsAlert() {
if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) {
chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts."));
} else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) {
chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent."));
} else {
chatWindow_->cancelAlert();
}
}
void ChatController::handleBlockingStateChanged() {
boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
if (blockList->getState() == BlockList::Available) {
if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) {
chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first."));
chatWindow_->setInputEnabled(false);
chatWindow_->setBlockingState(ChatWindow::IsBlocked);
} else {
chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
}
}
}
void ChatController::handleBlockingItemAdded(const JID& jid) {
if (toJID_ == (isInMUC_ ? jid: jid.toBare())) {
chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first."));
chatWindow_->setInputEnabled(false);
chatWindow_->setBlockingState(ChatWindow::IsBlocked);
}
}
void ChatController::handleBlockingItemRemoved(const JID& jid) {
if (toJID_ == (isInMUC_ ? jid: jid.toBare())) {
// FIXME: Support for different types of alerts.
chatWindow_->cancelAlert();
chatWindow_->setInputEnabled(true);
chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
}
}
void ChatController::handleBlockUserRequest() {
if (isInMUC_) {
eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_));
} else {
eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare()));
}
}
void ChatController::handleUnblockUserRequest() {
if (isInMUC_) {
eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_));
} else {
eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare()));
}
}
void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) {
boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs, RequestInviteToMUCUIEvent::Impromptu));
eventStream_->send(event);
}
+void ChatController::handleWindowClosed() {
+ onWindowClosed();
+}
+
void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event);
if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) {
onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason());
}
}
void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
if (replace) {
eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_));
replaceMessage(body, myLastMessageUIID_, true, boost::posix_time::microsec_clock::universal_time(), HighlightAction());
} else {
myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction());
}
if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) {
chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
unackedStanzas_[sentStanza] = myLastMessageUIID_;
}
if (sentStanza->getPayload<DeliveryReceiptRequest>()) {
requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_;
chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested);
}
lastWasPresence_ = false;
chatStateNotifier_->userSentMessage();
}
void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza);
if (unackedStanza != unackedStanzas_.end()) {
chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received);
unackedStanzas_.erase(unackedStanza);
}
}
void ChatController::setOnline(bool online) {
if (!online) {
std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin();
for ( ; it != unackedStanzas_.end(); ++it) {
chatWindow_->setAckState(it->second, ChatWindow::Failed);
}
unackedStanzas_.clear();
Presence::ref fakeOffline(new Presence());
fakeOffline->setFrom(toJID_);
fakeOffline->setType(Presence::Unavailable);
chatStateTracker_->handlePresenceChange(fakeOffline);
}
ChatControllerBase::setOnline(online);
}
void ChatController::handleNewFileTransferController(FileTransferController* ftc) {
std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty());
std::string ftID = ftc->setChatWindow(chatWindow_, nick);
ftControllers[ftID] = ftc;
}
void ChatController::handleWhiteboardSessionRequest(bool senderIsSelf) {
lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf);
}
void ChatController::handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state) {
chatWindow_->setWhiteboardSessionStatus(lastWbID_, state);
}
void ChatController::handleFileTransferCancel(std::string id) {
SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl;
if (ftControllers.find(id) != ftControllers.end()) {
ftControllers[id]->cancel();
} else {
std::cerr << "unknown file transfer UI id" << std::endl;
}
}
void ChatController::handleFileTransferStart(std::string id, std::string description) {
SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl;
if (ftControllers.find(id) != ftControllers.end()) {
ftControllers[id]->start(description);
} else {
std::cerr << "unknown file transfer UI id" << std::endl;
}
}
void ChatController::handleFileTransferAccept(std::string id, std::string filename) {
SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl;
if (ftControllers.find(id) != ftControllers.end()) {
ftControllers[id]->accept(filename);
} else {
std::cerr << "unknown file transfer UI id" << std::endl;
}
}
void ChatController::handleSendFileRequest(std::string filename) {
SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl;
eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename));
}
void ChatController::handleWhiteboardSessionAccept() {
eventStream_->send(boost::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_));
}
void ChatController::handleWhiteboardSessionCancel() {
eventStream_->send(boost::make_shared<CancelWhiteboardSessionUIEvent>(toJID_));
}
void ChatController::handleWhiteboardWindowShow() {
eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(toJID_));
}
std::string ChatController::senderDisplayNameFromMessage(const JID& from) {
return nickResolver_->jidToNick(from);
}
std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> presence) {
std::string nick = senderDisplayNameFromMessage(presence->getFrom());
std::string response;
if (!presence || presence->getType() == Presence::Unavailable || presence->getType() == Presence::Error) {
response = QT_TRANSLATE_NOOP("", "%1% has gone offline");
} else if (presence->getType() == Presence::Available) {
StatusShow::Type show = presence->getShow();
if (show == StatusShow::Online || show == StatusShow::FFC) {
response = QT_TRANSLATE_NOOP("", "%1% has become available");
} else if (show == StatusShow::Away || show == StatusShow::XA) {
response = QT_TRANSLATE_NOOP("", "%1% has gone away");
} else if (show == StatusShow::DND) {
response = QT_TRANSLATE_NOOP("", "%1% is now busy");
}
}
Idle::ref idle;
if ((idle = presence->getPayload<Idle>())) {
response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % dateTimeToLocalString(idle->getSince()));
}
if (!response.empty()) {
response = str(format(response) % nick);
}
if (!presence->getStatus().empty()) {
response += " (" + presence->getStatus() + ")";
}
return response + ".";
}
void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
bool me = false;
if (toJID_.isBare()) {
newPresence = presenceOracle_->getHighestPriorityPresence(toJID_);
if ((newPresence ? newPresence->getShow() : StatusShow::None) != lastShownStatus_) {
me = true;
}
} else if (toJID_.equals(newPresence->getFrom(), JID::WithResource)) {
me = true;
}
if (!me) {
return;
}
if (!newPresence) {
newPresence = boost::make_shared<Presence>();
newPresence->setType(Presence::Unavailable);
}
lastShownStatus_ = newPresence->getShow();
chatStateTracker_->handlePresenceChange(newPresence);
chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available);
std::string newStatusChangeString = getStatusChangeString(newPresence);
if (newStatusChangeString != lastStatusChangeString_) {
if (lastWasPresence_) {
chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp);
} else {
chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection);
}
lastStatusChangeString_ = newStatusChangeString;
lastWasPresence_ = true;
}
}
boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(boost::shared_ptr<Message> message) const {
return message->getTimestamp();
}
void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) {
HistoryMessage::Type type;
if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) {
type = HistoryMessage::PrivateMessage;
}
else {
type = HistoryMessage::Chat;
}
if (historyController_) {
historyController_->addMessage(message, fromJID, toJID, type, timeStamp);
}
}
ChatWindow* ChatController::detachChatWindow() {
chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
return ChatControllerBase::detachChatWindow();
}
}
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 8b1bb9a..2e92be7 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -1,113 +1,113 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <map>
#include <string>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
class AvatarManager;
class ChatStateNotifier;
class ChatStateTracker;
class NickResolver;
class EntityCapsProvider;
class FileTransferController;
class SettingsProvider;
class HistoryController;
class HighlightManager;
class ClientBlockListManager;
class UIEvent;
class ChatController : public ChatControllerBase {
public:
ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
virtual ~ChatController();
virtual void setToJID(const JID& jid);
virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
virtual void setOnline(bool online);
virtual void handleNewFileTransferController(FileTransferController* ftc);
virtual void handleWhiteboardSessionRequest(bool senderIsSelf);
virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state);
virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/);
virtual ChatWindow* detachChatWindow();
protected:
void cancelReplaces();
JID getBaseJID();
void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming);
private:
void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
std::string getStatusChangeString(boost::shared_ptr<Presence> presence);
bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
void postSendMessage(const std::string &body, boost::shared_ptr<Stanza> sentStanza);
void preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent);
void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction&);
void preSendMessageRequest(boost::shared_ptr<Message>);
std::string senderDisplayNameFromMessage(const JID& from);
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const;
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);
void handleFileTransferCancel(std::string /* id */);
void handleFileTransferStart(std::string /* id */, std::string /* description */);
void handleFileTransferAccept(std::string /* id */, std::string /* filename */);
void handleSendFileRequest(std::string filename);
void handleWhiteboardSessionAccept();
void handleWhiteboardSessionCancel();
void handleWhiteboardWindowShow();
void handleSettingChanged(const std::string& settingPath);
void checkForDisplayingDisplayReceiptsAlert();
void handleBlockingStateChanged();
void handleBlockingItemAdded(const JID&);
void handleBlockingItemRemoved(const JID&);
void handleBlockUserRequest();
void handleUnblockUserRequest();
void handleInviteToChat(const std::vector<JID>& droppedJIDs);
- void handleInviteToMUCWindowDismissed();
- void handleInviteToMUCWindowCompleted();
+
+ void handleWindowClosed();
void handleUIEvent(boost::shared_ptr<UIEvent> event);
private:
NickResolver* nickResolver_;
ChatStateNotifier* chatStateNotifier_;
ChatStateTracker* chatStateTracker_;
std::string myLastMessageUIID_;
bool isInMUC_;
bool lastWasPresence_;
std::string lastStatusChangeString_;
std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_;
std::map<std::string, std::string> requestedReceipts_;
StatusShow::Type lastShownStatus_;
UIEventStream* eventStream_;
ChatWindow::Tristate contactSupportsReceipts_;
bool receivingPresenceFromUs_;
bool userWantsReceipts_;
std::map<std::string, FileTransferController*> ftControllers;
SettingsProvider* settings_;
std::string lastWbID_;
ClientBlockListManager* clientBlockListManager_;
boost::bsignals::scoped_connection blockingOnStateChangedConnection_;
boost::bsignals::scoped_connection blockingOnItemAddedConnection_;
boost::bsignals::scoped_connection blockingOnItemRemovedConnection_;
};
}
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 5363e0c..24341e6 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,369 +1,373 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <sstream>
#include <map>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/algorithm/string.hpp>
#include <Swiften/Base/format.h>
#include <Swiften/Base/Path.h>
#include <Swiften/Base/String.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Elements/Delay.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Base/foreach.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
#include <Swift/Controllers/HighlightManager.h>
#include <Swift/Controllers/Highlighter.h>
#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
namespace Swift {
ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) {
chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this));
entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1));
highlighter_ = highlightManager->createHighlighter();
setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
createDayChangeTimer();
}
ChatControllerBase::~ChatControllerBase() {
delete chatWindow_;
}
void ChatControllerBase::handleLogCleared() {
cancelReplaces();
}
ChatWindow* ChatControllerBase::detachChatWindow() {
ChatWindow* chatWindow = chatWindow_;
chatWindow_ = NULL;
return chatWindow;
}
void ChatControllerBase::handleCapsChanged(const JID& jid) {
if (jid.compare(toJID_, JID::WithoutResource) == 0) {
handleBareJIDCapsChanged(jid);
}
}
void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) {
if (chatWindow_) {
chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu);
}
}
void ChatControllerBase::createDayChangeTimer() {
if (timerFactory_) {
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1));
int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds());
dateChangeTimer_ = boost::shared_ptr<Timer>(timerFactory_->createTimer(millisecondsUntilMidnight));
dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this));
dateChangeTimer_->start();
}
}
void ChatControllerBase::handleDayChangeTick() {
dateChangeTimer_->stop();
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection);
dayTicked();
createDayChangeTimer();
}
void ChatControllerBase::setEnabled(bool enabled) {
chatWindow_->setInputEnabled(enabled);
}
void ChatControllerBase::setOnline(bool online) {
setEnabled(online);
}
JID ChatControllerBase::getBaseJID() {
return JID(toJID_.toBare());
}
void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) {
GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_);
request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2));
request->send();
} else {
chatWindow_->setSecurityLabelsEnabled(false);
labelsEnabled_ = false;
}
}
void ChatControllerBase::handleAllMessagesRead() {
if (!unreadMessages_.empty()) {
targetedUnreadMessages_.clear();
foreach (boost::shared_ptr<StanzaEvent> stanzaEvent, unreadMessages_) {
stanzaEvent->conclude();
}
unreadMessages_.clear();
chatWindow_->setUnreadMessageCount(0);
onUnreadCountChanged();
}
}
int ChatControllerBase::getUnreadCount() {
return boost::numeric_cast<int>(targetedUnreadMessages_.size());
}
void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) {
if (!stanzaChannel_->isAvailable() || body.empty()) {
return;
}
boost::shared_ptr<Message> message(new Message());
message->setTo(toJID_);
message->setType(Swift::Message::Chat);
message->setBody(body);
if (labelsEnabled_) {
if (!isCorrectionMessage) {
lastLabel_ = chatWindow_->getSelectedSecurityLabel();
}
SecurityLabelsCatalog::Item labelItem = lastLabel_;
if (labelItem.getLabel()) {
message->addPayload(labelItem.getLabel());
}
}
preSendMessageRequest(message);
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
if (useDelayForLatency_) {
message->addPayload(boost::make_shared<Delay>(now, selfJID_));
}
if (isCorrectionMessage) {
message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_)));
}
message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID());
stanzaChannel_->sendMessage(message);
postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message));
onActivity(message->getBody());
#ifdef SWIFT_EXPERIMENTAL_HISTORY
logMessage(body, selfJID_, toJID_, now, false);
#endif
}
void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
if (catalog && !error) {
if (catalog->getItems().size() == 0) {
chatWindow_->setSecurityLabelsEnabled(false);
labelsEnabled_ = false;
} else {
labelsEnabled_ = true;
chatWindow_->setAvailableSecurityLabels(catalog->getItems());
chatWindow_->setSecurityLabelsEnabled(true);
}
} else {
labelsEnabled_ = false;
chatWindow_->setSecurityLabelsError();
}
}
void ChatControllerBase::showChatWindow() {
chatWindow_->show();
}
void ChatControllerBase::activateChatWindow() {
chatWindow_->activate();
}
+bool ChatControllerBase::hasOpenWindow() const {
+ return chatWindow_ && chatWindow_->isVisible();
+}
+
std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
if (boost::starts_with(message, "/me ")) {
return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
} else {
return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
}
}
void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
if (boost::starts_with(message, "/me ")) {
chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);
} else {
chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), id, time, highlight);
}
}
bool ChatControllerBase::isFromContact(const JID& from) {
return from.toBare() == toJID_.toBare();
}
void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
preHandleIncomingMessage(messageEvent);
if (messageEvent->isReadable() && !messageEvent->getConcluded()) {
unreadMessages_.push_back(messageEvent);
if (messageEvent->targetsMe()) {
targetedUnreadMessages_.push_back(messageEvent);
}
}
boost::shared_ptr<Message> message = messageEvent->getStanza();
std::string body = message->getBody();
HighlightAction highlight;
if (message->isError()) {
if (!message->getTo().getResource().empty()) {
std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
}
}
else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) {
handleMUCInvitation(messageEvent->getStanza());
return;
}
else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) {
handleMediatedMUCInvitation(messageEvent->getStanza());
return;
}
else {
if (!messageEvent->isReadable()) {
return;
}
showChatWindow();
JID from = message->getFrom();
std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
if (!delayPayloads[i]->getFrom()) {
continue;
}
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
std::ostringstream s;
s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << ".";
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
}
boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
// Determine the timestamp
boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time();
boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message);
if (messageTimeStamp) {
timeStamp = *messageTimeStamp;
}
onActivity(body);
// Highlight
if (!isIncomingMessageFromMe(message)) {
highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from));
}
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()) {
replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), timeStamp, highlight);
}
}
else {
lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, avatarManager_->getAvatarPath(from), timeStamp, highlight);
}
logMessage(body, from, selfJID_, timeStamp, true);
}
chatWindow_->show();
chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
onUnreadCountChanged();
postHandleIncomingMessage(messageEvent, highlight);
}
std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) {
std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message");
if (!error->getText().empty()) {
return error->getText();
}
else {
switch (error->getCondition()) {
case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request");
case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict");
case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented");
case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden");
case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted");
case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error");
case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found");
case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed");
case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected");
case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed");
case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized");
case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required");
case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable");
case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect");
case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required");
case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found");
case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout");
case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources");
case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable");
case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required");
case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition");
case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request");
}
}
assert(false);
return defaultMessage;
}
void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) {
unreadMessages_.push_back(event);
chatWindow_->show();
chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
onUnreadCountChanged();
chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu());
eventController_->handleIncomingEvent(event);
}
void ChatControllerBase::handleMUCInvitation(Message::ref message) {
MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true));
} else {
MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu());
handleGeneralMUCInvitation(inviteEvent);
}
}
void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {
MUCUserPayload::Invite invite = *message->getPayload<MUCUserPayload>()->getInvite();
JID from = message->getFrom();
std::string reason;
if (!invite.reason.empty()) {
reason = invite.reason;
}
std::string password;
if (message->getPayload<MUCUserPayload>()->getPassword()) {
password = *message->getPayload<MUCUserPayload>()->getPassword();
}
MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false);
handleGeneralMUCInvitation(inviteEvent);
}
}
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index cf0a4d2..a0b848b 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -1,134 +1,136 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <map>
#include <vector>
#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/optional.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <Swiften/Network/Timer.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Elements/Stanza.h>
#include <Swiften/Base/boost_bsignals.h>
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/JID/JID.h>
#include <Swiften/Elements/SecurityLabelsCatalog.h>
#include <Swiften/Elements/ErrorPayload.h>
#include <Swiften/Presence/PresenceOracle.h>
#include <Swiften/Queries/IQRouter.h>
#include <Swiften/Base/IDGenerator.h>
#include <Swiften/MUC/MUCRegistry.h>
#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
#include <Swift/Controllers/HistoryController.h>
#include <Swift/Controllers/HighlightManager.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
class IQRouter;
class StanzaChannel;
class ChatWindowFactory;
class AvatarManager;
class UIEventStream;
class EventController;
class EntityCapsProvider;
class HighlightManager;
class Highlighter;
class ChatMessageParser;
class AutoAcceptMUCInviteDecider;
class ChatControllerBase : public boost::bsignals::trackable {
public:
virtual ~ChatControllerBase();
void showChatWindow();
void activateChatWindow();
+ bool hasOpenWindow() const;
virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);
std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
void replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight);
virtual void setOnline(bool online);
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;
+ boost::signal<void ()> onWindowClosed;
int getUnreadCount();
const JID& getToJID() {return toJID_;}
void handleCapsChanged(const JID& jid);
void setCanStartImpromptuChats(bool supportsImpromptu);
virtual ChatWindow* detachChatWindow();
boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC;
protected:
ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
/**
* Pass the Message appended, and the stanza used to send it.
*/
virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {}
virtual std::string senderDisplayNameFromMessage(const JID& from) = 0;
virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0;
virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}
virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&) {}
virtual void preSendMessageRequest(boost::shared_ptr<Message>) {}
virtual bool isFromContact(const JID& from);
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0;
virtual void dayTicked() {}
virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {}
virtual void cancelReplaces() = 0;
/** JID any iq for account should go to - bare except for PMs */
virtual JID getBaseJID();
virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0;
private:
IDGenerator idGenerator_;
std::string lastSentMessageStanzaID_;
void createDayChangeTimer();
void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
void handleAllMessagesRead();
void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error);
void handleDayChangeTick();
void handleMUCInvitation(Message::ref message);
void handleMediatedMUCInvitation(Message::ref message);
void handleGeneralMUCInvitation(MUCInviteEvent::ref event);
void handleLogCleared();
protected:
JID selfJID_;
std::vector<boost::shared_ptr<StanzaEvent> > unreadMessages_;
std::vector<boost::shared_ptr<StanzaEvent> > targetedUnreadMessages_;
StanzaChannel* stanzaChannel_;
IQRouter* iqRouter_;
ChatWindowFactory* chatWindowFactory_;
ChatWindow* chatWindow_;
JID toJID_;
bool labelsEnabled_;
std::map<JID, std::string> lastMessagesUIID_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
bool useDelayForLatency_;
EventController* eventController_;
boost::shared_ptr<Timer> dateChangeTimer_;
TimerFactory* timerFactory_;
EntityCapsProvider* entityCapsProvider_;
SecurityLabelsCatalog::Item lastLabel_;
HistoryController* historyController_;
MUCRegistry* mucRegistry_;
Highlighter* highlighter_;
boost::shared_ptr<ChatMessageParser> chatMessageParser_;
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
UIEventStream* eventStream_;
};
}
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 8a077d1..3db1327 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,960 +1,990 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/Controllers/Chat/ChatsManager.h>
#include <boost/bind.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/optional.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/split_free.hpp>
#include <Swiften/Base/foreach.h>
#include <Swiften/Presence/PresenceSender.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/MUC/MUCManager.h>
#include <Swiften/Elements/ChatState.h>
#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swiften/MUC/MUCBookmarkManager.h>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
#include <Swiften/Roster/XMPPRoster.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/VCards/VCardManager.h>
#include <Swift/Controllers/Chat/ChatController.h>
#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <Swift/Controllers/Chat/MUCSearchController.h>
#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/Chat/MUCController.h>
#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h>
#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
#include <Swift/Controllers/FileTransfer/FileTransferController.h>
#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
#include <Swift/Controllers/ProfileSettingsProvider.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/WhiteboardManager.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <Swift/Controllers/Chat/UserSearchController.h>
#include <Swiften/Disco/DiscoServiceWalker.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/StringCodecs/Base64.h>
#include <Swiften/Base/Log.h>
BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 1)
namespace boost {
namespace serialization {
template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) {
std::string jidStr = jid.toString();
ar << jidStr;
}
template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) {
std::string stringJID;
ar >> stringJID;
jid = Swift::JID(stringJID);
}
template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){
split_free(ar, t, file_version);
}
template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) {
ar & chat.jid;
ar & chat.chatName;
ar & chat.activity;
ar & chat.isMUC;
ar & chat.nick;
ar & chat.impromptuJIDs;
if (version > 0) {
ar & chat.password;
}
}
}
}
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,
EventController* eventController,
ChatWindowFactory* chatWindowFactory,
JoinMUCWindowFactory* joinMUCWindowFactory,
NickResolver* nickResolver,
PresenceOracle* presenceOracle,
PresenceSender* presenceSender,
UIEventStream* uiEventStream,
ChatListWindowFactory* chatListWindowFactory,
bool useDelayForLatency,
TimerFactory* timerFactory,
MUCRegistry* mucRegistry,
EntityCapsProvider* entityCapsProvider,
MUCManager* mucManager,
MUCSearchWindowFactory* mucSearchWindowFactory,
ProfileSettingsProvider* profileSettings,
FileTransferOverview* ftOverview,
XMPPRoster* roster,
bool eagleMode,
SettingsProvider* settings,
HistoryController* historyController,
WhiteboardManager* whiteboardManager,
HighlightManager* highlightManager,
ClientBlockListManager* clientBlockListManager,
const std::map<std::string, std::string>& emoticons,
UserSearchController* inviteUserSearchController,
VCardManager* vcardManager) :
jid_(jid),
joinMUCWindowFactory_(joinMUCWindowFactory),
useDelayForLatency_(useDelayForLatency),
mucRegistry_(mucRegistry),
entityCapsProvider_(entityCapsProvider),
mucManager(mucManager),
ftOverview_(ftOverview),
roster_(roster),
eagleMode_(eagleMode),
settings_(settings),
historyController_(historyController),
whiteboardManager_(whiteboardManager),
highlightManager_(highlightManager),
emoticons_(emoticons),
clientBlockListManager_(clientBlockListManager),
inviteUserSearchController_(inviteUserSearchController),
vcardManager_(vcardManager) {
timerFactory_ = timerFactory;
eventController_ = eventController;
stanzaChannel_ = stanzaChannel;
iqRouter_ = iqRouter;
chatWindowFactory_ = chatWindowFactory;
nickResolver_ = nickResolver;
presenceOracle_ = presenceOracle;
avatarManager_ = NULL;
serverDiscoInfo_ = boost::make_shared<DiscoInfo>();
presenceSender_ = presenceSender;
uiEventStream_ = uiEventStream;
mucBookmarkManager_ = NULL;
profileSettings_ = profileSettings;
presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));
uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1));
chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_);
chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1));
chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1));
chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this));
joinMUCWindow_ = NULL;
mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_);
mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1));
ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1));
whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2));
whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted));
whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated));
whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected));
roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this));
settings_->onSettingChanged.connect(boost::bind(&ChatsManager::handleSettingChanged, this, _1));
userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
setupBookmarks();
loadRecents();
autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_);
}
ChatsManager::~ChatsManager() {
settings_->onSettingChanged.disconnect(boost::bind(&ChatsManager::handleSettingChanged, this, _1));
roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this));
delete joinMUCWindow_;
foreach (JIDChatControllerPair controllerPair, chatControllers_) {
delete controllerPair.second;
}
foreach (JIDMUCControllerPair controllerPair, mucControllers_) {
delete controllerPair.second;
}
delete mucBookmarkManager_;
delete mucSearchController_;
delete autoAcceptMUCInviteDecider_;
}
void ChatsManager::saveRecents() {
std::stringstream serializeStream;
boost::archive::text_oarchive oa(serializeStream);
std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
if (recentsLimited.size() > 25) {
recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end());
}
if (eagleMode_) {
foreach(ChatListWindow::Chat& chat, recentsLimited) {
chat.activity = "";
}
}
+ class RemoveRecent {
+ public:
+ static bool ifPrivateMessage(const ChatListWindow::Chat& chat) {
+ return chat.isPrivateMessage;
+ }
+ };
+
+ recentsLimited.erase(std::remove_if(recentsLimited.begin(), recentsLimited.end(), RemoveRecent::ifPrivateMessage), recentsLimited.end());
+
oa << recentsLimited;
std::string serializedStr = Base64::encode(createByteArray(serializeStream.str()));
profileSettings_->storeString(RECENT_CHATS, serializedStr);
}
void ChatsManager::handleClearRecentsRequested() {
recentChats_.clear();
saveRecents();
handleUnreadCountChanged(NULL);
}
void ChatsManager::handleJIDAddedToRoster(const JID &jid) {
updatePresenceReceivingStateOnChatController(jid);
}
void ChatsManager::handleJIDRemovedFromRoster(const JID &jid) {
updatePresenceReceivingStateOnChatController(jid);
}
void ChatsManager::handleJIDUpdatedInRoster(const JID &jid) {
updatePresenceReceivingStateOnChatController(jid);
}
void ChatsManager::handleRosterCleared() {
/* Setting that all chat controllers aren't receiving presence anymore;
including MUC 1-to-1 chats due to the assumtion that this handler
is only called on log out. */
foreach(JIDChatControllerPair pair, chatControllers_) {
pair.second->setContactIsReceivingPresence(false);
}
}
void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) {
ChatController* controller = getChatControllerIfExists(jid);
if (controller) {
if (!mucRegistry_->isMUC(jid.toBare())) {
RosterItemPayload::Subscription subscription = roster_->getSubscriptionStateForJID(jid);
controller->setContactIsReceivingPresence(subscription == RosterItemPayload::From || subscription == RosterItemPayload::Both);
} else {
controller->setContactIsReceivingPresence(true);
}
}
}
ChatListWindow::Chat ChatsManager::updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const {
ChatListWindow::Chat fixedChat = chat;
if (fixedChat.isMUC) {
if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) {
fixedChat.statusType = StatusShow::Online;
}
} else {
if (avatarManager_) {
fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid);
}
Presence::ref presence = presenceOracle_->getHighestPriorityPresence(fixedChat.jid.toBare());
fixedChat.statusType = presence ? presence->getShow() : StatusShow::None;
}
return fixedChat;
}
void ChatsManager::loadRecents() {
std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
if (recentsString.find("\t") != std::string::npos) {
// old format
std::vector<std::string> recents;
boost::split(recents, recentsString, boost::is_any_of("\n"));
int i = 0;
foreach (std::string recentString, recents) {
if (i++ > 30) {
break;
}
std::vector<std::string> recent;
boost::split(recent, recentString, boost::is_any_of("\t"));
if (recent.size() < 4) {
continue;
}
JID jid(recent[0]);
if (!jid.isValid()) {
continue;
}
std::string activity(recent[1]);
bool isMUC = recent[2] == "true";
std::string nick(recent[3]);
StatusShow::Type type = StatusShow::None;
boost::filesystem::path path;
- ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick);
+ ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, false, nick);
chat = updateChatStatusAndAvatarHelper(chat);
prependRecent(chat);
}
} else if (!recentsString.empty()){
// boost searilaize based format
ByteArray debase64 = Base64::decode(recentsString);
std::vector<ChatListWindow::Chat> recentChats;
std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size()));
try {
boost::archive::text_iarchive ia(deserializeStream);
ia >> recentChats;
} catch (const boost::archive::archive_exception& e) {
SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl;
return;
}
foreach(ChatListWindow::Chat chat, recentChats) {
chat.statusType = StatusShow::None;
chat = updateChatStatusAndAvatarHelper(chat);
prependRecent(chat);
}
}
handleUnreadCountChanged(NULL);
}
void ChatsManager::setupBookmarks() {
if (!mucBookmarkManager_) {
mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
mucBookmarkManager_->onBookmarksReady.connect(boost::bind(&ChatsManager::handleBookmarksReady, this));
mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&ChatsManager::handleMUCBookmarkAdded, this, _1));
mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&ChatsManager::handleMUCBookmarkRemoved, this, _1));
if (chatListWindow_) {
chatListWindow_->setBookmarksEnabled(false);
chatListWindow_->clearBookmarks();
}
}
}
void ChatsManager::handleBookmarksReady() {
if (chatListWindow_) {
chatListWindow_->setBookmarksEnabled(true);
}
}
void ChatsManager::handleMUCBookmarkAdded(const MUCBookmark& bookmark) {
std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom());
if (it == mucControllers_.end() && bookmark.getAutojoin()) {
handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false, false );
}
chatListWindow_->addMUCBookmark(bookmark);
}
void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) {
chatListWindow_->removeMUCBookmark(bookmark);
}
-ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity) {
+ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage) {
int unreadCount = 0;
if (mucRegistry_->isMUC(jid)) {
MUCController* controller = mucControllers_[jid.toBare()];
StatusShow::Type type = StatusShow::None;
std::string nick = "";
std::string password = "";
if (controller) {
unreadCount = controller->getUnreadCount();
if (controller->isJoined()) {
type = StatusShow::Online;
}
nick = controller->getNick();
if (controller->getPassword()) {
password = *controller->getPassword();
}
if (controller->isImpromptu()) {
- ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick, password);
+ ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password);
typedef std::pair<std::string, JID> StringJIDPair;
std::map<std::string, JID> participants = controller->getParticipantJIDs();
chat.impromptuJIDs = participants;
return chat;
}
}
- return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick, password);
+ return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password);
} else {
ChatController* controller = getChatControllerIfExists(jid, false);
if (controller) {
unreadCount = controller->getUnreadCount();
}
JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare();
Presence::ref presence = presenceOracle_->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);
+ return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false, privateMessage);
}
}
void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) {
- if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) {
- /* Don't include PMs in MUC rooms.*/
- return;
- }
- ChatListWindow::Chat chat = createChatListChatItem(jid, activity);
+ const bool privateMessage = mucRegistry_->isMUC(jid.toBare()) && !isMUC;
+ ChatListWindow::Chat chat = createChatListChatItem(jid, activity, privateMessage);
/* FIXME: handle nick changes */
appendRecent(chat);
handleUnreadCountChanged(NULL);
saveRecents();
}
+void ChatsManager::handleChatClosed(const JID& /*jid*/) {
+ cleanupPrivateMessageRecents();
+ chatListWindow_->setRecents(recentChats_);
+}
+
void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) {
int unreadTotal = 0;
bool controllerIsMUC = dynamic_cast<MUCController*>(controller);
bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare());
foreach (ChatListWindow::Chat& chatItem, recentChats_) {
bool match = false;
if (controller) {
/* Matching MUC item */
match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
/* Matching PM */
match |= isPM && chatItem.jid == controller->getToJID();
/* Matching non-PM */
match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
}
if (match) {
chatItem.setUnreadCount(controller->getUnreadCount());
}
unreadTotal += chatItem.unreadCount;
}
chatListWindow_->setRecents(recentChats_);
chatListWindow_->setUnreadCount(unreadTotal);
}
boost::optional<ChatListWindow::Chat> ChatsManager::removeExistingChat(const ChatListWindow::Chat& chat) {
std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat);
if (result != recentChats_.end()) {
ChatListWindow::Chat existingChat = *result;
recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
return boost::optional<ChatListWindow::Chat>(existingChat);
} else {
return boost::optional<ChatListWindow::Chat>();
}
}
+void ChatsManager::cleanupPrivateMessageRecents() {
+ /* if we leave a MUC and close a PM, remove it's recent chat entry */
+ const std::list<ChatListWindow::Chat> chats = recentChats_;
+ foreach (const ChatListWindow::Chat& chat, chats) {
+ if (chat.isPrivateMessage) {
+ typedef std::map<JID, MUCController*> ControllerMap;
+ ControllerMap::iterator muc = mucControllers_.find(chat.jid.toBare());
+ if (muc == mucControllers_.end() || !muc->second->isJoined()) {
+ ChatController* chatController = getChatControllerIfExists(chat.jid);
+ if (!chatController || !chatController->hasOpenWindow()) {
+ removeExistingChat(chat);
+ break;
+ }
+ }
+ }
+ }
+}
+
void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) {
boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
ChatListWindow::Chat mergedChat = chat;
if (oldChat && !oldChat->impromptuJIDs.empty()) {
mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
}
recentChats_.push_front(mergedChat);
}
void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) {
boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
ChatListWindow::Chat mergedChat = chat;
if (oldChat && !oldChat->impromptuJIDs.empty()) {
mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
}
recentChats_.push_back(mergedChat);
}
void ChatsManager::handleUserLeftMUC(MUCController* mucController) {
std::map<JID, MUCController*>::iterator it;
for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) {
if ((*it).second == mucController) {
foreach (ChatListWindow::Chat& chat, recentChats_) {
if (chat.isMUC && chat.jid == (*it).first) {
chat.statusType = StatusShow::None;
- chatListWindow_->setRecents(recentChats_);
- break;
}
}
mucControllers_.erase(it);
delete mucController;
- return;
+ break;
}
}
+ cleanupPrivateMessageRecents();
+ chatListWindow_->setRecents(recentChats_);
}
void ChatsManager::handleSettingChanged(const std::string& settingPath) {
if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) {
userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS);
return;
}
}
void ChatsManager::finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID) {
// send impromptu invites for the new MUC
std::vector<JID> missingJIDsToInvite = jidsToInvite;
typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
std::map<std::string, MUCOccupant> occupants = muc->getOccupants();
foreach(StringMUCOccupantPair occupant, occupants) {
boost::optional<JID> realJID = occupant.second.getRealJID();
if (realJID) {
missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end());
}
}
if (reuseChatJID) {
muc->invitePerson(reuseChatJID.get(), reason, true, true);
}
foreach(const JID& jid, missingJIDsToInvite) {
muc->invitePerson(jid, reason, true);
}
}
void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
boost::shared_ptr<RequestChatUIEvent> chatEvent = boost::dynamic_pointer_cast<RequestChatUIEvent>(event);
if (chatEvent) {
handleChatRequest(chatEvent->getContact());
return;
}
boost::shared_ptr<RemoveMUCBookmarkUIEvent> removeMUCBookmarkEvent = boost::dynamic_pointer_cast<RemoveMUCBookmarkUIEvent>(event);
if (removeMUCBookmarkEvent) {
mucBookmarkManager_->removeBookmark(removeMUCBookmarkEvent->getBookmark());
return;
}
boost::shared_ptr<AddMUCBookmarkUIEvent> addMUCBookmarkEvent = boost::dynamic_pointer_cast<AddMUCBookmarkUIEvent>(event);
if (addMUCBookmarkEvent) {
mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark());
return;
}
boost::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = boost::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event);
if (createImpromptuMUCEvent) {
assert(!localMUCServiceJID_.toString().empty());
// create new muc
JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID();
// join muc
MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true);
mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>()));
mucControllers_[roomJID]->activateChatWindow();
}
boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event);
if (editMUCBookmarkEvent) {
mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark());
}
else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu());
mucControllers_[joinEvent->getJID()]->activateChatWindow();
}
else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
if (!joinMUCWindow_) {
joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_);
joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));
}
joinMUCWindow_->setMUC(joinEvent->getRoom());
joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));
joinMUCWindow_->show();
}
}
void ChatsManager::markAllRecentsOffline() {
foreach (ChatListWindow::Chat& chat, recentChats_) {
chat.setStatusType(StatusShow::None);
}
chatListWindow_->setRecents(recentChats_);
}
void ChatsManager::handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason) {
JID reuseChatInvite = chatController->getToJID();
chatControllers_.erase(chatController->getToJID());
delete chatController;
// join new impromptu muc
assert(!localMUCServiceJID_.toString().empty());
// create new muc
JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_);
// join muc
MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow);
mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite)));
}
/**
* If a resource goes offline, release bound chatdialog to that resource.
*/
void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return;
foreach (ChatListWindow::Chat& chat, recentChats_) {
if (newPresence->getFrom().toBare() == chat.jid.toBare() && !chat.isMUC) {
Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare());
chat.setStatusType(presence ? presence->getShow() : StatusShow::None);
chatListWindow_->setRecents(recentChats_);
break;
}
}
//if (newPresence->getType() != Presence::Unavailable) return;
JID fullJID(newPresence->getFrom());
std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID);
if (it == chatControllers_.end()) return;
JID bareJID(fullJID.toBare());
//It doesn't make sense to have two unbound dialogs.
if (chatControllers_.find(bareJID) != chatControllers_.end()) return;
rebindControllerJID(fullJID, bareJID);
}
void ChatsManager::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) {
serverDiscoInfo_ = info;
foreach (JIDChatControllerPair pair, chatControllers_) {
pair.second->setAvailableServerFeatures(info);
}
foreach (JIDMUCControllerPair pair, mucControllers_) {
pair.second->setAvailableServerFeatures(info);
}
}
/**
* This is to be called on connect/disconnect.
*/
void ChatsManager::setOnline(bool enabled) {
foreach (JIDChatControllerPair controllerPair, chatControllers_) {
controllerPair.second->setOnline(enabled);
}
foreach (JIDMUCControllerPair controllerPair, mucControllers_) {
controllerPair.second->setOnline(enabled);
if (enabled) {
controllerPair.second->rejoin();
}
}
if (!enabled) {
delete mucBookmarkManager_;
mucBookmarkManager_ = NULL;
chatListWindow_->setBookmarksEnabled(false);
markAllRecentsOffline();
} else {
setupBookmarks();
localMUCServiceFinderWalker_ = boost::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_);
localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2));
localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
localMUCServiceFinderWalker_->beginWalk();
}
}
void ChatsManager::handleChatRequest(const std::string &contact) {
ChatController* controller = getChatControllerOrFindAnother(JID(contact));
controller->activateChatWindow();
}
ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) {
ChatController* controller = getChatControllerIfExists(contact);
if (!controller && !mucRegistry_->isMUC(contact.toBare())) {
foreach (JIDChatControllerPair pair, chatControllers_) {
if (pair.first.toBare() == contact.toBare()) {
controller = pair.second;
break;
}
}
}
return controller ? controller : createNewChatController(contact);
}
ChatController* ChatsManager::createNewChatController(const JID& contact) {
assert(chatControllers_.find(contact) == chatControllers_.end());
boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */
ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_);
chatControllers_[contact] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
+ controller->onWindowClosed.connect(boost::bind(&ChatsManager::handleChatClosed, this, contact));
controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3));
updatePresenceReceivingStateOnChatController(contact);
controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty());
return controller;
}
ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) {
ChatController* controller = getChatControllerIfExists(contact);
return controller ? controller : createNewChatController(contact);
}
ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) {
if (chatControllers_.find(contact) == chatControllers_.end()) {
if (mucRegistry_->isMUC(contact.toBare())) {
return NULL;
}
//Need to look for an unbound window to bind first
JID bare(contact.toBare());
if (chatControllers_.find(bare) != chatControllers_.end()) {
rebindControllerJID(bare, contact);
} else {
foreach (JIDChatControllerPair pair, chatControllers_) {
if (pair.first.toBare() == contact.toBare()) {
if (rebindIfNeeded) {
rebindControllerJID(pair.first, contact);
return chatControllers_[contact];
} else {
return pair.second;
}
}
}
return NULL;
}
}
return chatControllers_[contact];
}
void ChatsManager::rebindControllerJID(const JID& from, const JID& to) {
chatControllers_[to] = chatControllers_[from];
chatControllers_.erase(from);
chatControllers_[to]->setToJID(to);
}
MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow) {
MUC::ref muc;
if (!stanzaChannel_->isAvailable()) {
/* This is potentially not the optimal solution, but it will avoid consistency issues.*/
return muc;
}
if (addAutoJoin) {
MUCBookmark bookmark(mucJID, mucJID.getNode());
bookmark.setAutojoin(true);
if (nickMaybe) {
bookmark.setNick(*nickMaybe);
}
if (password) {
bookmark.setPassword(*password);
}
mucBookmarkManager_->addBookmark(bookmark);
}
std::map<JID, MUCController*>::iterator it = mucControllers_.find(mucJID);
if (it != mucControllers_.end()) {
it->second->rejoin();
} else {
std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_);
muc = mucManager->createMUC(mucJID);
if (createAsReservedIfNew) {
muc->setCreateAsReservedIfNew();
}
if (isImpromptu) {
muc->setCreateAsReservedIfNew();
}
MUCController* controller = NULL;
SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL;
if (reuseChatwindow) {
chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);
}
boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */
controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_);
if (chatWindowFactoryAdapter) {
/* The adapters are only passed to chat windows, which are deleted in their
* controllers' dtor, which are deleted in ChatManager's dtor. The adapters
* are also deleted there.*/
chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter;
}
mucControllers_[mucJID] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true));
controller->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]->showChatWindow();
return muc;
}
void ChatsManager::handleSearchMUCRequest() {
mucSearchController_->openSearchWindow();
}
void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
JID jid = message->getFrom();
boost::shared_ptr<MessageEvent> event(new MessageEvent(message));
bool isInvite = !!message->getPayload<MUCInvitationPayload>();
bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite());
if (isMediatedInvite) {
jid = (*message->getPayload<MUCUserPayload>()->getInvite()).from;
}
if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !isMediatedInvite && !message->hasSubject()) {
return;
}
// Try to deliver it to a MUC
if (message->getType() == Message::Groupchat || message->getType() == Message::Error /*|| (isInvite && message->getType() == Message::Normal)*/) {
std::map<JID, MUCController*>::iterator i = mucControllers_.find(jid.toBare());
if (i != mucControllers_.end()) {
i->second->handleIncomingMessage(event);
return;
}
else if (message->getType() == Message::Groupchat) {
//FIXME: Error handling - groupchat messages from an unknown muc.
return;
}
}
// check for impromptu invite to potentially auto-accept
MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
if (invite->getIsContinuation()) {
// check for existing chat controller for the from JID
ChatController* controller = getChatControllerIfExists(jid);
if (controller) {
ChatWindow* window = controller->detachChatWindow();
chatControllers_.erase(jid);
delete controller;
handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window);
}
} else {
handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true);
return;
}
}
//if not a mucroom
if (!event->isReadable() && !isInvite && !isMediatedInvite) {
/* Only route such messages if a window exists, don't open new windows for them.*/
ChatController* controller = getChatControllerIfExists(jid);
if (controller) {
controller->handleIncomingMessage(event);
}
} else {
getChatControllerOrCreate(jid)->handleIncomingMessage(event);
}
}
void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) {
if (joinMUCWindow_) {
joinMUCWindow_->setMUC(muc.toString());
}
}
void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) {
uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getPassword(), mucBookmark.getNick()));
}
void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) {
ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty());
chatController->handleNewFileTransferController(ftc);
chatController->activateChatWindow();
}
void ChatsManager::handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf) {
ChatController* chatController = getChatControllerOrCreate(contact);
chatController->handleWhiteboardSessionRequest(senderIsSelf);
chatController->activateChatWindow();
}
void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state) {
ChatController* chatController = getChatControllerOrCreate(contact);
chatController->handleWhiteboardStateChange(state);
chatController->activateChatWindow();
if (state == ChatWindow::WhiteboardAccepted) {
boost::filesystem::path path;
JID bareJID = contact.toBare();
if (avatarManager_) {
path = avatarManager_->getAvatarPath(bareJID);
}
ChatListWindow::Chat chat(bareJID, nickResolver_->jidToNick(bareJID), "", 0, StatusShow::None, path, false);
chatListWindow_->addWhiteboardSession(chat);
} else {
chatListWindow_->removeWhiteboardSession(contact.toBare());
}
}
void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) {
if (chat.isMUC && !chat.impromptuJIDs.empty()) {
typedef std::pair<std::string, JID> StringJIDPair;
std::vector<JID> inviteJIDs;
foreach(StringJIDPair pair, chat.impromptuJIDs) {
inviteJIDs.push_back(pair.second);
}
uiEventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, ""));
}
else if (chat.isMUC) {
/* FIXME: This means that recents requiring passwords will just flat-out not work */
uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick));
}
else {
uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(chat.jid));
}
}
void ChatsManager::handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info) {
foreach (DiscoInfo::Identity identity, info->getIdentities()) {
if ((identity.getCategory() == "directory"
&& identity.getType() == "chatroom")
|| (identity.getCategory() == "conference"
&& identity.getType() == "text")) {
localMUCServiceJID_ = service;
localMUCServiceFinderWalker_->endWalk();
SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl;
break;
}
}
}
void ChatsManager::handleLocalServiceWalkFinished() {
onImpromptuMUCServiceDiscovered(!localMUCServiceJID_.toString().empty());
}
std::vector<ChatListWindow::Chat> ChatsManager::getRecentChats() const {
return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
}
std::vector<Contact::ref> Swift::ChatsManager::getContacts() {
std::vector<Contact::ref> result;
foreach (ChatListWindow::Chat chat, recentChats_) {
if (!chat.isMUC) {
result.push_back(boost::make_shared<Contact>(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath));
}
}
return result;
}
ChatsManager::SingleChatWindowFactoryAdapter::SingleChatWindowFactoryAdapter(ChatWindow* chatWindow) : chatWindow_(chatWindow) {}
ChatsManager::SingleChatWindowFactoryAdapter::~SingleChatWindowFactoryAdapter() {}
ChatWindow* ChatsManager::SingleChatWindowFactoryAdapter::createChatWindow(const JID &, UIEventStream*) {
return chatWindow_;
}
}
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 41435d9..179f536 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -1,186 +1,188 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <map>
#include <string>
#include <boost/shared_ptr.hpp>
#include <Swiften/Base/IDGenerator.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 <Swiften/MUC/MUCBookmark.h>
#include <Swiften/MUC/MUC.h>
#include <Swift/Controllers/ContactProvider.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
namespace Swift {
class EventController;
class ChatController;
class ChatControllerBase;
class MUCController;
class MUCManager;
class JoinMUCWindow;
class JoinMUCWindowFactory;
class NickResolver;
class PresenceOracle;
class AvatarManager;
class StanzaChannel;
class IQRouter;
class PresenceSender;
class MUCBookmarkManager;
class ChatListWindowFactory;
class TimerFactory;
class EntityCapsProvider;
class DirectedPresenceSender;
class MUCSearchWindowFactory;
class ProfileSettingsProvider;
class MUCSearchController;
class FileTransferOverview;
class FileTransferController;
class XMPPRoster;
class SettingsProvider;
class WhiteboardManager;
class HistoryController;
class HighlightManager;
class ClientBlockListManager;
class ChatMessageParser;
class DiscoServiceWalker;
class AutoAcceptMUCInviteDecider;
class UserSearchController;
class VCardManager;
class ChatsManager : public ContactProvider {
public:
ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController, VCardManager* vcardManager);
virtual ~ChatsManager();
void setAvatarManager(AvatarManager* avatarManager);
void setOnline(bool enabled);
void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
void handleIncomingMessage(boost::shared_ptr<Message> message);
std::vector<ChatListWindow::Chat> getRecentChats() const;
virtual std::vector<Contact::ref> getContacts();
boost::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered;
private:
class SingleChatWindowFactoryAdapter : public ChatWindowFactory {
public:
SingleChatWindowFactoryAdapter(ChatWindow* chatWindow);
virtual ~SingleChatWindowFactoryAdapter();
virtual ChatWindow* createChatWindow(const JID &, UIEventStream*);
private:
ChatWindow* chatWindow_;
};
private:
- ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity);
+ ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage);
void handleChatRequest(const std::string& contact);
void finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID = boost::optional<JID>());
MUC::ref handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = 0);
void handleSearchMUCRequest();
void handleMUCSelectedAfterSearch(const JID&);
void rebindControllerJID(const JID& from, const JID& to);
void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
void handleUIEvent(boost::shared_ptr<UIEvent> event);
void handleMUCBookmarkAdded(const MUCBookmark& bookmark);
void handleMUCBookmarkRemoved(const MUCBookmark& bookmark);
void handleUserLeftMUC(MUCController* mucController);
void handleBookmarksReady();
void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC);
+ void handleChatClosed(const JID& jid);
void handleNewFileTransferController(FileTransferController*);
void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf);
void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state);
boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat);
+ void cleanupPrivateMessageRecents();
void appendRecent(const ChatListWindow::Chat& chat);
void prependRecent(const ChatListWindow::Chat& chat);
void setupBookmarks();
void loadRecents();
void saveRecents();
void handleChatMadeRecent();
void handleMUCBookmarkActivated(const MUCBookmark&);
void handleRecentActivated(const ChatListWindow::Chat&);
void handleUnreadCountChanged(ChatControllerBase* controller);
void handleAvatarChanged(const JID& jid);
void handleClearRecentsRequested();
void handleJIDAddedToRoster(const JID&);
void handleJIDRemovedFromRoster(const JID&);
void handleJIDUpdatedInRoster(const JID&);
void handleRosterCleared();
void handleSettingChanged(const std::string& settingPath);
void markAllRecentsOffline();
void handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason);
void handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info);
void handleLocalServiceWalkFinished();
void updatePresenceReceivingStateOnChatController(const JID&);
ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const;
ChatController* getChatControllerOrFindAnother(const JID &contact);
ChatController* createNewChatController(const JID &contact);
ChatController* getChatControllerOrCreate(const JID &contact);
ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true);
private:
std::map<JID, MUCController*> mucControllers_;
std::map<JID, ChatController*> chatControllers_;
std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_;
EventController* eventController_;
JID jid_;
StanzaChannel* stanzaChannel_;
IQRouter* iqRouter_;
ChatWindowFactory* chatWindowFactory_;
JoinMUCWindowFactory* joinMUCWindowFactory_;
NickResolver* nickResolver_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
PresenceSender* presenceSender_;
UIEventStream* uiEventStream_;
MUCBookmarkManager* mucBookmarkManager_;
boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
ChatListWindow* chatListWindow_;
JoinMUCWindow* joinMUCWindow_;
boost::bsignals::scoped_connection uiEventConnection_;
bool useDelayForLatency_;
TimerFactory* timerFactory_;
MUCRegistry* mucRegistry_;
EntityCapsProvider* entityCapsProvider_;
MUCManager* mucManager;
MUCSearchController* mucSearchController_;
std::list<ChatListWindow::Chat> recentChats_;
ProfileSettingsProvider* profileSettings_;
FileTransferOverview* ftOverview_;
XMPPRoster* roster_;
bool eagleMode_;
bool userWantsReceipts_;
SettingsProvider* settings_;
HistoryController* historyController_;
WhiteboardManager* whiteboardManager_;
HighlightManager* highlightManager_;
std::map<std::string, std::string> emoticons_;
ClientBlockListManager* clientBlockListManager_;
JID localMUCServiceJID_;
boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_;
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
UserSearchController* inviteUserSearchController_;
IDGenerator idGenerator_;
VCardManager* vcardManager_;
};
}