summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
-rw-r--r--Swift/Controllers/UIInterfaces/ChatListWindow.h7
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h1
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h1
-rw-r--r--Swift/QtUI/QtChatWindow.cpp4
-rw-r--r--Swift/QtUI/QtChatWindow.h1
11 files changed, 70 insertions, 19 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_;
};
}
diff --git a/Swift/Controllers/UIInterfaces/ChatListWindow.h b/Swift/Controllers/UIInterfaces/ChatListWindow.h
index 38d8c3e..f7eb151 100644
--- a/Swift/Controllers/UIInterfaces/ChatListWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h
@@ -1,97 +1,98 @@
/*
* 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 <list>
#include <set>
#include <map>
#include <boost/shared_ptr.hpp>
#include <Swiften/MUC/MUCBookmark.h>
#include <Swiften/Elements/StatusShow.h>
#include <boost/filesystem/path.hpp>
#include <Swiften/Base/foreach.h>
#include <Swiften/Base/boost_bsignals.h>
namespace Swift {
class ChatListWindow {
public:
class Chat {
public:
- Chat() : statusType(StatusShow::None), isMUC(false), unreadCount(0) {}
- Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, const std::string& nick = "", const boost::optional<std::string> password = boost::optional<std::string>())
- : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), password(password), unreadCount(unreadCount), avatarPath(avatarPath) {}
+ Chat() : statusType(StatusShow::None), isMUC(false), unreadCount(0), isPrivateMessage(false) {}
+ Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, bool isPrivateMessage = false, const std::string& nick = "", const boost::optional<std::string> password = boost::optional<std::string>())
+ : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), password(password), unreadCount(unreadCount), avatarPath(avatarPath), isPrivateMessage(isPrivateMessage) {}
/** Assume that nicks and other transient features aren't important for equality */
bool operator==(const Chat& other) const {
if (impromptuJIDs.empty()) {
return jid.toBare() == other.jid.toBare()
&& isMUC == other.isMUC;
} else { /* compare the chat occupant lists */
typedef std::map<std::string, JID> JIDMap;
foreach (const JIDMap::value_type& jid, impromptuJIDs) {
bool found = false;
foreach (const JIDMap::value_type& otherJID, other.impromptuJIDs) {
if (jid.second.toBare() == otherJID.second.toBare()) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
}
void setUnreadCount(int unread) {
unreadCount = unread;
}
void setStatusType(StatusShow::Type type) {
statusType = type;
}
void setAvatarPath(const boost::filesystem::path& path) {
avatarPath = path;
}
std::string getImpromptuTitle() const {
typedef std::pair<std::string, JID> StringJIDPair;
std::string title;
foreach(StringJIDPair pair, impromptuJIDs) {
if (title.empty()) {
title += pair.first;
} else {
title += ", " + pair.first;
}
}
return title;
}
JID jid;
std::string chatName;
std::string activity;
StatusShow::Type statusType;
bool isMUC;
std::string nick;
boost::optional<std::string> password;
int unreadCount;
boost::filesystem::path avatarPath;
std::map<std::string, JID> impromptuJIDs;
+ bool isPrivateMessage;
};
virtual ~ChatListWindow();
virtual void setBookmarksEnabled(bool enabled) = 0;
virtual void addMUCBookmark(const MUCBookmark& bookmark) = 0;
virtual void addWhiteboardSession(const ChatListWindow::Chat& chat) = 0;
virtual void removeWhiteboardSession(const JID& jid) = 0;
virtual void removeMUCBookmark(const MUCBookmark& bookmark) = 0;
virtual void setRecents(const std::list<Chat>& recents) = 0;
virtual void setUnreadCount(int unread) = 0;
virtual void clearBookmarks() = 0;
boost::signal<void (const MUCBookmark&)> onMUCBookmarkActivated;
boost::signal<void (const Chat&)> onRecentActivated;
boost::signal<void (const JID&)> onWhiteboardActivated;
boost::signal<void ()> onClearRecentsRequested;
};
}
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index bf4744b..81d26c8 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -1,213 +1,214 @@
/*
* 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 <vector>
#include <string>
#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <Swiften/Base/boost_bsignals.h>
#include <Swiften/Elements/SecurityLabelsCatalog.h>
#include <Swiften/Elements/ChatState.h>
#include <Swiften/Elements/Form.h>
#include <Swiften/Elements/MUCOccupant.h>
#include <Swiften/MUC/MUCBookmark.h>
#include <Swift/Controllers/HighlightManager.h>
namespace Swift {
class AvatarManager;
class TreeWidget;
class Roster;
class TabComplete;
class RosterItem;
class ContactRosterItem;
class FileTransferController;
class UserSearchWindow;
class ChatWindow {
public:
class ChatMessagePart {
public:
virtual ~ChatMessagePart() {}
};
class ChatMessage {
public:
ChatMessage() {}
ChatMessage(const std::string& text) {
append(boost::make_shared<ChatTextMessagePart>(text));
}
void append(const boost::shared_ptr<ChatMessagePart>& part) {
parts_.push_back(part);
}
const std::vector<boost::shared_ptr<ChatMessagePart> >& getParts() const {
return parts_;
}
private:
std::vector<boost::shared_ptr<ChatMessagePart> > parts_;
};
class ChatTextMessagePart : public ChatMessagePart {
public:
ChatTextMessagePart(const std::string& text) : text(text) {}
std::string text;
};
class ChatURIMessagePart : public ChatMessagePart {
public:
ChatURIMessagePart(const std::string& target) : target(target) {}
std::string target;
};
class ChatEmoticonMessagePart : public ChatMessagePart {
public:
std::string imagePath;
std::string alternativeText;
};
class ChatHighlightingMessagePart : public ChatMessagePart {
public:
std::string foregroundColor;
std::string backgroundColor;
std::string text;
};
enum AckState {Pending, Received, Failed};
enum ReceiptState {ReceiptRequested, ReceiptReceived, ReceiptFailed};
enum Tristate {Yes, No, Maybe};
enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor, AddContact, ShowProfile};
enum RoomAction {ChangeSubject, Configure, Affiliations, Destroy, Invite};
enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed};
enum WhiteboardSessionState {WhiteboardAccepted, WhiteboardTerminated, WhiteboardRejected};
enum BlockingState {BlockingUnsupported, IsBlocked, IsUnblocked};
enum Direction { UnknownDirection, DefaultDirection };
enum MUCType { StandardMUC, ImpromptuMUC };
enum TimestampBehaviour { KeepTimestamp, UpdateTimestamp };
ChatWindow() {}
virtual ~ChatWindow() {}
/** Add message to window.
* @return id of added message (for acks).
*/
virtual std::string addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
/** Adds action to window.
* @return id of added message (for acks);
*/
virtual std::string addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
virtual void addSystemMessage(const ChatMessage& message, Direction direction) = 0;
virtual void addPresenceMessage(const ChatMessage& message, Direction direction) = 0;
virtual void addErrorMessage(const ChatMessage& message) = 0;
virtual void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
virtual void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
// File transfer related stuff
virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0;
virtual void setFileTransferProgress(std::string, const int percentageDone) = 0;
virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0;
virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false) = 0;
virtual std::string addWhiteboardRequest(bool senderIsSelf) = 0;
virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0;
// message receipts
virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0;
virtual void setContactChatState(ChatState::ChatStateType state) = 0;
virtual void setName(const std::string& name) = 0;
virtual void show() = 0;
+ virtual bool isVisible() const = 0;
virtual void activate() = 0;
virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) = 0;
virtual void setSecurityLabelsEnabled(bool enabled) = 0;
virtual void setCorrectionEnabled(Tristate enabled) = 0;
virtual void setFileTransferEnabled(Tristate enabled) = 0;
virtual void setUnreadMessageCount(int count) = 0;
virtual void convertToMUC(MUCType mucType) = 0;
// virtual TreeWidget *getTreeWidget() = 0;
virtual void setSecurityLabelsError() = 0;
virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() = 0;
virtual void setInputEnabled(bool enabled) = 0;
virtual void setRosterModel(Roster* model) = 0;
virtual void setTabComplete(TabComplete* completer) = 0;
virtual void replaceLastMessage(const ChatMessage& message, const TimestampBehaviour timestampBehaviour) = 0;
virtual void setAckState(const std::string& id, AckState state) = 0;
virtual void flash() = 0;
virtual void setSubject(const std::string& subject) = 0;
virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) = 0;
virtual void setAvailableRoomActions(const std::vector<RoomAction> &actions) = 0;
virtual void setBlockingState(BlockingState state) = 0;
virtual void setCanInitiateImpromptuChats(bool supportsImpromptu) = 0;
virtual void showBookmarkWindow(const MUCBookmark& bookmark) = 0;
/**
* Set an alert on the window.
* @param alertText Description of alert (required).
* @param buttonText Button text to use (optional, no button is shown if empty).
*/
virtual void setAlert(const std::string& alertText, const std::string& buttonText = "") = 0;
/**
* Removes an alert.
*/
virtual void cancelAlert() = 0;
/**
* Actions that can be performed on the selected occupant.
*/
virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) = 0;
/**
* A room configuration has been requested, show the form.
* If the form is cancelled, must emit onConfigurationFormCancelled().
*/
virtual void showRoomConfigurationForm(Form::ref) = 0;
boost::signal<void ()> onClosed;
boost::signal<void ()> onAllMessagesRead;
boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest;
boost::signal<void ()> onSendCorrectionMessageRequest;
boost::signal<void ()> onUserTyping;
boost::signal<void ()> onUserCancelsTyping;
boost::signal<void ()> onAlertButtonClicked;
boost::signal<void (ContactRosterItem*)> onOccupantSelectionChanged;
boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected;
boost::signal<void (const std::string&)> onChangeSubjectRequest;
boost::signal<void ()> onBookmarkRequest;
boost::signal<void (Form::ref)> onConfigureRequest;
boost::signal<void ()> onDestroyRequest;
boost::signal<void (const std::vector<JID>&)> onInviteToChat;
boost::signal<void ()> onConfigurationFormCancelled;
boost::signal<void ()> onGetAffiliationsRequest;
boost::signal<void (MUCOccupant::Affiliation, const JID&)> onSetAffiliationRequest;
boost::signal<void (const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes)> onChangeAffiliationsRequest;
boost::signal<void ()> onLogCleared;
// File transfer related
boost::signal<void (std::string /* id */)> onFileTransferCancel;
boost::signal<void (std::string /* id */, std::string /* description */)> onFileTransferStart;
boost::signal<void (std::string /* id */, std::string /* path */)> onFileTransferAccept;
boost::signal<void (std::string /* path */)> onSendFileRequest;
//Whiteboard related
boost::signal<void ()> onWhiteboardSessionAccept;
boost::signal<void ()> onWhiteboardSessionCancel;
boost::signal<void ()> onWhiteboardWindowShow;
// Blocking Command related
boost::signal<void ()> onBlockUserRequest;
boost::signal<void ()> onUnblockUserRequest;
};
}
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 823ea3a..5e6606b 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -1,95 +1,96 @@
/*
* 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 <boost/shared_ptr.hpp>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swiften/Base/foreach.h>
namespace Swift {
class MockChatWindow : public ChatWindow {
public:
MockChatWindow() : labelsEnabled_(false) {}
virtual ~MockChatWindow();
virtual std::string addMessage(const ChatMessage& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {
lastMessageBody_ = bodyFromMessage(message); return "id";}
virtual std::string addAction(const ChatMessage& /*message*/, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {return "id";}
virtual void addSystemMessage(const ChatMessage& /*message*/, Direction /*direction*/) {}
virtual void addPresenceMessage(const ChatMessage& /*message*/, Direction /*direction*/) {}
virtual void addErrorMessage(const ChatMessage& /*message*/) {}
virtual void replaceMessage(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {}
virtual void replaceWithAction(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {}
virtual void replaceLastMessage(const ChatMessage& /*message*/, const TimestampBehaviour /*timestampBehaviour*/) {}
// File transfer related stuff
virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; }
virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { }
virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { }
virtual void setMessageReceiptState(const std::string &/* id */, ReceiptState /* state */) { }
virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}
virtual void setName(const std::string& name) {name_ = name;}
virtual void show() {}
+ virtual bool isVisible() const { return true; }
virtual void activate() {}
virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;}
virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;}
virtual void setUnreadMessageCount(int /*count*/) {}
virtual void convertToMUC(MUCType /*mucType*/) {}
virtual void setSecurityLabelsError() {}
virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() {return label_;}
virtual void setInputEnabled(bool /*enabled*/) {}
virtual void setRosterModel(Roster* roster) { roster_ = roster; }
Roster* getRosterModel() { return roster_; }
virtual void setTabComplete(TabComplete*) {}
void setAckState(const std::string& /*id*/, AckState /*state*/) {}
virtual void flash() {}
virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {}
virtual void cancelAlert() {}
virtual void setCorrectionEnabled(Tristate /*enabled*/) {}
virtual void setFileTransferEnabled(Tristate /*enabled*/) {}
void setAvailableOccupantActions(const std::vector<OccupantAction>&/* actions*/) {}
void setSubject(const std::string& /*subject*/) {}
virtual void showRoomConfigurationForm(Form::ref) {}
virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true, bool = false, bool = false) {}
virtual std::string addWhiteboardRequest(bool) {return "";}
virtual void setWhiteboardSessionStatus(std::string, const ChatWindow::WhiteboardSessionState){}
virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) {}
virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {}
virtual void setBlockingState(BlockingState) {}
virtual void setCanInitiateImpromptuChats(bool /*supportsImpromptu*/) {}
virtual void showBookmarkWindow(const MUCBookmark& /*bookmark*/) {}
std::string bodyFromMessage(const ChatMessage& message) {
boost::shared_ptr<ChatTextMessagePart> text;
foreach (boost::shared_ptr<ChatMessagePart> part, message.getParts()) {
if ((text = boost::dynamic_pointer_cast<ChatTextMessagePart>(part))) {
return text->text;
}
}
return "";
}
std::string name_;
std::string lastMessageBody_;
std::vector<SecurityLabelsCatalog::Item> labels_;
bool labelsEnabled_;
SecurityLabelsCatalog::Item label_;
Roster* roster_;
};
}
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 74ff109..47eeac0 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -1,806 +1,810 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/QtUI/QtChatWindow.h>
#include <boost/cstdint.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <qdebug.h>
#include <QApplication>
#include <QBoxLayout>
#include <QCloseEvent>
#include <QComboBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QMimeData>
#include <QPushButton>
#include <QSplitter>
#include <QString>
#include <QTextDocument>
#include <QTextEdit>
#include <QTime>
#include <QToolButton>
#include <QUrl>
#include <QMimeData>
#include <Swiften/Base/Log.h>
#include <Swift/Controllers/Roster/ContactRosterItem.h>
#include <Swift/Controllers/Roster/Roster.h>
#include <Swift/Controllers/Roster/RosterItem.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <SwifTools/TabComplete.h>
#include <Swift/QtUI/Roster/QtOccupantListWidget.h>
#include <Swift/QtUI/QtAddBookmarkWindow.h>
#include <Swift/QtUI/QtPlainChatView.h>
#include <Swift/QtUI/QtSettingsProvider.h>
#include <Swift/QtUI/QtScaledAvatarCache.h>
#include <Swift/QtUI/QtTextEdit.h>
#include <Swift/QtUI/QtUISettingConstants.h>
#include <Swift/QtUI/QtUtilities.h>
#include <Swift/QtUI/QtWebKitChatView.h>
namespace Swift {
QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) {
settings_ = settings;
unreadCount_ = 0;
inputEnabled_ = true;
completer_ = NULL;
affiliationEditor_ = NULL;
theme_ = theme;
isCorrection_ = false;
labelModel_ = NULL;
correctionEnabled_ = Maybe;
fileTransferEnabled_ = Maybe;
updateTitleWithUnreadCount();
#ifdef SWIFT_EXPERIMENTAL_FT
setAcceptDrops(true);
#endif
alertStyleSheet_ = "background: rgb(255, 255, 153); color: black";
QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(2);
alertWidget_ = new QWidget(this);
QHBoxLayout* alertLayout = new QHBoxLayout(alertWidget_);
layout->addWidget(alertWidget_);
alertLabel_ = new QLabel(this);
alertLayout->addWidget(alertLabel_);
alertButton_ = new QPushButton(this);
connect (alertButton_, SIGNAL(clicked()), this, SLOT(handleAlertButtonClicked()));
alertLayout->addWidget(alertButton_);
QPalette palette = alertWidget_->palette();
palette.setColor(QPalette::Window, QColor(Qt::yellow));
palette.setColor(QPalette::WindowText, QColor(Qt::black));
alertWidget_->setStyleSheet(alertStyleSheet_);
alertLabel_->setStyleSheet(alertStyleSheet_);
alertWidget_->hide();
subjectLayout_ = new QBoxLayout(QBoxLayout::LeftToRight);
subject_ = new QLineEdit(this);
subjectLayout_->addWidget(subject_);
setSubject("");
subject_->setReadOnly(true);
QPushButton* actionButton_ = new QPushButton(this);
actionButton_->setIcon(QIcon(":/icons/actions.png"));
connect(actionButton_, SIGNAL(clicked()), this, SLOT(handleActionButtonClicked()));
subject_->hide();
layout->addLayout(subjectLayout_);
logRosterSplitter_ = new QSplitter(this);
logRosterSplitter_->setAutoFillBackground(true);
layout->addWidget(logRosterSplitter_);
if (settings_->getSetting(QtUISettingConstants::USE_PLAIN_CHATS) || settings_->getSetting(QtUISettingConstants::USE_SCREENREADER)) {
messageLog_ = new QtPlainChatView(this, eventStream_);
}
else {
messageLog_ = new QtWebKitChatView(this, eventStream_, theme, this); // I accept that passing the ChatWindow in so that the view can call the signals is somewhat inelegant, but it saves a lot of boilerplate. This patch is unpleasant enough already. So let's fix this soon (it at least needs fixing by the time history is sorted), but not now.
}
logRosterSplitter_->addWidget(messageLog_);
treeWidget_ = new QtOccupantListWidget(eventStream_, settings_, QtTreeWidget::MessageDefaultJID, this);
treeWidget_->hide();
logRosterSplitter_->addWidget(treeWidget_);
logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(logRosterSplitter_, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(int, int)));
midBar_ = new QWidget(this);
//layout->addWidget(midBar);
midBar_->setAutoFillBackground(true);
QHBoxLayout *midBarLayout = new QHBoxLayout(midBar_);
midBarLayout->setContentsMargins(0,0,0,0);
midBarLayout->setSpacing(2);
//midBarLayout->addStretch();
labelsWidget_ = new QComboBox(this);
labelsWidget_->setFocusPolicy(Qt::NoFocus);
labelsWidget_->hide();
labelsWidget_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
midBarLayout->addWidget(labelsWidget_,0);
connect(labelsWidget_, SIGNAL(currentIndexChanged(int)), this, SLOT(handleCurrentLabelChanged(int)));
defaultLabelsPalette_ = labelsWidget_->palette();
QHBoxLayout* inputBarLayout = new QHBoxLayout();
inputBarLayout->setContentsMargins(0,0,0,0);
inputBarLayout->setSpacing(2);
input_ = new QtTextEdit(settings_, this);
input_->setAcceptRichText(false);
inputBarLayout->addWidget(midBar_);
inputBarLayout->addWidget(input_);
correctingLabel_ = new QLabel(tr("Correcting"), this);
inputBarLayout->addWidget(correctingLabel_);
correctingLabel_->hide();
// using an extra layout to work around Qt margin glitches on OS X
QHBoxLayout* actionLayout = new QHBoxLayout();
actionLayout->addWidget(actionButton_);
inputBarLayout->addLayout(actionLayout);
layout->addLayout(inputBarLayout);
inputClearing_ = false;
contactIsTyping_ = false;
tabCompletion_ = false;
connect(input_, SIGNAL(unhandledKeyPressEvent(QKeyEvent*)), this, SLOT(handleKeyPressEvent(QKeyEvent*)));
connect(input_, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
connect(input_, SIGNAL(textChanged()), this, SLOT(handleInputChanged()));
connect(input_, SIGNAL(cursorPositionChanged()), this, SLOT(handleCursorPositionChanged()));
setFocusProxy(input_);
logRosterSplitter_->setFocusProxy(input_);
midBar_->setFocusProxy(input_);
messageLog_->setFocusProxy(input_);
connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(qAppFocusChanged(QWidget*, QWidget*)));
connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus()));
resize(400,300);
connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int)));
connect(messageLog_, SIGNAL(logCleared()), this, SLOT(handleLogCleared()));
treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1));
treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2));
settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1));
messageLog_->showEmoticons(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS));
}
QtChatWindow::~QtChatWindow() {
if (mucConfigurationWindow_) {
delete mucConfigurationWindow_.data();
}
}
void QtChatWindow::handleSettingChanged(const std::string& setting) {
if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) {
bool showEmoticons = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS);
messageLog_->showEmoticons(showEmoticons);
}
}
void QtChatWindow::handleLogCleared() {
onLogCleared();
}
void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) {
onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item));
}
void QtChatWindow::handleFontResized(int fontSizeSteps) {
messageLog_->resizeFont(fontSizeSteps);
}
void QtChatWindow::handleAlertButtonClicked() {
onAlertButtonClicked();
}
void QtChatWindow::setAlert(const std::string& alertText, const std::string& buttonText) {
alertLabel_->setText(alertText.c_str());
if (buttonText.empty()) {
alertButton_->hide();
} else {
alertButton_->setText(buttonText.c_str());
alertButton_->show();
}
alertWidget_->show();
}
void QtChatWindow::cancelAlert() {
alertWidget_->hide();
}
void QtChatWindow::setTabComplete(TabComplete* completer) {
completer_ = completer;
}
void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) {
event->ignore();
if (event->isAccepted()) {
return;
}
event->accept();
int key = event->key();
if (key == Qt::Key_Tab) {
tabComplete();
} else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) {
beginCorrection();
} else if (key == Qt::Key_Down && isCorrection_ && input_->textCursor().atBlockEnd()) {
cancelCorrection();
} else if (key == Qt::Key_Down || key == Qt::Key_Up) {
/* Drop */
} else {
messageLog_->handleKeyPressEvent(event);
}
}
void QtChatWindow::beginCorrection() {
if (correctionEnabled_ == ChatWindow::Maybe) {
setAlert(Q2PSTRING(tr("This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message")));
} else if (correctionEnabled_ == ChatWindow::No) {
setAlert(Q2PSTRING(tr("This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message")));
}
QTextCursor cursor = input_->textCursor();
cursor.select(QTextCursor::Document);
cursor.beginEditBlock();
cursor.insertText(QString(lastSentMessage_));
cursor.endEditBlock();
isCorrection_ = true;
correctingLabel_->show();
input_->setStyleSheet(alertStyleSheet_);
labelsWidget_->setEnabled(false);
}
void QtChatWindow::cancelCorrection() {
cancelAlert();
QTextCursor cursor = input_->textCursor();
cursor.select(QTextCursor::Document);
cursor.removeSelectedText();
isCorrection_ = false;
correctingLabel_->hide();
input_->setStyleSheet(qApp->styleSheet());
labelsWidget_->setEnabled(true);
}
QByteArray QtChatWindow::getSplitterState() {
return logRosterSplitter_->saveState();
}
void QtChatWindow::handleChangeSplitterState(QByteArray state) {
logRosterSplitter_->restoreState(state);
}
void QtChatWindow::handleSplitterMoved(int, int) {
emit splitterMoved();
}
void QtChatWindow::tabComplete() {
if (!completer_) {
return;
}
QTextCursor cursor;
if (tabCompleteCursor_.hasSelection()) {
cursor = tabCompleteCursor_;
} else {
cursor = input_->textCursor();
while(cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor) && cursor.document()->characterAt(cursor.position() - 1) != ' ') { }
}
QString root = cursor.selectedText();
if (root.isEmpty()) {
return;
}
QString suggestion = P2QSTRING(completer_->completeWord(Q2PSTRING(root)));
if (root == suggestion) {
return;
}
tabCompletion_ = true;
cursor.beginEditBlock();
cursor.removeSelectedText();
int oldPosition = cursor.position();
cursor.insertText(suggestion);
tabCompleteCursor_ = cursor;
tabCompleteCursor_.setPosition(oldPosition, QTextCursor::KeepAnchor);
cursor.endEditBlock();
tabCompletion_ = false;
}
void QtChatWindow::setRosterModel(Roster* roster) {
treeWidget_->setRosterModel(roster);
}
void QtChatWindow::setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {
delete labelModel_;
labelModel_ = new LabelModel();
labelModel_->availableLabels_ = labels;
int i = 0;
int defaultIndex = 0;
labelsWidget_->setModel(labelModel_);
foreach (SecurityLabelsCatalog::Item label, labels) {
if (label.getIsDefault()) {
defaultIndex = i;
break;
}
i++;
}
labelsWidget_->setCurrentIndex(defaultIndex);
}
void QtChatWindow::handleCurrentLabelChanged(int index) {
if (static_cast<size_t>(index) >= labelModel_->availableLabels_.size()) {
qDebug() << "User selected a label that doesn't exist";
return;
}
const SecurityLabelsCatalog::Item& label = labelModel_->availableLabels_[index];
if (label.getLabel()) {
QPalette palette = labelsWidget_->palette();
//palette.setColor(QPalette::Base, P2QSTRING(label.getLabel()->getBackgroundColor()));
palette.setColor(labelsWidget_->backgroundRole(), P2QSTRING(label.getLabel()->getBackgroundColor()));
palette.setColor(labelsWidget_->foregroundRole(), P2QSTRING(label.getLabel()->getForegroundColor()));
labelsWidget_->setPalette(palette);
midBar_->setPalette(palette);
labelsWidget_->setAutoFillBackground(true);
}
else {
labelsWidget_->setAutoFillBackground(false);
labelsWidget_->setPalette(defaultLabelsPalette_);
midBar_->setPalette(defaultLabelsPalette_);
}
}
void QtChatWindow::setSecurityLabelsError() {
labelsWidget_->setEnabled(false);
}
void QtChatWindow::setSecurityLabelsEnabled(bool enabled) {
if (enabled) {
labelsWidget_->setEnabled(true);
labelsWidget_->show();
} else {
labelsWidget_->hide();
}
}
void QtChatWindow::setCorrectionEnabled(Tristate enabled) {
correctionEnabled_ = enabled;
}
void QtChatWindow::setFileTransferEnabled(Tristate enabled) {
fileTransferEnabled_ = enabled;
}
SecurityLabelsCatalog::Item QtChatWindow::getSelectedSecurityLabel() {
assert(labelsWidget_->isEnabled());
assert(labelsWidget_->currentIndex() >= 0 && static_cast<size_t>(labelsWidget_->currentIndex()) < labelModel_->availableLabels_.size());
return labelModel_->availableLabels_[labelsWidget_->currentIndex()];
}
void QtChatWindow::closeEvent(QCloseEvent* event) {
event->accept();
emit windowClosing();
onClosed();
}
void QtChatWindow::convertToMUC(MUCType mucType) {
impromptu_ = (mucType == ImpromptuMUC);
treeWidget_->setMessageTarget(impromptu_ ? QtTreeWidget::MessageDisplayJID : QtTreeWidget::MessageDefaultJID);
isMUC_ = true;
treeWidget_->show();
subject_->setVisible(!impromptu_);
}
void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) {
if (isWidgetSelected()) {
lastLineTracker_.setHasFocus(true);
input_->setFocus();
onAllMessagesRead();
}
else {
lastLineTracker_.setHasFocus(false);
}
}
void QtChatWindow::setInputEnabled(bool enabled) {
inputEnabled_ = enabled;
if (!enabled) {
if (mucConfigurationWindow_) {
delete mucConfigurationWindow_.data();
}
if (affiliationEditor_) {
delete affiliationEditor_.data();
}
}
}
void QtChatWindow::showEvent(QShowEvent* event) {
emit windowOpening();
QWidget::showEvent(event);
}
void QtChatWindow::setUnreadMessageCount(int count) {
if (unreadCount_ != count) {
unreadCount_ = count;
updateTitleWithUnreadCount();
emit countUpdated();
}
}
void QtChatWindow::setContactChatState(ChatState::ChatStateType state) {
contactIsTyping_ = (state == ChatState::Composing);
emit titleUpdated();
}
QtTabbable::AlertType QtChatWindow::getWidgetAlertState() {
if (contactIsTyping_) {
return ImpendingActivity;
}
if (unreadCount_ > 0) {
return WaitingActivity;
}
return NoActivity;
}
void QtChatWindow::setName(const std::string& name) {
contact_ = P2QSTRING(name);
updateTitleWithUnreadCount();
}
void QtChatWindow::updateTitleWithUnreadCount() {
if (isWindow()) {
setWindowTitle(unreadCount_ > 0 ? QString("(%1) %2").arg(unreadCount_).arg(contact_) : contact_);
} else {
setWindowTitle(contact_);
}
emit titleUpdated();
}
void QtChatWindow::flash() {
emit requestFlash();
}
int QtChatWindow::getCount() {
return unreadCount_;
}
void QtChatWindow::returnPressed() {
if (!inputEnabled_) {
return;
}
messageLog_->scrollToBottom();
lastSentMessage_ = QString(input_->toPlainText());
onSendMessageRequest(Q2PSTRING(input_->toPlainText()), isCorrection_);
inputClearing_ = true;
input_->clear();
cancelCorrection();
inputClearing_ = false;
}
void QtChatWindow::handleInputChanged() {
if (inputClearing_) {
return;
}
if (input_->toPlainText().isEmpty()) {
onUserCancelsTyping();
} else {
onUserTyping();
}
}
void QtChatWindow::handleCursorPositionChanged() {
if (tabCompletion_) {
return;
}
tabCompleteCursor_.clearSelection();
}
void QtChatWindow::show() {
if (parentWidget() == NULL) {
QWidget::show();
}
emit windowOpening();
}
+bool QtChatWindow::isVisible() const {
+ return QWidget::isVisible();
+}
+
void QtChatWindow::activate() {
if (isWindow()) {
QWidget::show();
}
emit wantsToActivate();
input_->setFocus();
}
void QtChatWindow::resizeEvent(QResizeEvent*) {
emit geometryChanged();
}
void QtChatWindow::moveEvent(QMoveEvent*) {
emit geometryChanged();
}
void QtChatWindow::dragEnterEvent(QDragEnterEvent *event) {
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) {
// TODO: check whether contact actually supports file transfer
if (!isMUC_) {
event->acceptProposedAction();
}
} else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-list")) {
if (isMUC_ || supportsImpromptuChat_) {
event->acceptProposedAction();
}
}
}
void QtChatWindow::dropEvent(QDropEvent *event) {
if (fileTransferEnabled_ == ChatWindow::Yes && event->mimeData()->hasUrls()) {
if (event->mimeData()->urls().size() == 1) {
onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile()));
} else {
std::string messageText(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time.")));
ChatMessage message;
message.append(boost::make_shared<ChatTextMessagePart>(messageText));
addSystemMessage(message, DefaultDirection);
}
} else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-list")) {
QByteArray dataBytes = event->mimeData()->data("application/vnd.swift.contact-jid-list");
QDataStream dataStream(&dataBytes, QIODevice::ReadOnly);
std::vector<JID> invites;
while (!dataStream.atEnd()) {
QString jidString;
dataStream >> jidString;
invites.push_back(Q2PSTRING(jidString));
}
onInviteToChat(invites);
}
}
void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {
treeWidget_->setAvailableOccupantActions(actions);
}
void QtChatWindow::setSubject(const std::string& subject) {
//subject_->setVisible(!subject.empty());
subject_->setText(P2QSTRING(subject));
subject_->setToolTip(P2QSTRING(subject));
subject_->setCursorPosition(0);
}
void QtChatWindow::handleActionButtonClicked() {
QMenu contextMenu;
QAction* changeSubject = NULL;
QAction* configure = NULL;
QAction* affiliations = NULL;
QAction* destroy = NULL;
QAction* invite = NULL;
QAction* block = NULL;
QAction* unblock = NULL;
if (availableRoomActions_.empty()) {
if (blockingState_ == IsBlocked) {
unblock = contextMenu.addAction(tr("Unblock"));
} else if (blockingState_ == IsUnblocked) {
block = contextMenu.addAction(tr("Block"));
}
if (supportsImpromptuChat_) {
invite = contextMenu.addAction(tr("Invite person to this chat…"));
}
} else {
foreach(ChatWindow::RoomAction availableAction, availableRoomActions_)
{
if (impromptu_) {
// hide options we don't need in impromptu chats
if (availableAction == ChatWindow::ChangeSubject ||
availableAction == ChatWindow::Configure ||
availableAction == ChatWindow::Affiliations ||
availableAction == ChatWindow::Destroy) {
continue;
}
}
switch(availableAction)
{
case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break;
case ChatWindow::Configure: configure = contextMenu.addAction(tr("Configure room…")); break;
case ChatWindow::Affiliations: affiliations = contextMenu.addAction(tr("Edit affiliations…")); break;
case ChatWindow::Destroy: destroy = contextMenu.addAction(tr("Destroy room")); break;
case ChatWindow::Invite: invite = contextMenu.addAction(tr("Invite person to this room…")); break;
}
}
}
QAction* bookmark = contextMenu.addAction(tr("Add boomark..."));
QAction* result = contextMenu.exec(QCursor::pos());
if (result == NULL) {
/* Skip processing. Note that otherwise, because the actions could be null they could match */
}
else if (result == changeSubject) {
bool ok;
QString subject = QInputDialog::getText(this, tr("Change room subject"), tr("New subject:"), QLineEdit::Normal, subject_->text(), &ok);
if (ok) {
onChangeSubjectRequest(Q2PSTRING(subject));
}
}
else if (result == configure) {
onConfigureRequest(Form::ref());
}
else if (result == affiliations) {
if (!affiliationEditor_) {
onGetAffiliationsRequest();
affiliationEditor_ = new QtAffiliationEditor(this);
connect(affiliationEditor_, SIGNAL(accepted()), this, SLOT(handleAffiliationEditorAccepted()));
}
affiliationEditor_->show();
}
else if (result == destroy) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Confirm room destruction"));
msgBox.setText(tr("Are you sure you want to destroy the room?"));
msgBox.setInformativeText(tr("This will destroy the room."));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
if (msgBox.exec() == QMessageBox::Yes) {
onDestroyRequest();
}
}
else if (result == invite) {
onInviteToChat(std::vector<JID>());
}
else if (result == block) {
onBlockUserRequest();
}
else if (result == unblock) {
onUnblockUserRequest();
} else if (result == bookmark) {
onBookmarkRequest();
}
}
void QtChatWindow::handleAffiliationEditorAccepted() {
onChangeAffiliationsRequest(affiliationEditor_->getChanges());
}
void QtChatWindow::setAffiliations(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) {
if (!affiliationEditor_) return;
affiliationEditor_->setAffiliations(affiliation, jids);
}
void QtChatWindow::setAvailableRoomActions(const std::vector<RoomAction>& actions) {
availableRoomActions_ = actions;
}
void QtChatWindow::setBlockingState(BlockingState state) {
blockingState_ = state;
}
void QtChatWindow::setCanInitiateImpromptuChats(bool supportsImpromptu) {
supportsImpromptuChat_ = supportsImpromptu;
}
void QtChatWindow::showBookmarkWindow(const MUCBookmark& bookmark) {
QtAddBookmarkWindow* window = new QtAddBookmarkWindow(eventStream_, bookmark);
window->show();
}
void QtChatWindow::showRoomConfigurationForm(Form::ref form) {
if (mucConfigurationWindow_) {
delete mucConfigurationWindow_.data();
}
mucConfigurationWindow_ = new QtMUCConfigurationWindow(form);
mucConfigurationWindow_->onFormComplete.connect(boost::bind(boost::ref(onConfigureRequest), _1));
mucConfigurationWindow_->onFormCancelled.connect(boost::bind(boost::ref(onConfigurationFormCancelled)));
}
void QtChatWindow::handleAppendedToLog() {
if (lastLineTracker_.getShouldMoveLastLine()) {
/* should this be queued? */
messageLog_->addLastSeenLine();
}
if (isWidgetSelected()) {
onAllMessagesRead();
}
}
void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) {
handleAppendedToLog();
messageLog_->addMUCInvitation(senderName, jid, reason, password, direct, isImpromptu, isContinuation);
}
std::string QtChatWindow::addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
handleAppendedToLog();
return messageLog_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time, highlight);
}
std::string QtChatWindow::addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
handleAppendedToLog();
return messageLog_->addAction(message, senderName, senderIsSelf, label, avatarPath, time, highlight);
}
void QtChatWindow::addSystemMessage(const ChatMessage& message, Direction direction) {
handleAppendedToLog();
messageLog_->addSystemMessage(message, direction);
}
void QtChatWindow::addPresenceMessage(const ChatMessage& message, Direction direction) {
handleAppendedToLog();
messageLog_->addPresenceMessage(message, direction);
}
void QtChatWindow::addErrorMessage(const ChatMessage& message) {
handleAppendedToLog();
messageLog_->addErrorMessage(message);
}
void QtChatWindow::replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
handleAppendedToLog();
messageLog_->replaceMessage(message, id, time, highlight);
}
void QtChatWindow::replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
handleAppendedToLog();
messageLog_->replaceWithAction(message, id, time, highlight);
}
std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) {
handleAppendedToLog();
return messageLog_->addFileTransfer(senderName, senderIsSelf, filename, sizeInBytes);
}
void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) {
messageLog_->setFileTransferProgress(id, percentageDone);
}
void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg) {
messageLog_->setFileTransferStatus(id, state, msg);
}
std::string QtChatWindow::addWhiteboardRequest(bool senderIsSelf) {
handleAppendedToLog();
return messageLog_->addWhiteboardRequest(contact_, senderIsSelf);
}
void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) {
messageLog_->setWhiteboardSessionStatus(id, state);
}
void QtChatWindow::replaceLastMessage(const ChatMessage& message, const TimestampBehaviour timestampBehaviour) {
messageLog_->replaceLastMessage(message, timestampBehaviour);
}
void QtChatWindow::setAckState(const std::string& id, AckState state) {
messageLog_->setAckState(id, state);
}
void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) {
messageLog_->setMessageReceiptState(id, state);
}
}
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index b8e3c6a..b3a3f5e 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -1,220 +1,221 @@
/*
* 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 <QPointer>
#include <QTextCursor>
#include <QMap>
#include <SwifTools/LastLineTracker.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/QtUI/ChatSnippet.h>
#include <Swift/QtUI/QtAffiliationEditor.h>
#include <Swift/QtUI/QtMUCConfigurationWindow.h>
#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/QtTabbable.h>
class QTextEdit;
class QLineEdit;
class QComboBox;
class QLabel;
class QSplitter;
class QPushButton;
namespace Swift {
class QtChatView;
class QtOccupantListWidget;
class QtChatTheme;
class TreeWidget;
class QtTextEdit;
class UIEventStream;
class QtChatWindowJSBridge;
class SettingsProvider;
class LabelModel : public QAbstractListModel {
Q_OBJECT
public:
LabelModel(QObject* parent = NULL) : QAbstractListModel(parent) {}
virtual int rowCount(const QModelIndex& /*index*/) const {
return static_cast<int>(availableLabels_.size());
}
virtual QVariant data(const QModelIndex& index, int role) const {
if (!index.isValid()) {
return QVariant();
}
boost::shared_ptr<SecurityLabel> label = availableLabels_[index.row()].getLabel();
if (label && role == Qt::TextColorRole) {
return P2QSTRING(label->getForegroundColor());
}
if (label && role == Qt::TextColorRole) {
return P2QSTRING(label->getBackgroundColor());
}
if (role == Qt::DisplayRole) {
std::string selector = availableLabels_[index.row()].getSelector();
std::string displayMarking = label ? label->getDisplayMarking() : "";
QString labelName = selector.empty() ? displayMarking.c_str() : selector.c_str();
return labelName;
}
return QVariant();
}
std::vector<SecurityLabelsCatalog::Item> availableLabels_;
};
class QtChatWindow : public QtTabbable, public ChatWindow {
Q_OBJECT
public:
QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings);
~QtChatWindow();
std::string addMessage(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
std::string addAction(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
void addSystemMessage(const ChatMessage& message, Direction direction);
void addPresenceMessage(const ChatMessage& message, Direction direction);
void addErrorMessage(const ChatMessage& message);
void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
// File transfer related stuff
std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes);
void setFileTransferProgress(std::string id, const int percentageDone);
void setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg);
std::string addWhiteboardRequest(bool senderIsSelf);
void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state);
void show();
+ bool isVisible() const;
void activate();
void setUnreadMessageCount(int count);
void convertToMUC(MUCType mucType);
// TreeWidget *getTreeWidget();
void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels);
void setSecurityLabelsEnabled(bool enabled);
void setSecurityLabelsError();
SecurityLabelsCatalog::Item getSelectedSecurityLabel();
void setName(const std::string& name);
void setInputEnabled(bool enabled);
QtTabbable::AlertType getWidgetAlertState();
void setContactChatState(ChatState::ChatStateType state);
void setRosterModel(Roster* roster);
void setTabComplete(TabComplete* completer);
int getCount();
void replaceLastMessage(const ChatMessage& message, const TimestampBehaviour timestampBehaviour);
void setAckState(const std::string& id, AckState state);
// message receipts
void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state);
void flash();
QByteArray getSplitterState();
virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions);
void setSubject(const std::string& subject);
void showRoomConfigurationForm(Form::ref);
void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false);
void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&);
void setAvailableRoomActions(const std::vector<RoomAction>& actions);
void setBlockingState(BlockingState state);
virtual void setCanInitiateImpromptuChats(bool supportsImpromptu);
virtual void showBookmarkWindow(const MUCBookmark& bookmark);
public slots:
void handleChangeSplitterState(QByteArray state);
void handleFontResized(int fontSizeSteps);
void setAlert(const std::string& alertText, const std::string& buttonText = "");
void cancelAlert();
void setCorrectionEnabled(Tristate enabled);
void setFileTransferEnabled(Tristate enabled);
signals:
void geometryChanged();
void splitterMoved();
void fontResized(int);
protected slots:
void qAppFocusChanged(QWidget* old, QWidget* now);
void closeEvent(QCloseEvent* event);
void resizeEvent(QResizeEvent* event);
void moveEvent(QMoveEvent* event);
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent *event);
protected:
void showEvent(QShowEvent* event);
private slots:
void handleLogCleared();
void returnPressed();
void handleInputChanged();
void handleCursorPositionChanged();
void handleKeyPressEvent(QKeyEvent* event);
void handleSplitterMoved(int pos, int index);
void handleAlertButtonClicked();
void handleActionButtonClicked();
void handleAffiliationEditorAccepted();
void handleCurrentLabelChanged(int);
private:
void updateTitleWithUnreadCount();
void tabComplete();
void beginCorrection();
void cancelCorrection();
void handleSettingChanged(const std::string& setting);
void handleOccupantSelectionChanged(RosterItem* item);
void handleAppendedToLog();
int unreadCount_;
bool contactIsTyping_;
LastLineTracker lastLineTracker_;
QString contact_;
QString lastSentMessage_;
QTextCursor tabCompleteCursor_;
QtChatView* messageLog_;
QtChatTheme* theme_;
QtTextEdit* input_;
QWidget* midBar_;
QBoxLayout* subjectLayout_;
QComboBox* labelsWidget_;
QtOccupantListWidget* treeWidget_;
QLabel* correctingLabel_;
QLabel* alertLabel_;
QWidget* alertWidget_;
QPushButton* alertButton_;
TabComplete* completer_;
QLineEdit* subject_;
bool isCorrection_;
bool inputClearing_;
bool tabCompletion_;
UIEventStream* eventStream_;
bool inputEnabled_;
QSplitter *logRosterSplitter_;
Tristate correctionEnabled_;
Tristate fileTransferEnabled_;
QString alertStyleSheet_;
QPointer<QtMUCConfigurationWindow> mucConfigurationWindow_;
QPointer<QtAffiliationEditor> affiliationEditor_;
SettingsProvider* settings_;
std::vector<ChatWindow::RoomAction> availableRoomActions_;
QPalette defaultLabelsPalette_;
LabelModel* labelModel_;
BlockingState blockingState_;
bool impromptu_;
bool isMUC_;
bool supportsImpromptuChat_;
};
}