diff options
| -rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 37 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatController.h | 6 | ||||
| -rw-r--r-- | Swift/Controllers/UIInterfaces/ChatWindow.h | 11 | ||||
| -rw-r--r-- | Swift/Controllers/UnitTest/MockChatWindow.h | 4 | ||||
| -rw-r--r-- | Swift/QtUI/QtChatWindow.cpp | 59 | ||||
| -rw-r--r-- | Swift/QtUI/QtChatWindow.h | 10 |
6 files changed, 70 insertions, 57 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 9b65c9f..786b4e9 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,519 +1,510 @@ /* * 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)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); 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.")); + deliveryReceiptAlert_ = chatWindow_->addAlert(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.")); + deliveryReceiptAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.")); } else { - chatWindow_->cancelAlert(); + if (deliveryReceiptAlert_) { + chatWindow_->removeAlert(*deliveryReceiptAlert_); + deliveryReceiptAlert_.reset(); + } } } 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.")); + blockedContactAlert_ = chatWindow_->addAlert(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 { + if (blockedContactAlert_) { + chatWindow_->removeAlert(*blockedContactAlert_); + blockedContactAlert_.reset(); + } + chatWindow_->setInputEnabled(true); 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 2e92be7..998b437 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 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_; + + boost::optional<ChatWindow::AlertID> deliveryReceiptAlert_; + boost::optional<ChatWindow::AlertID> blockedContactAlert_; }; } diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index 81d26c8..f2f5f76 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -1,214 +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 <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; + + /** + * A handle that uniquely identities an alert message. + */ + typedef int AlertID; /** * 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). + * @return A handle to the alert message. */ - virtual void setAlert(const std::string& alertText, const std::string& buttonText = "") = 0; + virtual AlertID addAlert(const std::string& alertText, const std::string& buttonText = "") = 0; /** * Removes an alert. + * @param id An alert ID previously returned from setAlert */ - virtual void cancelAlert() = 0; + virtual void removeAlert(const AlertID id) = 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 5e6606b..ef7216b 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -1,96 +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 AlertID addAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) { return 0; } + virtual void removeAlert(const AlertID /*id*/) {} 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 47eeac0..574a0a2 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -1,810 +1,823 @@ /* * 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) { +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), nextAlertId_(0), 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(); + alertLayout_ = new QVBoxLayout(this); + layout->addLayout(alertLayout_); 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()); +QtChatWindow::AlertID QtChatWindow::addAlert(const std::string& alertText, const std::string& buttonText) { + QWidget* alertWidget = new QWidget(this); + QHBoxLayout* alertLayout = new QHBoxLayout(alertWidget); + alertLayout_->addWidget(alertWidget); + QLabel* 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_); + + alertLabel->setText(alertText.c_str()); if (buttonText.empty()) { alertButton_->hide(); } else { alertButton_->setText(buttonText.c_str()); alertButton_->show(); } - alertWidget_->show(); + + AlertID id = nextAlertId_++; + alertWidgets_[id] = alertWidget; + return id; } -void QtChatWindow::cancelAlert() { - alertWidget_->hide(); +void QtChatWindow::removeAlert(const AlertID id) { + std::map<AlertID, QWidget*>::iterator i = alertWidgets_.find(id); + if (i != alertWidgets_.end()) { + alertLayout_->removeWidget(i->second); + delete i->second; + alertWidgets_.erase(i); + } } 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"))); + correctingAlert_ = addAlert(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"))); + correctingAlert_ = addAlert(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(); + if (correctingAlert_) { + removeAlert(*correctingAlert_); + correctingAlert_.reset(); + } 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 b3a3f5e..45eaa6f 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -1,221 +1,223 @@ /* * 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(); + AlertID addAlert(const std::string& alertText, const std::string& buttonText = ""); + void removeAlert(const AlertID id); 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_; + boost::optional<AlertID> correctingAlert_; + QVBoxLayout* alertLayout_; + std::map<AlertID, QWidget*> alertWidgets_; + AlertID nextAlertId_; 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_; }; } |
Swift