summaryrefslogtreecommitdiffstats
path: root/Swift
diff options
context:
space:
mode:
Diffstat (limited to 'Swift')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp4
-rw-r--r--Swift/Controllers/Chat/ChatController.h4
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp12
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h8
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.cpp75
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.h11
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp9
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h2
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp2
-rw-r--r--Swift/Controllers/Chat/MUCController.h2
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp134
-rw-r--r--Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp7
-rw-r--r--Swift/Controllers/HighlightAction.h31
-rw-r--r--Swift/Controllers/HighlightEditorController.cpp34
-rw-r--r--Swift/Controllers/HighlightEditorController.h20
-rw-r--r--Swift/Controllers/HighlightManager.cpp92
-rw-r--r--Swift/Controllers/HighlightManager.h32
-rw-r--r--Swift/Controllers/HighlightRule.cpp64
-rw-r--r--Swift/Controllers/HighlightRule.h30
-rw-r--r--Swift/Controllers/Highlighter.cpp14
-rw-r--r--Swift/Controllers/MainController.cpp9
-rw-r--r--Swift/Controllers/SConscript1
-rw-r--r--Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp19
-rw-r--r--Swift/Controllers/UIInterfaces/HighlightEditorWindow.h27
-rw-r--r--Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h24
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h6
-rw-r--r--Swift/Controllers/UnitTest/HighlightRuleTest.cpp38
-rw-r--r--Swift/QtUI/QtHighlightEditor.cpp524
-rw-r--r--Swift/QtUI/QtHighlightEditor.h69
-rw-r--r--Swift/QtUI/QtHighlightEditor.ui446
-rw-r--r--Swift/QtUI/QtHighlightRuleWidget.cpp134
-rw-r--r--Swift/QtUI/QtHighlightRuleWidget.h49
-rw-r--r--Swift/QtUI/QtHighlightRuleWidget.ui260
-rw-r--r--Swift/QtUI/QtUIFactory.cpp6
-rw-r--r--Swift/QtUI/QtUIFactory.h2
-rw-r--r--Swift/QtUI/QtWebKitChatView.cpp33
-rw-r--r--Swift/QtUI/QtWebKitChatView.h5
-rw-r--r--Swift/QtUI/SConscript7
38 files changed, 1571 insertions, 675 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 2367761..9df7708 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -1,514 +1,514 @@
/*
* 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, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider)
+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));
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::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_, boost::posix_time::microsec_clock::universal_time(), HighlightAction());
+ 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 f8b6d8b..8b1bb9a 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -1,113 +1,113 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
+ * 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, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
+ 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 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 23137dc..5363e0c 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,369 +1,369 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
+ * 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, 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) {
+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();
}
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), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
+ 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, const boost::posix_time::ptime& time, const HighlightAction& 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), id, time, highlight);
+ 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], timeStamp, highlight);
+ 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 7db94a4..cf0a4d2 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -1,134 +1,134 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
+ * 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();
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, 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;
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, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
+ 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_;
- ChatMessageParser* chatMessageParser_;
+ boost::shared_ptr<ChatMessageParser> chatMessageParser_;
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
UIEventStream* eventStream_;
};
}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp
index 698b766..09d93ac 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.cpp
+++ b/Swift/Controllers/Chat/ChatMessageParser.cpp
@@ -1,127 +1,194 @@
/*
* Copyright (c) 2013-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <vector>
#include <utility>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/algorithm/string.hpp>
#include <Swiften/Base/Regex.h>
#include <Swiften/Base/foreach.h>
#include <SwifTools/Linkify.h>
namespace Swift {
- ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons) : emoticons_(emoticons) {
-
+ ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode)
+ : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) {
}
typedef std::pair<std::string, std::string> StringPair;
- ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body) {
+ ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, bool senderIsSelf) {
ChatWindow::ChatMessage parsedMessage;
std::string remaining = body;
/* Parse one, URLs */
while (!remaining.empty()) {
bool found = false;
std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining);
remaining = "";
for (size_t i = 0; i < links.first.size(); i++) {
const std::string& part = links.first[i];
if (found) {
// Must be on the last part, then
remaining = part;
}
else {
if (i == links.second) {
found = true;
parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part));
}
else {
parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part));
}
}
}
}
-
+ /* do emoticon substitution */
+ parsedMessage = emoticonHighlight(parsedMessage);
+
+ if (!senderIsSelf) { /* do not highlight our own messsages */
+ /* do word-based color highlighting */
+ parsedMessage = splitHighlight(parsedMessage);
+ }
+
+ return parsedMessage;
+ }
+
+ ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message)
+ {
+ ChatWindow::ChatMessage parsedMessage = message;
std::string regexString;
/* Parse two, emoticons */
foreach (StringPair emoticon, emoticons_) {
/* Construct a regexp that finds an instance of any of the emoticons inside a group
* at the start or end of the line, or beside whitespace.
*/
regexString += regexString.empty() ? "" : "|";
std::string escaped = "(" + Regex::escape(emoticon.first) + ")";
regexString += "^" + escaped + "|";
regexString += escaped + "$|";
regexString += "\\s" + escaped + "|";
regexString += escaped + "\\s";
}
if (!regexString.empty()) {
regexString += "";
boost::regex emoticonRegex(regexString);
ChatWindow::ChatMessage newMessage;
foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
try {
boost::match_results<std::string::const_iterator> match;
const std::string& text = textPart->text;
std::string::const_iterator start = text.begin();
while (regex_search(start, text.end(), match, emoticonRegex)) {
int matchIndex = 0;
for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) {
if (match[matchIndex].length() > 0) {
//This is the matching subgroup
break;
}
}
std::string::const_iterator matchStart = match[matchIndex].first;
std::string::const_iterator matchEnd = match[matchIndex].second;
if (start != matchStart) {
/* If we're skipping over plain text since the previous emoticon, record it as plain text */
newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
}
boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>();
std::string matchString = match[matchIndex].str();
std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString);
assert (emoticonIterator != emoticons_.end());
const StringPair& emoticon = *emoticonIterator;
emoticonPart->imagePath = emoticon.second;
emoticonPart->alternativeText = emoticon.first;
newMessage.append(emoticonPart);
start = matchEnd;
}
if (start != text.end()) {
/* If there's plain text after the last emoticon, record it */
newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
}
}
catch (std::runtime_error) {
/* Basically too expensive to compute the regex results and it gave up, so pass through as text */
newMessage.append(part);
}
}
else {
newMessage.append(part);
}
}
parsedMessage = newMessage;
}
return parsedMessage;
}
+
+ ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message)
+ {
+ ChatWindow::ChatMessage parsedMessage = message;
+
+ for (size_t i = 0; i < highlightRules_->getSize(); ++i) {
+ const HighlightRule& rule = highlightRules_->getRule(i);
+ if (rule.getMatchMUC() && !mucMode_) {
+ continue; /* this rule only applies to MUC's, and this is a CHAT */
+ } else if (rule.getMatchChat() && mucMode_) {
+ continue; /* this rule only applies to CHAT's, and this is a MUC */
+ }
+ foreach(const boost::regex &regex, rule.getKeywordRegex()) {
+ ChatWindow::ChatMessage newMessage;
+ foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
+ boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+ if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
+ try {
+ boost::match_results<std::string::const_iterator> match;
+ const std::string& text = textPart->text;
+ std::string::const_iterator start = text.begin();
+ while (regex_search(start, text.end(), match, regex)) {
+ std::string::const_iterator matchStart = match[0].first;
+ std::string::const_iterator matchEnd = match[0].second;
+ if (start != matchStart) {
+ /* If we're skipping over plain text since the previous emoticon, record it as plain text */
+ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
+ }
+ boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>();
+ highlightPart->text = match.str();
+ highlightPart->foregroundColor = rule.getAction().getTextColor();
+ highlightPart->backgroundColor = rule.getAction().getTextBackground();
+ newMessage.append(highlightPart);
+ start = matchEnd;
+ }
+ if (start != text.end()) {
+ /* If there's plain text after the last emoticon, record it */
+ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
+ }
+ }
+ catch (std::runtime_error) {
+ /* Basically too expensive to compute the regex results and it gave up, so pass through as text */
+ newMessage.append(part);
+ }
+ } else {
+ newMessage.append(part);
+ }
+ }
+ parsedMessage = newMessage;
+ }
+ }
+
+ return parsedMessage;
+ }
}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h
index c9b9456..cff4ffa 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.h
+++ b/Swift/Controllers/Chat/ChatMessageParser.h
@@ -1,23 +1,26 @@
/*
- * Copyright (c) 2013 Kevin Smith
+ * Copyright (c) 2013-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <string>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
class ChatMessageParser {
public:
- ChatMessageParser(const std::map<std::string, std::string>& emoticons);
- ChatWindow::ChatMessage parseMessageBody(const std::string& body);
+ ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false);
+ ChatWindow::ChatMessage parseMessageBody(const std::string& body, bool senderIsSelf = false);
private:
+ ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage);
+ ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage);
std::map<std::string, std::string> emoticons_;
-
+ HighlightRulesListPtr highlightRules_;
+ bool mucMode_;
};
}
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 1698b4a..8a077d1 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,959 +1,960 @@
/*
* 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;
- chatMessageParser_ = new ChatMessageParser(emoticons);
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 chatMessageParser_;
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 = "";
}
}
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);
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) {
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);
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);
} else {
ChatController* controller = getChatControllerIfExists(jid, false);
if (controller) {
unreadCount = controller->getUnreadCount();
}
JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare();
Presence::ref presence = presenceOracle_->getHighestPriorityPresence(bareishJID);
StatusShow::Type type = presence ? presence->getShow() : StatusShow::None;
boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path();
return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false);
}
}
void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) {
if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) {
/* Don't include PMs in MUC rooms.*/
return;
}
ChatListWindow::Chat chat = createChatListChatItem(jid, activity);
/* FIXME: handle nick changes */
appendRecent(chat);
handleUnreadCountChanged(NULL);
saveRecents();
}
void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) {
int unreadTotal = 0;
bool controllerIsMUC = dynamic_cast<MUCController*>(controller);
bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare());
foreach (ChatListWindow::Chat& chatItem, recentChats_) {
bool match = false;
if (controller) {
/* Matching MUC item */
match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
/* Matching PM */
match |= isPM && chatItem.jid == controller->getToJID();
/* Matching non-PM */
match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
}
if (match) {
chatItem.setUnreadCount(controller->getUnreadCount());
}
unreadTotal += chatItem.unreadCount;
}
chatListWindow_->setRecents(recentChats_);
chatListWindow_->setUnreadCount(unreadTotal);
}
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::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;
}
}
}
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());
- 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_);
+ 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->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);
}
- 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_);
+ 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 88a0986..41435d9 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -1,186 +1,186 @@
/*
* 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);
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 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 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_;
- ChatMessageParser* chatMessageParser_;
JID localMUCServiceJID_;
boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_;
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
UserSearchController* inviteUserSearchController_;
IDGenerator idGenerator_;
VCardManager* vcardManager_;
};
}
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index d09bc3d..b467227 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -1,617 +1,617 @@
/*
* 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/MUCController.h>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/foreach.h>
#include <Swiften/Base/foreach.h>
#include <Swiften/Base/format.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Elements/Delay.h>
#include <Swiften/MUC/MUC.h>
#include <Swiften/Network/Timer.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Roster/XMPPRoster.h>
#include <SwifTools/TabComplete.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <Swift/Controllers/Highlighter.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/Roster/ContactRosterItem.h>
#include <Swift/Controllers/Roster/GroupRosterItem.h>
#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h>
#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h>
#include <Swift/Controllers/Roster/ItemOperations/SetMUC.h>
#include <Swift/Controllers/Roster/Roster.h>
#include <Swift/Controllers/Roster/RosterVCardProvider.h>
#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
namespace Swift {
/**
* The controller does not gain ownership of the stanzaChannel, nor the factory.
*/
MUCController::MUCController (
const JID& self,
MUC::ref muc,
const boost::optional<std::string>& password,
const std::string &nick,
StanzaChannel* stanzaChannel,
IQRouter* iqRouter,
ChatWindowFactory* chatWindowFactory,
PresenceOracle* presenceOracle,
AvatarManager* avatarManager,
UIEventStream* uiEventStream,
bool useDelayForLatency,
TimerFactory* timerFactory,
EventController* eventController,
EntityCapsProvider* entityCapsProvider,
XMPPRoster* roster,
HistoryController* historyController,
MUCRegistry* mucRegistry,
HighlightManager* highlightManager,
- ChatMessageParser* chatMessageParser,
+ boost::shared_ptr<ChatMessageParser> chatMessageParser,
bool isImpromptu,
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,
VCardManager* vcardManager) :
ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) {
parting_ = true;
joined_ = false;
lastWasPresence_ = false;
shouldJoinOnReconnect_ = true;
doneGettingHistory_ = false;
events_ = uiEventStream;
xmppRoster_ = roster;
roster_ = new Roster(false, true);
rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource);
completer_ = new TabComplete();
chatWindow_->setRosterModel(roster_);
chatWindow_->setTabComplete(completer_);
chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1));
chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2));
chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1));
chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this));
chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1));
chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this));
chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this));
chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1));
chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this));
chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1));
muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1));
muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3));
muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2));
muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1));
muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1));
highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode);
highlighter_->setNick(nick_);
if (timerFactory) {
loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
loginCheckTimer_->start();
}
if (isImpromptu) {
muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this));
chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC);
} else {
muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3));
muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3));
chatWindow_->convertToMUC(ChatWindow::StandardMUC);
chatWindow_->setName(muc->getJID().getNode());
}
setOnline(true);
if (avatarManager_ != NULL) {
avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
}
handleBareJIDCapsChanged(muc->getJID());
eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1));
}
MUCController::~MUCController() {
eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1));
chatWindow_->setRosterModel(NULL);
delete rosterVCardProvider_;
delete roster_;
if (loginCheckTimer_) {
loginCheckTimer_->stop();
}
chatWindow_->setTabComplete(NULL);
delete completer_;
}
void MUCController::cancelReplaces() {
lastWasPresence_ = false;
}
void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item) {
std::vector<ChatWindow::OccupantAction> actions;
if (item) {
MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation();
MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole();
if (role == MUCOccupant::Moderator && !isImpromptu_)
{
if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) {
actions.push_back(ChatWindow::Ban);
}
actions.push_back(ChatWindow::Kick);
actions.push_back(ChatWindow::MakeModerator);
actions.push_back(ChatWindow::MakeParticipant);
actions.push_back(ChatWindow::MakeVisitor);
}
// Add contact is available only if the real JID is also available
if (muc_->getOccupant(item->getJID().getResource()).getRealJID()) {
actions.push_back(ChatWindow::AddContact);
}
actions.push_back(ChatWindow::ShowProfile);
}
chatWindow_->setAvailableOccupantActions(actions);
}
void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction action, ContactRosterItem* item) {
JID mucJID = item->getJID();
MUCOccupant occupant = muc_->getOccupant(mucJID.getResource());
JID realJID;
if (occupant.getRealJID()) {
realJID = occupant.getRealJID().get();
}
switch (action) {
case ChatWindow::Kick: muc_->kickOccupant(mucJID);break;
case ChatWindow::Ban: muc_->changeAffiliation(realJID, MUCOccupant::Outcast);break;
case ChatWindow::MakeModerator: muc_->changeOccupantRole(mucJID, MUCOccupant::Moderator);break;
case ChatWindow::MakeParticipant: muc_->changeOccupantRole(mucJID, MUCOccupant::Participant);break;
case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break;
case ChatWindow::AddContact: if (occupant.getRealJID()) events_->send(boost::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break;
case ChatWindow::ShowProfile: events_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break;
}
}
void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) {
ChatWindow::Tristate support = ChatWindow::Yes;
bool any = false;
foreach (const std::string& nick, currentOccupants_) {
DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick);
if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
any = true;
} else {
support = ChatWindow::Maybe;
}
}
if (!any) {
support = ChatWindow::No;
}
chatWindow_->setCorrectionEnabled(support);
}
/**
* Join the MUC if not already in it.
*/
void MUCController::rejoin() {
if (parting_) {
joined_ = false;
parting_ = false;
if (password_) {
muc_->setPassword(*password_);
}
//FIXME: check for received activity
#ifdef SWIFT_EXPERIMENTAL_HISTORY
if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) {
lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_);
}
#endif
if (lastActivity_ == boost::posix_time::not_a_date_time) {
muc_->joinAs(nick_);
}
else {
muc_->joinWithContextSince(nick_, lastActivity_);
}
}
}
bool MUCController::isJoined() {
return joined_;
}
const std::string& MUCController::getNick() {
return nick_;
}
const boost::optional<std::string> MUCController::getPassword() const {
return password_;
}
bool MUCController::isImpromptu() const {
return isImpromptu_;
}
std::map<std::string, JID> MUCController::getParticipantJIDs() const {
std::map<std::string, JID> participants;
typedef std::pair<std::string, MUCOccupant> MUCOccupantPair;
std::map<std::string, MUCOccupant> occupants = muc_->getOccupants();
foreach(const MUCOccupantPair& occupant, occupants) {
if (occupant.first != nick_) {
participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID();
}
}
return participants;
}
void MUCController::sendInvites(const std::vector<JID>& jids, const std::string& reason) const {
foreach (const JID& jid, jids) {
muc_->invitePerson(jid, reason, isImpromptu_);
}
}
void MUCController::handleJoinTimeoutTick() {
receivedActivity();
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);
}
void MUCController::receivedActivity() {
if (loginCheckTimer_) {
loginCheckTimer_->stop();
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wswitch-enum"
void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
receivedActivity();
std::string errorMessage = QT_TRANSLATE_NOOP("", "Unable to enter this room");
std::string rejoinNick;
if (error) {
switch (error->getCondition()) {
case ErrorPayload::Conflict:
rejoinNick = nick_ + "_";
errorMessage = str(format(QT_TRANSLATE_NOOP("", "Unable to enter this room as %1%, retrying as %2%")) % nick_ % rejoinNick);
break;
case ErrorPayload::JIDMalformed:
errorMessage += ": ";
errorMessage += QT_TRANSLATE_NOOP("", "No nickname specified");
break;
case ErrorPayload::NotAuthorized:
errorMessage += ": ";
errorMessage += QT_TRANSLATE_NOOP("", "The correct room password is needed");
break;
case ErrorPayload::RegistrationRequired:
errorMessage += ": ";
errorMessage += QT_TRANSLATE_NOOP("", "Only members may enter");
break;
case ErrorPayload::Forbidden:
errorMessage += ": ";
errorMessage += QT_TRANSLATE_NOOP("", "You are banned from the room");
break;
case ErrorPayload::ServiceUnavailable:
errorMessage += ": ";
errorMessage += QT_TRANSLATE_NOOP("", "The room is full");
break;
case ErrorPayload::ItemNotFound:
errorMessage += ": ";
errorMessage += QT_TRANSLATE_NOOP("", "The room does not exist");
break;
default: break;
}
}
errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage);
chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
parting_ = true;
if (!rejoinNick.empty() && renameCounter_ < 10) {
renameCounter_++;
setNick(rejoinNick);
rejoin();
}
}
#pragma clang diagnostic pop
void MUCController::handleJoinComplete(const std::string& nick) {
receivedActivity();
renameCounter_ = 0;
joined_ = true;
std::string joinMessage;
if (isImpromptu_) {
joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have joined the chat as %1%.")) % nick);
} else {
joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
}
setNick(nick);
chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::UpdateTimestamp);
#ifdef SWIFT_EXPERIMENTAL_HISTORY
addRecentLogs();
#endif
clearPresenceQueue();
shouldJoinOnReconnect_ = true;
setEnabled(true);
if (isImpromptu_) {
setAvailableRoomActions(MUCOccupant::NoAffiliation, MUCOccupant::Participant);
} else {
MUCOccupant occupant = muc_->getOccupant(nick);
setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
}
onUserJoined();
if (isImpromptu_) {
setImpromptuWindowTitle();
}
}
void MUCController::handleAvatarChanged(const JID& jid) {
if (parting_ || !jid.equals(toJID_, JID::WithoutResource)) {
return;
}
roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource));
}
void MUCController::handleWindowClosed() {
parting_ = true;
shouldJoinOnReconnect_ = false;
muc_->part();
onUserLeft();
}
void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
if (nick_ != occupant.getNick()) {
completer_->addWord(occupant.getNick());
}
receivedActivity();
JID jid(nickToJID(occupant.getNick()));
JID realJID;
if (occupant.getRealJID()) {
realJID = occupant.getRealJID().get();
}
currentOccupants_.insert(occupant.getNick());
NickJoinPart event(occupant.getNick(), Join);
appendToJoinParts(joinParts_, event);
MUCOccupant::Role role = MUCOccupant::Participant;
MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation;
if (!isImpromptu_) {
role = occupant.getRole();
affiliation = occupant.getAffiliation();
}
std::string groupName(roleToGroupName(role));
roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid));
roster_->applyOnItems(SetMUC(jid, role, affiliation));
roster_->getGroup(groupName)->setManualSort(roleToSortName(role));
if (joined_) {
std::string joinString;
if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) {
joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
}
else {
joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
}
if (shouldUpdateJoinParts()) {
updateJoinParts();
} else {
addPresenceMessage(joinString);
}
if (isImpromptu_) {
setImpromptuWindowTitle();
onActivity("");
}
}
if (avatarManager_ != NULL) {
handleAvatarChanged(jid);
}
}
void MUCController::addPresenceMessage(const std::string& message) {
lastWasPresence_ = true;
chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection);
}
void MUCController::setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role)
{
std::vector<ChatWindow::RoomAction> actions;
if (role <= MUCOccupant::Participant) {
actions.push_back(ChatWindow::ChangeSubject);
}
if (affiliation == MUCOccupant::Owner) {
actions.push_back(ChatWindow::Configure);
}
if (affiliation <= MUCOccupant::Admin) {
actions.push_back(ChatWindow::Affiliations);
}
if (affiliation == MUCOccupant::Owner) {
actions.push_back(ChatWindow::Destroy);
}
if (role <= MUCOccupant::Visitor) {
actions.push_back(ChatWindow::Invite);
}
chatWindow_->setAvailableRoomActions(actions);
}
void MUCController::clearPresenceQueue() {
lastWasPresence_ = false;
joinParts_.clear();
}
std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) {
switch (role) {
case MUCOccupant::Moderator: return QT_TRANSLATE_NOOP("", "moderator");
case MUCOccupant::Participant: return QT_TRANSLATE_NOOP("", "participant");
case MUCOccupant::Visitor: return QT_TRANSLATE_NOOP("", "visitor");
case MUCOccupant::NoRole: return "";
}
assert(false);
return "";
}
std::string MUCController::roleToSortName(MUCOccupant::Role role) {
switch (role) {
case MUCOccupant::Moderator: return "1";
case MUCOccupant::Participant: return "2";
case MUCOccupant::Visitor: return "3";
case MUCOccupant::NoRole: return "4";
}
assert(false);
return "5";
}
JID MUCController::nickToJID(const std::string& nick) {
return JID(toJID_.getNode(), toJID_.getDomain(), nick);
}
bool MUCController::messageTargetsMe(boost::shared_ptr<Message> message) {
std::string stringRegexp(".*\\b" + boost::to_lower_copy(nick_) + "\\b.*");
boost::regex myRegexp(stringRegexp);
return boost::regex_match(boost::to_lower_copy(message->getBody()), myRegexp);
}
void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
if (messageEvent->getStanza()->getType() == Message::Groupchat) {
lastActivity_ = boost::posix_time::microsec_clock::universal_time();
}
clearPresenceQueue();
boost::shared_ptr<Message> message = messageEvent->getStanza();
if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable()) {
chatWindow_->flash();
}
else {
messageEvent->setTargetsMe(false);
}
if (messageEvent->isReadable() && isImpromptu_) {
chatWindow_->flash(); /* behave like a regular char*/
}
if (joined_) {
std::string nick = message->getFrom().getResource();
if (nick != nick_ && currentOccupants_.find(nick) != currentOccupants_.end()) {
completer_->addWord(nick);
}
}
/*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/
receivedActivity();
joined_ = true;
if (message->hasSubject() && message->getBody().empty()) {
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);;
chatWindow_->setSubject(message->getSubject());
doneGettingHistory_ = true;
}
if (!doneGettingHistory_ && !message->getPayload<Delay>()) {
doneGettingHistory_ = true;
}
if (!doneGettingHistory_) {
checkDuplicates(message);
messageEvent->conclude();
}
}
void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) {
boost::shared_ptr<Message> message = messageEvent->getStanza();
if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) {
if (messageTargetsMe(message) || isImpromptu_) {
eventController_->handleIncomingEvent(messageEvent);
if (!messageEvent->getConcluded()) {
highlighter_->handleHighlightAction(highlight);
}
}
}
}
void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant, const MUCOccupant::Role& oldRole) {
clearPresenceQueue();
receivedActivity();
JID jid(nickToJID(nick));
roster_->removeContactFromGroup(jid, roleToGroupName(oldRole));
JID realJID;
if (occupant.getRealJID()) {
realJID = occupant.getRealJID().get();
}
std::string group(roleToGroupName(occupant.getRole()));
roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid));
roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation()));
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
if (nick == nick_) {
setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
}
}
void MUCController::handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Affiliation& /*oldAffiliation*/)
{
if (nick == nick_) {
setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole());
}
JID jid(nickToJID(nick));
MUCOccupant occupant = muc_->getOccupant(nick);
roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation));
}
std::string MUCController::roleToGroupName(MUCOccupant::Role role) {
std::string result;
switch (role) {
case MUCOccupant::Moderator: result = QT_TRANSLATE_NOOP("", "Moderators"); break;
case MUCOccupant::Participant: result = QT_TRANSLATE_NOOP("", "Participants"); break;
case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break;
case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break;
}
return result;
}
void MUCController::setOnline(bool online) {
ChatControllerBase::setOnline(online);
if (!online) {
muc_->part();
parting_ = true;
processUserPart();
} else {
if (shouldJoinOnReconnect_) {
renameCounter_ = 0;
if (isImpromptu_) {
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to join chat")), ChatWindow::DefaultDirection);
} else {
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
}
if (loginCheckTimer_) {
loginCheckTimer_->start();
}
setNick(desiredNick_);
rejoin();
}
}
}
void MUCController::processUserPart() {
roster_->removeAll();
/* handleUserLeft won't throw a part back up unless this is called
when it doesn't yet know we've left - which only happens on
disconnect, so call with disconnect here so if the signal does
bubble back up, it'll be with the right type.*/
muc_->handleUserLeft(MUC::Disconnect);
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index feffaba..b5b5837 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -1,156 +1,156 @@
/*
* 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 <set>
#include <string>
#include <map>
#include <boost/shared_ptr.hpp>
#include <boost/signals/connection.hpp>
#include <Swiften/Base/boost_bsignals.h>
#include <Swiften/Network/Timer.h>
#include <Swiften/Elements/Message.h>
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/JID/JID.h>
#include <Swiften/MUC/MUC.h>
#include <Swiften/Elements/MUCOccupant.h>
#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <Swift/Controllers/Roster/RosterItem.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
class StanzaChannel;
class IQRouter;
class ChatWindowFactory;
class Roster;
class AvatarManager;
class UIEventStream;
class TimerFactory;
class TabComplete;
class XMPPRoster;
class HighlightManager;
class UIEvent;
class VCardManager;
class RosterVCardProvider;
enum JoinPart {Join, Part, JoinThenPart, PartThenJoin};
struct NickJoinPart {
NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {}
std::string nick;
JoinPart type;
};
class MUCController : public ChatControllerBase {
public:
- MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager);
+ MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager);
virtual ~MUCController();
boost::signal<void ()> onUserLeft;
boost::signal<void ()> onUserJoined;
boost::signal<void ()> onImpromptuConfigCompleted;
virtual void setOnline(bool online);
void rejoin();
static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent);
static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu);
static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts);
bool isJoined();
const std::string& getNick();
const boost::optional<std::string> getPassword() const;
bool isImpromptu() const;
std::map<std::string, JID> getParticipantJIDs() const;
void sendInvites(const std::vector<JID>& jids, const std::string& reason) const;
protected:
void preSendMessageRequest(boost::shared_ptr<Message> message);
bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
std::string senderDisplayNameFromMessage(const JID& from);
boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message> message) const;
void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>);
void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&);
void cancelReplaces();
void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming);
private:
void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role);
void clearPresenceQueue();
void addPresenceMessage(const std::string& message);
void handleWindowOccupantSelectionChanged(ContactRosterItem* item);
void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item);
void handleWindowClosed();
void handleAvatarChanged(const JID& jid);
void handleOccupantJoined(const MUCOccupant& occupant);
void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason);
void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence);
void handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant,const MUCOccupant::Role& oldRole);
void handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation,const MUCOccupant::Affiliation& oldAffiliation);
void handleJoinComplete(const std::string& nick);
void handleJoinFailed(boost::shared_ptr<ErrorPayload> error);
void handleJoinTimeoutTick();
void handleChangeSubjectRequest(const std::string&);
void handleBookmarkRequest();
std::string roleToGroupName(MUCOccupant::Role role);
std::string roleToSortName(MUCOccupant::Role role);
JID nickToJID(const std::string& nick);
std::string roleToFriendlyName(MUCOccupant::Role role);
void receivedActivity();
bool messageTargetsMe(boost::shared_ptr<Message> message);
void updateJoinParts();
bool shouldUpdateJoinParts();
void dayTicked() {clearPresenceQueue();}
void processUserPart();
void handleBareJIDCapsChanged(const JID& jid);
void handleConfigureRequest(Form::ref);
void handleConfigurationFailed(ErrorPayload::ref);
void handleConfigurationFormReceived(Form::ref);
void handleDestroyRoomRequest();
void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite);
void handleConfigurationCancelled();
void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role);
void handleGetAffiliationsRequest();
void handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids);
void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes);
void handleInviteToMUCWindowDismissed();
void handleInviteToMUCWindowCompleted();
void handleUIEvent(boost::shared_ptr<UIEvent> event);
void addRecentLogs();
void checkDuplicates(boost::shared_ptr<Message> newMessage);
void setNick(const std::string& nick);
void setImpromptuWindowTitle();
void handleRoomUnlocked();
void configureAsImpromptuRoom(Form::ref form);
Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm);
private:
MUC::ref muc_;
UIEventStream* events_;
std::string nick_;
std::string desiredNick_;
Roster* roster_;
TabComplete* completer_;
bool parting_;
bool joined_;
bool lastWasPresence_;
bool shouldJoinOnReconnect_;
bool doneGettingHistory_;
boost::bsignals::scoped_connection avatarChangedConnection_;
boost::shared_ptr<Timer> loginCheckTimer_;
std::set<std::string> currentOccupants_;
std::vector<NickJoinPart> joinParts_;
boost::posix_time::ptime lastActivity_;
boost::optional<std::string> password_;
XMPPRoster* xmppRoster_;
std::vector<HistoryMessage> joinContext_;
size_t renameCounter_;
bool isImpromptu_;
bool isImpromptuAlreadyConfigured_;
RosterVCardProvider* rosterVCardProvider_;
};
}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
index 0a14303..5dca63a 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
@@ -1,125 +1,231 @@
/*
* Copyright (c) 2013-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <hippomocks.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
using namespace Swift;
class ChatMessageParserTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(ChatMessageParserTest);
CPPUNIT_TEST(testFullBody);
CPPUNIT_TEST(testOneEmoticon);
CPPUNIT_TEST(testBareEmoticon);
CPPUNIT_TEST(testHiddenEmoticon);
CPPUNIT_TEST(testEndlineEmoticon);
CPPUNIT_TEST(testBoundedEmoticons);
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
smile1_ = ":)";
smile1Path_ = "/blah/smile1.png";
smile2_ = ":(";
smile2Path_ = "/blah/smile2.jpg";
emoticons_[smile1_] = smile1Path_;
emoticons_[smile2_] = smile2Path_;
}
void tearDown() {
emoticons_.clear();
}
void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]);
CPPUNIT_ASSERT_EQUAL(text, part->text);
}
void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) {
boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]);
CPPUNIT_ASSERT(!!part);
CPPUNIT_ASSERT_EQUAL(text, part->alternativeText);
CPPUNIT_ASSERT_EQUAL(path, part->imagePath);
}
+ void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+ boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]);
+ CPPUNIT_ASSERT_EQUAL(text, part->text);
+ }
+
void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]);
CPPUNIT_ASSERT_EQUAL(text, part->target);
}
+ static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord)
+ {
+ HighlightRule rule;
+ std::vector<std::string> keywords;
+ keywords.push_back(keyword);
+ rule.setKeywords(keywords);
+ rule.setMatchCase(matchCase);
+ rule.setMatchWholeWords(matchWholeWord);
+ rule.setMatchChat(true);
+ return rule;
+ }
+
+ static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord)
+ {
+ boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>();
+ list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord));
+ return list;
+ }
+
+ static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2)
+ {
+ boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>();
+ list->addRule(rule1);
+ list->addRule(rule2);
+ return list;
+ }
+
void testFullBody() {
- ChatMessageParser testling(emoticons_);
- ChatWindow::ChatMessage result = testling.parseMessageBody(":) shiny :( :) http://wonderland.lit/blah http://denmark.lit boom boom");
+ const std::string no_special_message = "a message with no special content";
+ ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
+ ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message);
+ assertText(result, 0, no_special_message);
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
+ result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom");
assertEmoticon(result, 0, smile1_, smile1Path_);
assertText(result, 1, " shiny ");
assertEmoticon(result, 2, smile2_, smile2Path_);
assertText(result, 3, " ");
- assertEmoticon(result, 4, smile1_, smile1Path_);
+ assertHighlight(result, 4, "trigger");
assertText(result, 5, " ");
- assertURL(result, 6, "http://wonderland.lit/blah");
+ assertEmoticon(result, 6, smile1_, smile1Path_);
assertText(result, 7, " ");
- assertURL(result, 8, "http://denmark.lit");
- assertText(result, 9, " boom boom");
+ assertURL(result, 8, "http://wonderland.lit/blah");
+ assertText(result, 9, " ");
+ assertURL(result, 10, "http://denmark.lit");
+ assertText(result, 11, " boom boom");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
+ result = testling.parseMessageBody("testtriggermessage");
+ assertText(result, 0, "test");
+ assertHighlight(result, 1, "trigger");
+ assertText(result, 2, "message");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true));
+ result = testling.parseMessageBody("testtriggermessage");
+ assertText(result, 0, "testtriggermessage");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false));
+ result = testling.parseMessageBody("TrIgGeR");
+ assertText(result, 0, "TrIgGeR");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
+ result = testling.parseMessageBody("TrIgGeR");
+ assertHighlight(result, 0, "TrIgGeR");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
+ result = testling.parseMessageBody("partialTrIgGeRmatch");
+ assertText(result, 0, "partial");
+ assertHighlight(result, 1, "TrIgGeR");
+ assertText(result, 2, "match");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false)));
+ result = testling.parseMessageBody("zero one two three");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "one");
+ assertText(result, 2, " two ");
+ assertHighlight(result, 3, "three");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false)));
+ result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "oNe");
+ assertText(result, 2, " two ");
+ assertHighlight(result, 3, "tHrEe");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false)));
+ result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "oNe");
+ assertText(result, 2, " two tHrEe");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false)));
+ result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero oNe two tHrEe");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false)));
+ result = testling.parseMessageBody("zeroonetwothree");
+ assertText(result, 0, "zero");
+ assertHighlight(result, 1, "one");
+ assertText(result, 2, "two");
+ assertHighlight(result, 3, "three");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false)));
+ result = testling.parseMessageBody("zeroOnEtwoThReE");
+ assertText(result, 0, "zeroOnEtwo");
+ assertHighlight(result, 1, "ThReE");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false)));
+ result = testling.parseMessageBody("zeroonetwothree");
+ assertText(result, 0, "zeroonetwo");
+ assertHighlight(result, 1, "three");
+
+ testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true)));
+ result = testling.parseMessageBody("zeroonetwothree");
+ assertText(result, 0, "zeroonetwothree");
}
void testOneEmoticon() {
- ChatMessageParser testling(emoticons_);
+ ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody(" :) ");
assertText(result, 0, " ");
assertEmoticon(result, 1, smile1_, smile1Path_);
assertText(result, 2, " ");
}
void testBareEmoticon() {
- ChatMessageParser testling(emoticons_);
+ ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody(":)");
assertEmoticon(result, 0, smile1_, smile1Path_);
}
void testHiddenEmoticon() {
- ChatMessageParser testling(emoticons_);
+ ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a");
assertText(result, 0, "b:)a");
}
void testEndlineEmoticon() {
- ChatMessageParser testling(emoticons_);
+ ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)");
assertText(result, 0, "Lazy");
assertEmoticon(result, 1, smile1_, smile1Path_);
}
void testBoundedEmoticons() {
- ChatMessageParser testling(emoticons_);
+ ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:(");
assertEmoticon(result, 0, smile1_, smile1Path_);
assertText(result, 1, "Lazy");
assertEmoticon(result, 2, smile2_, smile2Path_);
}
void testEmoticonParenthesis() {
- ChatMessageParser testling(emoticons_);
+ ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))");
assertText(result, 0, "(Like this ");
assertEmoticon(result, 1, smile1_, smile1Path_);
assertText(result, 2, ")");
}
-
private:
std::map<std::string, std::string> emoticons_;
std::string smile1_;
std::string smile1Path_;
std::string smile2_;
std::string smile2Path_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest);
-
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 7268878..bb22e43 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -1,439 +1,438 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
+ * Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <boost/algorithm/string.hpp>
#include <hippomocks.h>
#include "Swiften/Base/foreach.h"
#include "Swift/Controllers/XMPPEvents/EventController.h"
#include "Swiften/Presence/DirectedPresenceSender.h"
#include "Swiften/Presence/StanzaChannelPresenceSender.h"
#include "Swiften/Avatars/NullAvatarManager.h"
#include "Swift/Controllers/Chat/MUCController.h"
#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
#include "Swiften/Client/NickResolver.h"
#include "Swiften/Roster/XMPPRoster.h"
#include "Swift/Controllers/UIEvents/UIEventStream.h"
#include "Swift/Controllers/UnitTest/MockChatWindow.h"
#include "Swiften/MUC/UnitTest/MockMUC.h"
#include "Swiften/Client/DummyStanzaChannel.h"
#include "Swiften/Queries/DummyIQChannel.h"
#include "Swiften/Presence/PresenceOracle.h"
#include "Swiften/Network/TimerFactory.h"
#include "Swiften/Elements/MUCUserPayload.h"
#include "Swiften/Disco/DummyEntityCapsProvider.h"
#include <Swiften/VCards/VCardMemoryStorage.h>
#include <Swiften/Crypto/PlatformCryptoProvider.h>
#include <Swiften/VCards/VCardManager.h>
#include <Swift/Controllers/Settings/DummySettingsProvider.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <Swift/Controllers/Chat/UserSearchController.h>
#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
#include <Swift/Controllers/Roster/Roster.h>
#include <Swift/Controllers/Roster/GroupRosterItem.h>
#include <Swiften/Crypto/CryptoProvider.h>
using namespace Swift;
class MUCControllerTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(MUCControllerTest);
CPPUNIT_TEST(testJoinPartStringContructionSimple);
CPPUNIT_TEST(testJoinPartStringContructionMixed);
CPPUNIT_TEST(testAppendToJoinParts);
CPPUNIT_TEST(testAddressedToSelf);
CPPUNIT_TEST(testNotAddressedToSelf);
CPPUNIT_TEST(testAddressedToSelfBySelf);
CPPUNIT_TEST(testMessageWithEmptyLabelItem);
CPPUNIT_TEST(testMessageWithLabelItem);
CPPUNIT_TEST(testCorrectMessageWithLabelItem);
CPPUNIT_TEST(testRoleAffiliationStates);
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create());
self_ = JID("girl@wonderland.lit/rabbithole");
nick_ = "aLiCe";
mucJID_ = JID("teaparty@rooms.wonderland.lit");
mocks_ = new MockRepository();
stanzaChannel_ = new DummyStanzaChannel();
iqChannel_ = new DummyIQChannel();
iqRouter_ = new IQRouter(iqChannel_);
eventController_ = new EventController();
chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>();
presenceOracle_ = new PresenceOracle(stanzaChannel_);
presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
uiEventStream_ = new UIEventStream();
avatarManager_ = new NullAvatarManager();
TimerFactory* timerFactory = NULL;
window_ = new MockChatWindow();
mucRegistry_ = new MUCRegistry();
entityCapsProvider_ = new DummyEntityCapsProvider();
settings_ = new DummySettingsProvider();
highlightManager_ = new HighlightManager(settings_);
muc_ = boost::make_shared<MockMUC>(mucJID_);
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
- chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>());
+ chatMessageParser_ = boost::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true);
vcardStorage_ = new VCardMemoryStorage(crypto_.get());
vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL, vcardManager_);
}
void tearDown() {
delete controller_;
delete vcardManager_;
delete vcardStorage_;
delete highlightManager_;
delete settings_;
delete entityCapsProvider_;
delete eventController_;
delete presenceOracle_;
delete mocks_;
delete uiEventStream_;
delete stanzaChannel_;
delete presenceSender_;
delete directedPresenceSender_;
delete iqRouter_;
delete iqChannel_;
delete mucRegistry_;
delete avatarManager_;
- delete chatMessageParser_;
}
void finishJoin() {
Presence::ref presence(new Presence());
presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
MUCUserPayload::ref status(new MUCUserPayload());
MUCUserPayload::StatusCode code;
code.code = 110;
status->addStatusCode(code);
presence->addPayload(status);
stanzaChannel_->onPresenceReceived(presence);
}
void testAddressedToSelf() {
finishJoin();
Message::ref message(new Message());
message = Message::ref(new Message());
message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
message->setBody("basic " + nick_ + " test.");
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)1, eventController_->getEvents().size());
message = Message::ref(new Message());
message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
message->setBody(nick_ + ": hi there");
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)2, eventController_->getEvents().size());
message->setFrom(JID(muc_->getJID().toString() + "/other"));
message->setBody("Hi there " + nick_);
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)3, eventController_->getEvents().size());
message = Message::ref(new Message());
message->setFrom(JID(muc_->getJID().toString() + "/other2"));
message->setBody("Hi " + boost::to_lower_copy(nick_) + ".");
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());
message = Message::ref(new Message());
message->setFrom(JID(muc_->getJID().toString() + "/other3"));
message->setBody("Hi bert.");
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());
message = Message::ref(new Message());
message->setFrom(JID(muc_->getJID().toString() + "/other2"));
message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie.");
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());
}
void testNotAddressedToSelf() {
finishJoin();
Message::ref message(new Message());
message->setFrom(JID(muc_->getJID().toString() + "/other3"));
message->setBody("Hi there Hatter");
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size());
}
void testAddressedToSelfBySelf() {
finishJoin();
Message::ref message(new Message());
message->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
message->setBody("Hi there " + nick_);
message->setType(Message::Groupchat);
controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size());
}
void testMessageWithEmptyLabelItem() {
SecurityLabelsCatalog::Item label;
label.setSelector("Bob");
window_->label_ = label;
boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>();
features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
controller_->setAvailableServerFeatures(features);
IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>();
labelPayload->addItem(label);
IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
iqChannel_->onIQReceived(result);
std::string messageBody("agamemnon");
window_->onSendMessageRequest(messageBody, false);
boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza);
CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
CPPUNIT_ASSERT(window_->labelsEnabled_);
CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
CPPUNIT_ASSERT(message);
CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
CPPUNIT_ASSERT(!message->getPayload<SecurityLabel>());
}
void testMessageWithLabelItem() {
boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>();
label->setLabel("a");
SecurityLabelsCatalog::Item labelItem;
labelItem.setSelector("Bob");
labelItem.setLabel(label);
window_->label_ = labelItem;
boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>();
features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
controller_->setAvailableServerFeatures(features);
IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>();
labelPayload->addItem(labelItem);
IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
iqChannel_->onIQReceived(result);
std::string messageBody("agamemnon");
window_->onSendMessageRequest(messageBody, false);
boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza);
CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
CPPUNIT_ASSERT(window_->labelsEnabled_);
CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
CPPUNIT_ASSERT(message);
CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
}
void testCorrectMessageWithLabelItem() {
boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>();
label->setLabel("a");
SecurityLabelsCatalog::Item labelItem;
labelItem.setSelector("Bob");
labelItem.setLabel(label);
boost::shared_ptr<SecurityLabel> label2 = boost::make_shared<SecurityLabel>();
label->setLabel("b");
SecurityLabelsCatalog::Item labelItem2;
labelItem2.setSelector("Charlie");
labelItem2.setLabel(label2);
window_->label_ = labelItem;
boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>();
features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
controller_->setAvailableServerFeatures(features);
IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>();
labelPayload->addItem(labelItem);
IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
iqChannel_->onIQReceived(result);
std::string messageBody("agamemnon");
window_->onSendMessageRequest(messageBody, false);
boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza);
CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
CPPUNIT_ASSERT(window_->labelsEnabled_);
CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
CPPUNIT_ASSERT(message);
CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
window_->label_ = labelItem2;
window_->onSendMessageRequest(messageBody, true);
rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
message = boost::dynamic_pointer_cast<Message>(rawStanza);
CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody());
CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
}
void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) {
CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size());
for (size_t i = 0; i < expected.size(); i++) {
CPPUNIT_ASSERT_EQUAL(expected[i].nick, actual[i].nick);
CPPUNIT_ASSERT_EQUAL(expected[i].type, actual[i].type);
}
}
void testAppendToJoinParts() {
std::vector<NickJoinPart> list;
std::vector<NickJoinPart> gold;
MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
gold.push_back(NickJoinPart("Kev", Join));
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join));
gold.push_back(NickJoinPart("Remko", Join));
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join));
gold.push_back(NickJoinPart("Bert", Join));
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part));
gold[2].type = JoinThenPart;
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part));
gold[0].type = JoinThenPart;
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part));
gold[1].type = JoinThenPart;
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
gold.push_back(NickJoinPart("Ernie", Part));
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join));
gold[3].type = PartThenJoin;
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
gold[0].type = Join;
checkEqual(gold, list);
MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
gold[3].type = Part;
checkEqual(gold, list);
}
void testJoinPartStringContructionSimple() {
std::vector<NickJoinPart> list;
list.push_back(NickJoinPart("Kev", Join));
CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false));
list.push_back(NickJoinPart("Remko", Part));
CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
list.push_back(NickJoinPart("Bert", Join));
CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
list.push_back(NickJoinPart("Ernie", Join));
CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
}
void testJoinPartStringContructionMixed() {
std::vector<NickJoinPart> list;
list.push_back(NickJoinPart("Kev", JoinThenPart));
CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
list.push_back(NickJoinPart("Remko", Part));
CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
list.push_back(NickJoinPart("Bert", PartThenJoin));
CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
list.push_back(NickJoinPart("Ernie", JoinThenPart));
CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
}
JID jidFromOccupant(const MUCOccupant& occupant) {
return JID(mucJID_.toString()+"/"+occupant.getNick());
}
void testRoleAffiliationStates() {
typedef std::map<std::string, MUCOccupant> occupant_map;
occupant_map occupants;
occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner)));
occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner)));
occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner)));
occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner)));
/* populate the MUC with fake users */
typedef const std::pair<std::string,MUCOccupant> occupantIterator;
foreach(occupantIterator &occupant, occupants) {
muc_->insertOccupant(occupant.second);
}
std::vector<MUCOccupant> alterations;
alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin));
alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member));
alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast));
alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member));
alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner));
alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast));
alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation));
alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation));
alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast));
foreach(const MUCOccupant& alteration, alterations) {
/* perform an alteration to a user's role and affiliation */
occupant_map::iterator occupant = occupants.find(alteration.getNick());
CPPUNIT_ASSERT(occupant != occupants.end());
const JID jid = jidFromOccupant(occupant->second);
/* change the affiliation, leave the role in place */
muc_->changeAffiliation(jid, alteration.getAffiliation());
occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation());
testRoleAffiliationStatesVerify(occupants);
/* change the role, leave the affiliation in place */
muc_->changeOccupantRole(jid, alteration.getRole());
occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation());
testRoleAffiliationStatesVerify(occupants);
}
}
void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) {
/* verify that the roster is in sync */
GroupRosterItem* group = window_->getRosterModel()->getRoot();
foreach(RosterItem* rosterItem, group->getChildren()) {
GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem);
CPPUNIT_ASSERT(child);
foreach(RosterItem* childItem, child->getChildren()) {
ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem);
CPPUNIT_ASSERT(item);
std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource());
CPPUNIT_ASSERT(occupant != occupants.end());
CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole());
CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation());
}
}
}
private:
JID self_;
JID mucJID_;
MockMUC::ref muc_;
std::string nick_;
DummyStanzaChannel* stanzaChannel_;
DummyIQChannel* iqChannel_;
IQRouter* iqRouter_;
EventController* eventController_;
ChatWindowFactory* chatWindowFactory_;
UserSearchWindowFactory* userSearchWindowFactory_;
MUCController* controller_;
// NickResolver* nickResolver_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
StanzaChannelPresenceSender* presenceSender_;
DirectedPresenceSender* directedPresenceSender_;
MockRepository* mocks_;
UIEventStream* uiEventStream_;
MockChatWindow* window_;
MUCRegistry* mucRegistry_;
DummyEntityCapsProvider* entityCapsProvider_;
DummySettingsProvider* settings_;
HighlightManager* highlightManager_;
- ChatMessageParser* chatMessageParser_;
+ boost::shared_ptr<ChatMessageParser> chatMessageParser_;
boost::shared_ptr<CryptoProvider> crypto_;
VCardManager* vcardManager_;
VCardMemoryStorage* vcardStorage_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h
index bfbed74..de1f201 100644
--- a/Swift/Controllers/HighlightAction.h
+++ b/Swift/Controllers/HighlightAction.h
@@ -1,45 +1,76 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#pragma once
#include <string>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/archive/text_iarchive.hpp>
+
namespace Swift {
class HighlightRule;
class HighlightAction {
public:
HighlightAction() : highlightText_(false), playSound_(false) {}
bool highlightText() const { return highlightText_; }
void setHighlightText(bool highlightText);
+ /**
+ * Gets the foreground highlight color. If the string is empty, assume a default color.
+ */
const std::string& getTextColor() const { return textColor_; }
void setTextColor(const std::string& textColor) { textColor_ = textColor; }
+ /**
+ * Gets the background highlight color. If the string is empty, assume a default color.
+ */
const std::string& getTextBackground() const { return textBackground_; }
void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; }
bool playSound() const { return playSound_; }
void setPlaySound(bool playSound);
+ /**
+ * Gets the sound filename. If the string is empty, assume a default sound file.
+ */
const std::string& getSoundFile() const { return soundFile_; }
void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; }
bool isEmpty() const { return !highlightText_ && !playSound_; }
private:
+ friend class boost::serialization::access;
+ template<class Archive> void serialize(Archive & ar, const unsigned int version);
+
bool highlightText_;
std::string textColor_;
std::string textBackground_;
bool playSound_;
std::string soundFile_;
};
+ template<class Archive>
+ void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/)
+ {
+ ar & highlightText_;
+ ar & textColor_;
+ ar & textBackground_;
+ ar & playSound_;
+ ar & soundFile_;
+ }
+
}
diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/HighlightEditorController.cpp
index 899e4bb..38007f0 100644
--- a/Swift/Controllers/HighlightEditorController.cpp
+++ b/Swift/Controllers/HighlightEditorController.cpp
@@ -1,40 +1,56 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#include <boost/bind.hpp>
#include <Swift/Controllers/HighlightEditorController.h>
-#include <Swift/Controllers/UIInterfaces/HighlightEditorWidget.h>
-#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h>
#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h>
+#include <Swift/Controllers/ContactSuggester.h>
namespace Swift {
-HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager) : highlightEditorWidgetFactory_(highlightEditorWidgetFactory), highlightEditorWidget_(NULL), highlightManager_(highlightManager)
+HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager)
+: highlightEditorWindowFactory_(highlightEditorWindowFactory), highlightEditorWindow_(NULL), highlightManager_(highlightManager), contactSuggester_(0)
{
uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1));
}
HighlightEditorController::~HighlightEditorController()
{
- delete highlightEditorWidget_;
- highlightEditorWidget_ = NULL;
+ delete highlightEditorWindow_;
+ highlightEditorWindow_ = NULL;
}
void HighlightEditorController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent)
{
boost::shared_ptr<RequestHighlightEditorUIEvent> event = boost::dynamic_pointer_cast<RequestHighlightEditorUIEvent>(rawEvent);
if (event) {
- if (!highlightEditorWidget_) {
- highlightEditorWidget_ = highlightEditorWidgetFactory_->createHighlightEditorWidget();
- highlightEditorWidget_->setHighlightManager(highlightManager_);
+ if (!highlightEditorWindow_) {
+ highlightEditorWindow_ = highlightEditorWindowFactory_->createHighlightEditorWindow();
+ highlightEditorWindow_->setHighlightManager(highlightManager_);
+ highlightEditorWindow_->onContactSuggestionsRequested.connect(boost::bind(&HighlightEditorController::handleContactSuggestionsRequested, this, _1));
}
- highlightEditorWidget_->show();
+ highlightEditorWindow_->show();
+ }
+}
+
+void HighlightEditorController::handleContactSuggestionsRequested(const std::string& text)
+{
+ if (contactSuggester_) {
+ highlightEditorWindow_->setContactSuggestions(contactSuggester_->getSuggestions(text));
}
}
}
diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/HighlightEditorController.h
index 3868251..54322e2 100644
--- a/Swift/Controllers/HighlightEditorController.h
+++ b/Swift/Controllers/HighlightEditorController.h
@@ -1,38 +1,48 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * 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/UIEvents/UIEvent.h>
namespace Swift {
class UIEventStream;
- class HighlightEditorWidgetFactory;
- class HighlightEditorWidget;
+ class HighlightEditorWindowFactory;
+ class HighlightEditorWindow;
class HighlightManager;
+ class ContactSuggester;
class HighlightEditorController {
public:
- HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager);
+ HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager);
~HighlightEditorController();
HighlightManager* getHighlightManager() const { return highlightManager_; }
+ void setContactSuggester(ContactSuggester *suggester) { contactSuggester_ = suggester; }
private:
void handleUIEvent(boost::shared_ptr<UIEvent> event);
+ void handleContactSuggestionsRequested(const std::string& text);
private:
- HighlightEditorWidgetFactory* highlightEditorWidgetFactory_;
- HighlightEditorWidget* highlightEditorWidget_;
+ HighlightEditorWindowFactory* highlightEditorWindowFactory_;
+ HighlightEditorWindow* highlightEditorWindow_;
HighlightManager* highlightManager_;
+ ContactSuggester* contactSuggester_;
};
}
diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp
index 7ab578e..eac562f 100644
--- a/Swift/Controllers/HighlightManager.cpp
+++ b/Swift/Controllers/HighlightManager.cpp
@@ -1,139 +1,131 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#include <cassert>
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <boost/bind.hpp>
#include <boost/numeric/conversion/cast.hpp>
+#include <boost/serialization/vector.hpp>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/archive/text_iarchive.hpp>
#include <Swiften/Base/foreach.h>
#include <Swift/Controllers/HighlightManager.h>
#include <Swift/Controllers/Highlighter.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/SettingConstants.h>
/* How does highlighting work?
*
* HighlightManager manages a list of if-then rules used to highlight messages.
* Rule is represented by HighlightRule. Action ("then" part) is HighlightAction.
*
*
* HighlightManager is also used as a factory for Highlighter objects.
* Each ChatControllerBase has its own Highlighter.
* Highligher may be customized by using setNick(), etc.
*
* ChatControllerBase passes incoming messages to Highlighter and gets HighlightAction in return
* (first matching rule is returned).
* If needed, HighlightAction is then passed back to Highlighter for further handling.
* This results in HighlightManager emiting onHighlight event,
* which is handled by SoundController to play sound notification
*/
namespace Swift {
HighlightManager::HighlightManager(SettingsProvider* settings)
: settings_(settings)
, storingSettings_(false)
{
+ rules_ = boost::make_shared<HighlightRulesList>();
loadSettings();
settings_->onSettingChanged.connect(boost::bind(&HighlightManager::handleSettingChanged, this, _1));
}
void HighlightManager::handleSettingChanged(const std::string& settingPath)
{
if (!storingSettings_ && SettingConstants::HIGHLIGHT_RULES.getKey() == settingPath) {
loadSettings();
}
}
-void HighlightManager::loadSettings()
-{
- std::string highlightRules = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES);
- if (highlightRules == "@") {
- rules_ = getDefaultRules();
- } else {
- rules_ = rulesFromString(highlightRules);
- }
-}
-
std::string HighlightManager::rulesToString() const
{
- std::string s;
- foreach (HighlightRule r, rules_) {
- s += r.toString() + '\f';
- }
- if (s.size()) {
- s.erase(s.end() - 1);
- }
- return s;
-}
-
-std::vector<HighlightRule> HighlightManager::rulesFromString(const std::string& rulesString)
-{
- std::vector<HighlightRule> rules;
- std::string s(rulesString);
- typedef boost::split_iterator<std::string::iterator> split_iterator;
- for (split_iterator it = boost::make_split_iterator(s, boost::first_finder("\f")); it != split_iterator(); ++it) {
- HighlightRule r = HighlightRule::fromString(boost::copy_range<std::string>(*it));
- if (!r.isEmpty()) {
- rules.push_back(r);
- }
- }
- return rules;
+ std::stringstream stream;
+ boost::archive::text_oarchive archive(stream);
+ archive << rules_->list_;
+ return stream.str();
}
std::vector<HighlightRule> HighlightManager::getDefaultRules()
{
std::vector<HighlightRule> rules;
HighlightRule r;
r.setMatchChat(true);
r.getAction().setPlaySound(true);
rules.push_back(r);
return rules;
}
-void HighlightManager::storeSettings()
-{
- storingSettings_ = true; // don't reload settings while saving
- settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString());
- storingSettings_ = false;
-}
-
HighlightRule HighlightManager::getRule(int index) const
{
- assert(index >= 0 && static_cast<size_t>(index) < rules_.size());
- return rules_[static_cast<size_t>(index)];
+ assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize());
+ return rules_->getRule(static_cast<size_t>(index));
}
void HighlightManager::setRule(int index, const HighlightRule& rule)
{
- assert(index >= 0 && static_cast<size_t>(index) < rules_.size());
- rules_[static_cast<size_t>(index)] = rule;
- storeSettings();
+ assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize());
+ rules_->list_[static_cast<size_t>(index)] = rule;
}
void HighlightManager::insertRule(int index, const HighlightRule& rule)
{
- assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_.size());
- rules_.insert(rules_.begin() + index, rule);
- storeSettings();
+ assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_->getSize());
+ rules_->list_.insert(rules_->list_.begin() + index, rule);
}
void HighlightManager::removeRule(int index)
{
- assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_.size());
- rules_.erase(rules_.begin() + index);
- storeSettings();
+ assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_->getSize());
+ rules_->list_.erase(rules_->list_.begin() + index);
+}
+
+void HighlightManager::storeSettings()
+{
+ storingSettings_ = true; // don't reload settings while saving
+ settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString());
+ storingSettings_ = false;
+}
+
+void HighlightManager::loadSettings()
+{
+ std::string rulesString = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES);
+ std::stringstream stream;
+ stream << rulesString;
+ try {
+ boost::archive::text_iarchive archive(stream);
+ archive >> rules_->list_;
+ } catch (boost::archive::archive_exception&) {
+ rules_->list_ = getDefaultRules();
+ }
}
Highlighter* HighlightManager::createHighlighter()
{
return new Highlighter(this);
}
}
diff --git a/Swift/Controllers/HighlightManager.h b/Swift/Controllers/HighlightManager.h
index d195d05..3da72eb 100644
--- a/Swift/Controllers/HighlightManager.h
+++ b/Swift/Controllers/HighlightManager.h
@@ -1,49 +1,71 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#pragma once
#include <vector>
#include <string>
#include <Swiften/Base/boost_bsignals.h>
#include <Swift/Controllers/HighlightRule.h>
namespace Swift {
class SettingsProvider;
class Highlighter;
class HighlightManager {
public:
+
+ class HighlightRulesList {
+ public:
+ friend class HighlightManager;
+ size_t getSize() const { return list_.size(); }
+ const HighlightRule& getRule(const size_t index) const { return list_[index]; }
+ void addRule(const HighlightRule &rule) { list_.push_back(rule); }
+ void combineRules(const HighlightRulesList &rhs) {
+ list_.insert(list_.end(), rhs.list_.begin(), rhs.list_.end());
+ }
+ private:
+ std::vector<HighlightRule> list_;
+ };
+
HighlightManager(SettingsProvider* settings);
Highlighter* createHighlighter();
- const std::vector<HighlightRule>& getRules() const { return rules_; }
+ boost::shared_ptr<const HighlightManager::HighlightRulesList> getRules() const { return rules_; }
+
HighlightRule getRule(int index) const;
void setRule(int index, const HighlightRule& rule);
void insertRule(int index, const HighlightRule& rule);
void removeRule(int index);
+ void storeSettings();
+ void loadSettings();
boost::signal<void (const HighlightAction&)> onHighlight;
private:
void handleSettingChanged(const std::string& settingPath);
std::string rulesToString() const;
- static std::vector<HighlightRule> rulesFromString(const std::string&);
static std::vector<HighlightRule> getDefaultRules();
SettingsProvider* settings_;
bool storingSettings_;
- void storeSettings();
- void loadSettings();
- std::vector<HighlightRule> rules_;
+ boost::shared_ptr<HighlightManager::HighlightRulesList> rules_;
};
+ typedef boost::shared_ptr<const HighlightManager::HighlightRulesList> HighlightRulesListPtr;
+
}
diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp
index 9ca7d86..f1a5235 100644
--- a/Swift/Controllers/HighlightRule.cpp
+++ b/Swift/Controllers/HighlightRule.cpp
@@ -1,192 +1,140 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/lambda/lambda.hpp>
#include <Swiften/Base/foreach.h>
#include <Swiften/Base/Regex.h>
#include <Swift/Controllers/HighlightRule.h>
namespace Swift {
HighlightRule::HighlightRule()
: nickIsKeyword_(false)
, matchCase_(false)
, matchWholeWords_(false)
, matchChat_(false)
, matchMUC_(false)
{
}
boost::regex HighlightRule::regexFromString(const std::string & s) const
{
std::string escaped = Regex::escape(s);
std::string word = matchWholeWords_ ? "\\b" : "";
boost::regex::flag_type flags = boost::regex::normal;
if (!matchCase_) {
flags |= boost::regex::icase;
}
return boost::regex(word + escaped + word, flags);
}
void HighlightRule::updateRegex() const
{
keywordRegex_.clear();
foreach (const std::string & k, keywords_) {
keywordRegex_.push_back(regexFromString(k));
}
senderRegex_.clear();
foreach (const std::string & s, senders_) {
senderRegex_.push_back(regexFromString(s));
}
}
std::string HighlightRule::boolToString(bool b)
{
return b ? "1" : "0";
}
bool HighlightRule::boolFromString(const std::string& s)
{
return s == "1";
}
-std::string HighlightRule::toString() const
-{
- std::vector<std::string> v;
- v.push_back(boost::join(senders_, "\t"));
- v.push_back(boost::join(keywords_, "\t"));
- v.push_back(boolToString(nickIsKeyword_));
- v.push_back(boolToString(matchChat_));
- v.push_back(boolToString(matchMUC_));
- v.push_back(boolToString(matchCase_));
- v.push_back(boolToString(matchWholeWords_));
- v.push_back(boolToString(action_.highlightText()));
- v.push_back(action_.getTextColor());
- v.push_back(action_.getTextBackground());
- v.push_back(boolToString(action_.playSound()));
- v.push_back(action_.getSoundFile());
- return boost::join(v, "\n");
-}
-
-HighlightRule HighlightRule::fromString(const std::string& s)
-{
- std::vector<std::string> v;
- boost::split(v, s, boost::is_any_of("\n"));
-
- HighlightRule r;
- size_t i = 0;
- try {
- boost::split(r.senders_, v.at(i++), boost::is_any_of("\t"));
- r.senders_.erase(std::remove_if(r.senders_.begin(), r.senders_.end(), boost::lambda::_1 == ""), r.senders_.end());
- boost::split(r.keywords_, v.at(i++), boost::is_any_of("\t"));
- r.keywords_.erase(std::remove_if(r.keywords_.begin(), r.keywords_.end(), boost::lambda::_1 == ""), r.keywords_.end());
- r.nickIsKeyword_ = boolFromString(v.at(i++));
- r.matchChat_ = boolFromString(v.at(i++));
- r.matchMUC_ = boolFromString(v.at(i++));
- r.matchCase_ = boolFromString(v.at(i++));
- r.matchWholeWords_ = boolFromString(v.at(i++));
- r.action_.setHighlightText(boolFromString(v.at(i++)));
- r.action_.setTextColor(v.at(i++));
- r.action_.setTextBackground(v.at(i++));
- r.action_.setPlaySound(boolFromString(v.at(i++)));
- r.action_.setSoundFile(v.at(i++));
- }
- catch (std::out_of_range) {
- // this may happen if more properties are added to HighlightRule object, etc...
- // in such case, default values (set by default constructor) will be used
- }
-
- r.updateRegex();
-
- return r;
-}
-
bool HighlightRule::isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType messageType) const
{
if ((messageType == HighlightRule::ChatMessage && matchChat_) || (messageType == HighlightRule::MUCMessage && matchMUC_)) {
bool matchesKeyword = keywords_.empty() && (nick.empty() || !nickIsKeyword_);
bool matchesSender = senders_.empty();
- foreach (const boost::regex & rx, keywordRegex_) {
- if (boost::regex_search(body, rx)) {
- matchesKeyword = true;
- break;
- }
- }
-
if (!matchesKeyword && nickIsKeyword_ && !nick.empty()) {
if (boost::regex_search(body, regexFromString(nick))) {
matchesKeyword = true;
}
}
foreach (const boost::regex & rx, senderRegex_) {
if (boost::regex_search(sender, rx)) {
matchesSender = true;
break;
}
}
if (matchesKeyword && matchesSender) {
return true;
}
}
return false;
}
void HighlightRule::setSenders(const std::vector<std::string>& senders)
{
senders_ = senders;
updateRegex();
}
void HighlightRule::setKeywords(const std::vector<std::string>& keywords)
{
keywords_ = keywords;
updateRegex();
}
void HighlightRule::setNickIsKeyword(bool nickIsKeyword)
{
nickIsKeyword_ = nickIsKeyword;
updateRegex();
}
void HighlightRule::setMatchCase(bool matchCase)
{
matchCase_ = matchCase;
updateRegex();
}
void HighlightRule::setMatchWholeWords(bool matchWholeWords)
{
matchWholeWords_ = matchWholeWords;
updateRegex();
}
void HighlightRule::setMatchChat(bool matchChat)
{
matchChat_ = matchChat;
updateRegex();
}
void HighlightRule::setMatchMUC(bool matchMUC)
{
matchMUC_ = matchMUC;
updateRegex();
}
bool HighlightRule::isEmpty() const
{
return senders_.empty() && keywords_.empty() && !nickIsKeyword_ && !matchChat_ && !matchMUC_ && action_.isEmpty();
}
}
diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h
index 1abfa5a..ae1a3d3 100644
--- a/Swift/Controllers/HighlightRule.h
+++ b/Swift/Controllers/HighlightRule.h
@@ -1,77 +1,101 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#pragma once
#include <vector>
#include <string>
#include <boost/regex.hpp>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/archive/text_iarchive.hpp>
#include <Swift/Controllers/HighlightAction.h>
namespace Swift {
class HighlightRule {
public:
HighlightRule();
enum MessageType { ChatMessage, MUCMessage };
bool isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType) const;
const HighlightAction& getAction() const { return action_; }
HighlightAction& getAction() { return action_; }
- static HighlightRule fromString(const std::string&);
- std::string toString() const;
-
const std::vector<std::string>& getSenders() const { return senders_; }
void setSenders(const std::vector<std::string>&);
+ const std::vector<boost::regex>& getSenderRegex() const { return senderRegex_; }
const std::vector<std::string>& getKeywords() const { return keywords_; }
void setKeywords(const std::vector<std::string>&);
+ const std::vector<boost::regex>& getKeywordRegex() const { return keywordRegex_; }
bool getNickIsKeyword() const { return nickIsKeyword_; }
void setNickIsKeyword(bool);
bool getMatchCase() const { return matchCase_; }
void setMatchCase(bool);
bool getMatchWholeWords() const { return matchWholeWords_; }
void setMatchWholeWords(bool);
bool getMatchChat() const { return matchChat_; }
void setMatchChat(bool);
bool getMatchMUC() const { return matchMUC_; }
void setMatchMUC(bool);
bool isEmpty() const;
private:
+ friend class boost::serialization::access;
+ template<class Archive> void serialize(Archive & ar, const unsigned int version);
+
static std::string boolToString(bool);
static bool boolFromString(const std::string&);
std::vector<std::string> senders_;
std::vector<std::string> keywords_;
bool nickIsKeyword_;
mutable std::vector<boost::regex> senderRegex_;
mutable std::vector<boost::regex> keywordRegex_;
void updateRegex() const;
boost::regex regexFromString(const std::string&) const;
bool matchCase_;
bool matchWholeWords_;
bool matchChat_;
bool matchMUC_;
HighlightAction action_;
};
+ template<class Archive>
+ void HighlightRule::serialize(Archive& ar, const unsigned int /*version*/)
+ {
+ ar & senders_;
+ ar & keywords_;
+ ar & nickIsKeyword_;
+ ar & matchChat_;
+ ar & matchMUC_;
+ ar & matchCase_;
+ ar & matchWholeWords_;
+ ar & action_;
+ updateRegex();
+ }
+
}
diff --git a/Swift/Controllers/Highlighter.cpp b/Swift/Controllers/Highlighter.cpp
index 754641a..efeeb6b 100644
--- a/Swift/Controllers/Highlighter.cpp
+++ b/Swift/Controllers/Highlighter.cpp
@@ -1,41 +1,49 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#include <Swiften/Base/foreach.h>
#include <Swift/Controllers/Highlighter.h>
#include <Swift/Controllers/HighlightManager.h>
namespace Swift {
Highlighter::Highlighter(HighlightManager* manager)
: manager_(manager)
{
setMode(ChatMode);
}
void Highlighter::setMode(Mode mode)
{
mode_ = mode;
messageType_ = mode_ == ChatMode ? HighlightRule::ChatMessage : HighlightRule::MUCMessage;
}
HighlightAction Highlighter::findAction(const std::string& body, const std::string& sender) const
{
- foreach (const HighlightRule & r, manager_->getRules()) {
- if (r.isMatch(body, sender, nick_, messageType_)) {
- return r.getAction();
+ HighlightRulesListPtr rules = manager_->getRules();
+ for (size_t i = 0; i < rules->getSize(); ++i) {
+ const HighlightRule& rule = rules->getRule(i);
+ if (rule.isMatch(body, sender, nick_, messageType_)) {
+ return rule.getAction();
}
}
return HighlightAction();
}
void Highlighter::handleHighlightAction(const HighlightAction& action)
{
manager_->onHighlight(action);
}
}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 79b7502..a16cbe7 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -1,895 +1,890 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-/*
- * Copyright (c) 2013 Remko Tronçon
+ * Copyright (c) 2010-2014 Kevin Smith and Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/Controllers/MainController.h>
#include <cstdlib>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <Swiften/Base/format.h>
#include <Swiften/Base/Algorithm.h>
#include <Swiften/Base/String.h>
#include <Swiften/StringCodecs/Base64.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Client/Storages.h>
#include <Swiften/VCards/VCardManager.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/Base/foreach.h>
#include <Swiften/Client/Client.h>
#include <Swiften/Presence/PresenceSender.h>
#include <Swiften/Elements/ChatState.h>
#include <Swiften/Elements/Presence.h>
#include <Swiften/Elements/VCardUpdate.h>
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/Disco/CapsInfoGenerator.h>
#include <Swiften/Disco/GetDiscoInfoRequest.h>
#include <Swiften/Disco/ClientDiscoManager.h>
#include <Swiften/VCards/GetVCardRequest.h>
#include <Swiften/StringCodecs/Hexify.h>
#include <Swiften/Network/NetworkFactories.h>
#include <Swiften/FileTransfer/FileTransferManager.h>
#include <Swiften/Client/ClientXMLTracer.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Crypto/CryptoProvider.h>
#include <SwifTools/Dock/Dock.h>
#include <SwifTools/Notifier/TogglableNotifier.h>
#include <SwifTools/Idle/IdleDetector.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/UIInterfaces/UIFactory.h>
#include <Swift/Controllers/BuildVersion.h>
#include <Swift/Controllers/Chat/UserSearchController.h>
#include <Swift/Controllers/Chat/ChatsManager.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/EventWindowController.h>
#include <Swift/Controllers/UIInterfaces/LoginWindow.h>
#include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/MainWindow.h>
#include <Swift/Controllers/Chat/MUCController.h>
#include <Swift/Controllers/Roster/RosterController.h>
#include <Swift/Controllers/SoundEventController.h>
#include <Swift/Controllers/SoundPlayer.h>
#include <Swift/Controllers/StatusTracker.h>
#include <Swift/Controllers/SystemTray.h>
#include <Swift/Controllers/SystemTrayController.h>
#include <Swift/Controllers/XMLConsoleController.h>
#include <Swift/Controllers/HistoryController.h>
#include <Swift/Controllers/HistoryViewController.h>
#include <Swift/Controllers/FileTransferListController.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/PresenceNotifier.h>
#include <Swift/Controllers/EventNotifier.h>
#include <Swift/Controllers/Storages/StoragesFactory.h>
#include <Swift/Controllers/WhiteboardManager.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
#include <Swift/Controllers/Storages/CertificateStorageTrustChecker.h>
#include <Swift/Controllers/ProfileController.h>
#include <Swift/Controllers/ShowProfileController.h>
#include <Swift/Controllers/ContactEditController.h>
#include <Swift/Controllers/XMPPURIController.h>
#include <Swift/Controllers/AdHocManager.h>
#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/HighlightManager.h>
#include <Swift/Controllers/HighlightEditorController.h>
#include <Swift/Controllers/BlockListController.h>
#include <Swift/Controllers/ContactSuggester.h>
#include <Swift/Controllers/ContactsFromXMPPRoster.h>
namespace Swift {
static const std::string CLIENT_NAME = "Swift";
static const std::string CLIENT_NODE = "http://swift.im";
MainController::MainController(
EventLoop* eventLoop,
NetworkFactories* networkFactories,
UIFactory* uiFactories,
SettingsProvider* settings,
SystemTray* systemTray,
SoundPlayer* soundPlayer,
StoragesFactory* storagesFactory,
CertificateStorageFactory* certificateStorageFactory,
Dock* dock,
Notifier* notifier,
URIHandler* uriHandler,
IdleDetector* idleDetector,
const std::map<std::string, std::string>& emoticons,
bool useDelayForLatency) :
eventLoop_(eventLoop),
networkFactories_(networkFactories),
uiFactory_(uiFactories),
storagesFactory_(storagesFactory),
certificateStorageFactory_(certificateStorageFactory),
settings_(settings),
uriHandler_(uriHandler),
idleDetector_(idleDetector),
loginWindow_(NULL) ,
useDelayForLatency_(useDelayForLatency),
ftOverview_(NULL),
emoticons_(emoticons) {
storages_ = NULL;
certificateStorage_ = NULL;
statusTracker_ = NULL;
presenceNotifier_ = NULL;
eventNotifier_ = NULL;
rosterController_ = NULL;
chatsManager_ = NULL;
historyController_ = NULL;
historyViewController_ = NULL;
eventWindowController_ = NULL;
profileController_ = NULL;
blockListController_ = NULL;
showProfileController_ = NULL;
contactEditController_ = NULL;
userSearchControllerChat_ = NULL;
userSearchControllerAdd_ = NULL;
userSearchControllerInvite_ = NULL;
contactsFromRosterProvider_ = NULL;
contactSuggesterWithoutRoster_ = NULL;
contactSuggesterWithRoster_ = NULL;
whiteboardManager_ = NULL;
adHocManager_ = NULL;
quitRequested_ = false;
clientInitialized_ = false;
offlineRequested_ = false;
timeBeforeNextReconnect_ = -1;
dock_ = dock;
uiEventStream_ = new UIEventStream();
notifier_ = new TogglableNotifier(notifier);
notifier_->setPersistentEnabled(settings_->getSetting(SettingConstants::SHOW_NOTIFICATIONS));
eventController_ = new EventController();
eventController_->onEventQueueLengthChange.connect(boost::bind(&MainController::handleEventQueueLengthChange, this, _1));
systemTrayController_ = new SystemTrayController(eventController_, systemTray);
loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_);
loginWindow_->setShowNotificationToggle(!notifier->isExternallyConfigured());
highlightManager_ = new HighlightManager(settings_);
highlightEditorController_ = new HighlightEditorController(uiEventStream_, uiFactory_, highlightManager_);
soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, highlightManager_);
xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_);
std::string selectedLoginJID = settings_->getSetting(SettingConstants::LAST_LOGIN_JID);
bool loginAutomatically = settings_->getSetting(SettingConstants::LOGIN_AUTOMATICALLY);
std::string cachedPassword;
std::string cachedCertificate;
ClientOptions cachedOptions;
bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS);
if (!eagle) {
foreach (std::string profile, settings->getAvailableProfiles()) {
ProfileSettingsProvider profileSettings(profile, settings);
std::string password = profileSettings.getStringSetting("pass");
std::string certificate = profileSettings.getStringSetting("certificate");
std::string jid = profileSettings.getStringSetting("jid");
ClientOptions clientOptions = parseClientOptions(profileSettings.getStringSetting("options"));
loginWindow_->addAvailableAccount(jid, password, certificate, clientOptions);
if (jid == selectedLoginJID) {
cachedPassword = password;
cachedCertificate = certificate;
cachedOptions = clientOptions;
}
}
loginWindow_->selectUser(selectedLoginJID);
loginWindow_->setLoginAutomatically(loginAutomatically);
}
loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6, _7));
loginWindow_->onPurgeSavedLoginRequest.connect(boost::bind(&MainController::handlePurgeSavedLoginRequest, this, _1));
loginWindow_->onCancelLoginRequest.connect(boost::bind(&MainController::handleCancelLoginRequest, this));
loginWindow_->onQuitRequest.connect(boost::bind(&MainController::handleQuitRequest, this));
idleDetector_->setIdleTimeSeconds(settings->getSetting(SettingConstants::IDLE_TIMEOUT));
idleDetector_->onIdleChanged.connect(boost::bind(&MainController::handleInputIdleChanged, this, _1));
xmlConsoleController_ = new XMLConsoleController(uiEventStream_, uiFactory_);
fileTransferListController_ = new FileTransferListController(uiEventStream_, uiFactory_);
settings_->onSettingChanged.connect(boost::bind(&MainController::handleSettingChanged, this, _1));
if (loginAutomatically) {
profileSettings_ = new ProfileSettingsProvider(selectedLoginJID, settings_);
/* FIXME: deal with autologin with a cert*/
handleLoginRequest(selectedLoginJID, cachedPassword, cachedCertificate, CertificateWithKey::ref(), cachedOptions, true, true);
} else {
profileSettings_ = NULL;
}
}
MainController::~MainController() {
idleDetector_->onIdleChanged.disconnect(boost::bind(&MainController::handleInputIdleChanged, this, _1));
purgeCachedCredentials();
//setManagersOffline();
eventController_->disconnectAll();
resetClient();
delete highlightEditorController_;
delete highlightManager_;
delete fileTransferListController_;
delete xmlConsoleController_;
delete xmppURIController_;
delete soundEventController_;
delete systemTrayController_;
delete eventController_;
delete notifier_;
delete uiEventStream_;
}
void MainController::purgeCachedCredentials() {
safeClear(password_);
}
void MainController::resetClient() {
purgeCachedCredentials();
resetCurrentError();
resetPendingReconnects();
vCardPhotoHash_.clear();
delete contactEditController_;
contactEditController_ = NULL;
delete profileController_;
profileController_ = NULL;
delete showProfileController_;
showProfileController_ = NULL;
delete eventWindowController_;
eventWindowController_ = NULL;
delete chatsManager_;
chatsManager_ = NULL;
#ifdef SWIFT_EXPERIMENTAL_HISTORY
delete historyViewController_;
historyViewController_ = NULL;
delete historyController_;
historyController_ = NULL;
#endif
delete ftOverview_;
ftOverview_ = NULL;
delete rosterController_;
rosterController_ = NULL;
delete eventNotifier_;
eventNotifier_ = NULL;
delete presenceNotifier_;
presenceNotifier_ = NULL;
delete certificateStorage_;
certificateStorage_ = NULL;
delete storages_;
storages_ = NULL;
delete statusTracker_;
statusTracker_ = NULL;
delete profileSettings_;
profileSettings_ = NULL;
delete userSearchControllerChat_;
userSearchControllerChat_ = NULL;
delete userSearchControllerAdd_;
userSearchControllerAdd_ = NULL;
delete userSearchControllerInvite_;
userSearchControllerInvite_ = NULL;
delete contactSuggesterWithoutRoster_;
contactSuggesterWithoutRoster_ = NULL;
delete contactSuggesterWithRoster_;
contactSuggesterWithRoster_ = NULL;
delete contactsFromRosterProvider_;
contactsFromRosterProvider_ = NULL;
delete adHocManager_;
adHocManager_ = NULL;
delete whiteboardManager_;
whiteboardManager_ = NULL;
clientInitialized_ = false;
}
void MainController::handleSettingChanged(const std::string& settingPath) {
if (settingPath == SettingConstants::SHOW_NOTIFICATIONS.getKey()) {
notifier_->setPersistentEnabled(settings_->getSetting(SettingConstants::SHOW_NOTIFICATIONS));
}
}
void MainController::resetPendingReconnects() {
timeBeforeNextReconnect_ = -1;
if (reconnectTimer_) {
reconnectTimer_->stop();
reconnectTimer_.reset();
}
resetCurrentError();
}
void MainController::resetCurrentError() {
if (lastDisconnectError_) {
lastDisconnectError_->conclude();
lastDisconnectError_ = boost::shared_ptr<ErrorEvent>();
}
}
void MainController::handleConnected() {
boundJID_ = client_->getJID();
resetCurrentError();
resetPendingReconnects();
if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) {
purgeCachedCredentials();
}
bool freshLogin = rosterController_ == NULL;
myStatusLooksOnline_ = true;
if (freshLogin) {
profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_);
showProfileController_ = new ShowProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_);
ftOverview_ = new FileTransferOverview(client_->getFileTransferManager());
fileTransferListController_->setFileTransferOverview(ftOverview_);
rosterController_ = new RosterController(boundJID_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager(), client_->getVCardManager());
rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2));
rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this));
rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this));
blockListController_ = new BlockListController(client_->getClientBlockListManager(), uiEventStream_, uiFactory_, eventController_);
contactEditController_ = new ContactEditController(rosterController_, client_->getVCardManager(), uiFactory_, uiEventStream_);
whiteboardManager_ = new WhiteboardManager(uiFactory_, uiEventStream_, client_->getNickResolver(), client_->getWhiteboardSessionManager());
/* Doing this early as an ordering fix. Various things later will
* want to have the user's nick available and this means it will
* be before they receive stanzas that need it (e.g. bookmarks).*/
client_->getVCardManager()->requestOwnVCard();
contactSuggesterWithoutRoster_ = new ContactSuggester();
contactSuggesterWithRoster_ = new ContactSuggester();
userSearchControllerInvite_ = new UserSearchController(UserSearchController::InviteToChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle());
#ifdef SWIFT_EXPERIMENTAL_HISTORY
historyController_ = new HistoryController(storages_->getHistoryStorage());
historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager());
#else
chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager());
#endif
contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle());
contactSuggesterWithoutRoster_->addContactProvider(chatsManager_);
contactSuggesterWithRoster_->addContactProvider(chatsManager_);
contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_);
+ highlightEditorController_->setContactSuggester(contactSuggesterWithoutRoster_);
client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
chatsManager_->setAvatarManager(client_->getAvatarManager());
eventWindowController_ = new EventWindowController(eventController_, uiFactory_);
loginWindow_->morphInto(rosterController_->getWindow());
DiscoInfo discoInfo;
discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc"));
discoInfo.addFeature(DiscoInfo::ChatStatesFeature);
discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature);
discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature);
#ifdef SWIFT_EXPERIMENTAL_FT
discoInfo.addFeature(DiscoInfo::JingleFeature);
discoInfo.addFeature(DiscoInfo::JingleFTFeature);
discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature);
discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature);
#endif
#ifdef SWIFT_EXPERIMENTAL_WB
discoInfo.addFeature(DiscoInfo::WhiteboardFeature);
#endif
discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature);
client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
client_->getDiscoManager()->setDiscoInfo(discoInfo);
userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle());
userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithoutRoster_, client_->getAvatarManager(), client_->getPresenceOracle());
adHocManager_ = new AdHocManager(JID(boundJID_.getDomain()), uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow());
chatsManager_->onImpromptuMUCServiceDiscovered.connect(boost::bind(&UserSearchController::setCanInitiateImpromptuMUC, userSearchControllerChat_, _1));
}
loginWindow_->setIsLoggingIn(false);
client_->requestRoster();
GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(JID(boundJID_.getDomain()), client_->getIQRouter());
discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2));
discoInfoRequest->send();
client_->getVCardManager()->requestOwnVCard();
rosterController_->setJID(boundJID_);
rosterController_->setEnabled(true);
rosterController_->getWindow()->setStreamEncryptionStatus(client_->isStreamEncrypted());
profileController_->setAvailable(true);
contactEditController_->setAvailable(true);
/* Send presence later to catch all the incoming presences. */
sendPresence(statusTracker_->getNextPresence());
/* Enable chats last of all, so rejoining MUCs has the right sent presence */
assert(chatsManager_);
chatsManager_->setOnline(true);
adHocManager_->setOnline(true);
}
void MainController::handleEventQueueLengthChange(int count) {
dock_->setNumberOfPendingMessages(count);
}
void MainController::reconnectAfterError() {
if (reconnectTimer_) {
reconnectTimer_->stop();
}
performLoginFromCachedCredentials();
}
void MainController::handleChangeStatusRequest(StatusShow::Type show, const std::string &statusText) {
boost::shared_ptr<Presence> presence(new Presence());
if (show == StatusShow::None) {
// Note: this is misleading, None doesn't mean unavailable on the wire.
presence->setType(Presence::Unavailable);
resetPendingReconnects();
myStatusLooksOnline_ = false;
offlineRequested_ = true;
}
else {
offlineRequested_ = false;
presence->setShow(show);
}
presence->setStatus(statusText);
statusTracker_->setRequestedPresence(presence);
if (presence->getType() != Presence::Unavailable) {
profileSettings_->storeInt("lastShow", presence->getShow());
profileSettings_->storeString("lastStatus", presence->getStatus());
}
if (presence->getType() != Presence::Unavailable && !client_->isAvailable()) {
performLoginFromCachedCredentials();
} else {
sendPresence(presence);
}
}
void MainController::sendPresence(boost::shared_ptr<Presence> presence) {
rosterController_->getWindow()->setMyStatusType(presence->getShow());
rosterController_->getWindow()->setMyStatusText(presence->getStatus());
systemTrayController_->setMyStatusType(presence->getShow());
notifier_->setTemporarilyDisabled(presence->getShow() == StatusShow::DND);
// Add information and send
presence->updatePayload(boost::make_shared<VCardUpdate>(vCardPhotoHash_));
client_->getPresenceSender()->sendPresence(presence);
if (presence->getType() == Presence::Unavailable) {
logout();
}
}
void MainController::handleInputIdleChanged(bool idle) {
if (!statusTracker_) {
//Haven't logged in yet.
return;
}
if (settings_->getSetting(SettingConstants::IDLE_GOES_OFFLINE)) {
if (idle) {
logout();
}
}
else {
if (idle) {
if (statusTracker_->goAutoAway(idleDetector_->getIdleTimeSeconds())) {
if (client_ && client_->isAvailable()) {
sendPresence(statusTracker_->getNextPresence());
}
}
} else {
if (statusTracker_->goAutoUnAway()) {
if (client_ && client_->isAvailable()) {
sendPresence(statusTracker_->getNextPresence());
}
}
}
}
}
void MainController::handleShowCertificateRequest() {
std::vector<Certificate::ref> chain = client_->getStanzaChannel()->getPeerCertificateChain();
rosterController_->getWindow()->openCertificateDialog(chain);
}
void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificatePath, CertificateWithKey::ref certificate, const ClientOptions& options, bool remember, bool loginAutomatically) {
jid_ = JID(username);
if (!jid_.isValid() || jid_.getNode().empty()) {
loginWindow_->setMessage(QT_TRANSLATE_NOOP("", "User address invalid. User address should be of the form 'alice@wonderland.lit'"));
loginWindow_->setIsLoggingIn(false);
} else {
loginWindow_->setMessage("");
loginWindow_->setIsLoggingIn(true);
profileSettings_ = new ProfileSettingsProvider(username, settings_);
if (!settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) {
profileSettings_->storeString("jid", username);
profileSettings_->storeString("certificate", certificatePath);
profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : "");
std::string optionString = serializeClientOptions(options);
profileSettings_->storeString("options", optionString);
settings_->storeSetting(SettingConstants::LAST_LOGIN_JID, username);
settings_->storeSetting(SettingConstants::LOGIN_AUTOMATICALLY, loginAutomatically);
loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"), options);
}
password_ = password;
certificate_ = certificate;
clientOptions_ = options;
performLoginFromCachedCredentials();
}
}
void MainController::handlePurgeSavedLoginRequest(const std::string& username) {
settings_->removeProfile(username);
loginWindow_->removeAvailableAccount(username);
}
void MainController::performLoginFromCachedCredentials() {
if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS) && password_.empty()) {
/* Then we can't try to login again. */
return;
}
/* If we logged in with a bare JID, and we have a full bound JID, re-login with the
* bound JID to try and keep dynamically assigned resources */
JID clientJID = jid_;
if (boundJID_.isValid() && jid_.isBare() && boundJID_.toBare() == jid_) {
clientJID = boundJID_;
}
if (!statusTracker_) {
statusTracker_ = new StatusTracker();
}
if (!clientInitialized_) {
storages_ = storagesFactory_->createStorages(jid_.toBare());
certificateStorage_ = certificateStorageFactory_->createCertificateStorage(jid_.toBare());
certificateTrustChecker_ = new CertificateStorageTrustChecker(certificateStorage_);
client_ = boost::make_shared<Swift::Client>(clientJID, createSafeByteArray(password_.c_str()), networkFactories_, storages_);
clientInitialized_ = true;
client_->setCertificateTrustChecker(certificateTrustChecker_);
client_->onDataRead.connect(boost::bind(&XMLConsoleController::handleDataRead, xmlConsoleController_, _1));
client_->onDataWritten.connect(boost::bind(&XMLConsoleController::handleDataWritten, xmlConsoleController_, _1));
client_->onDisconnected.connect(boost::bind(&MainController::handleDisconnected, this, _1));
client_->onConnected.connect(boost::bind(&MainController::handleConnected, this));
client_->setSoftwareVersion(CLIENT_NAME, buildVersion);
client_->getVCardManager()->onVCardChanged.connect(boost::bind(&MainController::handleVCardReceived, this, _1, _2));
presenceNotifier_ = new PresenceNotifier(client_->getStanzaChannel(), notifier_, client_->getMUCRegistry(), client_->getAvatarManager(), client_->getNickResolver(), client_->getPresenceOracle(), networkFactories_->getTimerFactory());
presenceNotifier_->onNotificationActivated.connect(boost::bind(&MainController::handleNotificationClicked, this, _1));
eventNotifier_ = new EventNotifier(eventController_, notifier_, client_->getAvatarManager(), client_->getNickResolver());
eventNotifier_->onNotificationActivated.connect(boost::bind(&MainController::handleNotificationClicked, this, _1));
if (certificate_ && !certificate_->isNull()) {
client_->setCertificate(certificate_);
}
boost::shared_ptr<Presence> presence(new Presence());
presence->setShow(static_cast<StatusShow::Type>(profileSettings_->getIntSetting("lastShow", StatusShow::Online)));
presence->setStatus(profileSettings_->getStringSetting("lastStatus"));
statusTracker_->setRequestedPresence(presence);
} else {
/* In case we're in the middle of another login, make sure they don't overlap */
client_->disconnect();
}
systemTrayController_->setConnecting();
if (rosterController_) {
rosterController_->getWindow()->setConnecting();
}
ClientOptions clientOptions = clientOptions_;
bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS);
clientOptions.forgetPassword = eagle;
clientOptions.useTLS = eagle ? ClientOptions::RequireTLS : clientOptions_.useTLS;
client_->connect(clientOptions);
}
void MainController::handleDisconnected(const boost::optional<ClientError>& error) {
if (rosterController_) {
rosterController_->getWindow()->setStreamEncryptionStatus(false);
}
if (adHocManager_) {
adHocManager_->setOnline(false);
}
if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) {
purgeCachedCredentials();
}
if (quitRequested_) {
resetClient();
loginWindow_->quit();
}
else if (error) {
std::string message;
std::string certificateErrorMessage;
bool forceSignout = false;
switch(error->getType()) {
case ClientError::UnknownError: message = QT_TRANSLATE_NOOP("", "Unknown Error"); break;
case ClientError::DomainNameResolveError: message = QT_TRANSLATE_NOOP("", "Unable to find server"); break;
case ClientError::ConnectionError: message = QT_TRANSLATE_NOOP("", "Error connecting to server"); break;
case ClientError::ConnectionReadError: message = QT_TRANSLATE_NOOP("", "Error while receiving server data"); break;
case ClientError::ConnectionWriteError: message = QT_TRANSLATE_NOOP("", "Error while sending data to the server"); break;
case ClientError::XMLError: message = QT_TRANSLATE_NOOP("", "Error parsing server data"); break;
case ClientError::AuthenticationFailedError: message = QT_TRANSLATE_NOOP("", "Login/password invalid"); break;
case ClientError::CompressionFailedError: message = QT_TRANSLATE_NOOP("", "Error while compressing stream"); break;
case ClientError::ServerVerificationFailedError: message = QT_TRANSLATE_NOOP("", "Server verification failed"); break;
case ClientError::NoSupportedAuthMechanismsError: message = QT_TRANSLATE_NOOP("", "Authentication mechanisms not supported"); break;
case ClientError::UnexpectedElementError: message = QT_TRANSLATE_NOOP("", "Unexpected response"); break;
case ClientError::ResourceBindError: message = QT_TRANSLATE_NOOP("", "Error binding resource"); break;
case ClientError::SessionStartError: message = QT_TRANSLATE_NOOP("", "Error starting session"); break;
case ClientError::StreamError: message = QT_TRANSLATE_NOOP("", "Stream error"); break;
case ClientError::TLSError: message = QT_TRANSLATE_NOOP("", "Encryption error"); break;
case ClientError::ClientCertificateLoadError: message = QT_TRANSLATE_NOOP("", "Error loading certificate (Invalid password?)"); break;
case ClientError::ClientCertificateError: message = QT_TRANSLATE_NOOP("", "Certificate not authorized"); break;
case ClientError::CertificateCardRemoved: message = QT_TRANSLATE_NOOP("", "Certificate card removed"); forceSignout = true; break;
case ClientError::UnknownCertificateError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Unknown certificate"); break;
case ClientError::CertificateExpiredError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate has expired"); break;
case ClientError::CertificateNotYetValidError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate is not yet valid"); break;
case ClientError::CertificateSelfSignedError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate is self-signed"); break;
case ClientError::CertificateRejectedError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate has been rejected"); break;
case ClientError::CertificateUntrustedError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate is not trusted"); break;
case ClientError::InvalidCertificatePurposeError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate cannot be used for encrypting your connection"); break;
case ClientError::CertificatePathLengthExceededError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate path length constraint exceeded"); break;
case ClientError::InvalidCertificateSignatureError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Invalid certificate signature"); break;
case ClientError::InvalidCAError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Invalid Certificate Authority"); break;
case ClientError::InvalidServerIdentityError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate does not match the host identity"); break;
case ClientError::RevokedError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Certificate has been revoked"); break;
case ClientError::RevocationCheckFailedError: certificateErrorMessage = QT_TRANSLATE_NOOP("", "Unable to determine certificate revocation state"); break;
}
bool forceReconnectAfterCertificateTrust = false;
if (!certificateErrorMessage.empty()) {
std::vector<Certificate::ref> certificates = certificateTrustChecker_->getLastCertificateChain();
if (!certificates.empty() && loginWindow_->askUserToTrustCertificatePermanently(certificateErrorMessage, certificates)) {
certificateStorage_->addCertificate(certificates[0]);
forceReconnectAfterCertificateTrust = true;
}
else {
message = QT_TRANSLATE_NOOP("", "Certificate error");
}
}
if (forceReconnectAfterCertificateTrust && settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) {
forceReconnectAfterCertificateTrust = false;
forceSignout = true;
message = QT_TRANSLATE_NOOP("", "Re-enter credentials and retry");
}
if (forceReconnectAfterCertificateTrust) {
performLoginFromCachedCredentials();
}
else if (forceSignout || !rosterController_) { //hasn't been logged in yet or permanent error
signOut();
loginWindow_->setMessage(message);
loginWindow_->setIsLoggingIn(false);
} else {
logout();
if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) {
message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.")) % jid_.getDomain() % message);
} else {
if (!offlineRequested_) {
setReconnectTimer();
}
if (lastDisconnectError_) {
message = str(format(QT_TRANSLATE_NOOP("", "Reconnect to %1% failed: %2%. Will retry in %3% seconds.")) % jid_.getDomain() % message % boost::lexical_cast<std::string>(timeBeforeNextReconnect_));
lastDisconnectError_->conclude();
} else {
message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%.")) % jid_.getDomain() % message);
}
lastDisconnectError_ = boost::make_shared<ErrorEvent>(JID(jid_.getDomain()), message);
eventController_->handleIncomingEvent(lastDisconnectError_);
}
}
}
else if (!rosterController_) { //hasn't been logged in yet
loginWindow_->setIsLoggingIn(false);
}
}
void MainController::setReconnectTimer() {
if (timeBeforeNextReconnect_ < 0) {
timeBeforeNextReconnect_ = 1;
} else {
timeBeforeNextReconnect_ = timeBeforeNextReconnect_ >= 150 ? 300 : timeBeforeNextReconnect_ * 2; // Randomly selected by roll of a die, as required by 3920bis
}
if (reconnectTimer_) {
reconnectTimer_->stop();
}
reconnectTimer_ = networkFactories_->getTimerFactory()->createTimer(timeBeforeNextReconnect_ * 1000);
reconnectTimer_->onTick.connect(boost::bind(&MainController::reconnectAfterError, this));
reconnectTimer_->start();
}
void MainController::handleCancelLoginRequest() {
signOut();
}
void MainController::signOut() {
if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) {
purgeCachedCredentials();
}
eventController_->clear();
logout();
loginWindow_->loggedOut();
resetClient();
}
void MainController::logout() {
if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) {
purgeCachedCredentials();
}
systemTrayController_->setMyStatusType(StatusShow::None);
if (clientInitialized_ /*&& client_->isAvailable()*/) {
client_->disconnect();
}
if (rosterController_ && myStatusLooksOnline_) {
rosterController_->getWindow()->setMyStatusType(StatusShow::None);
rosterController_->getWindow()->setMyStatusText("");
myStatusLooksOnline_ = false;
}
setManagersOffline();
}
void MainController::setManagersOffline() {
if (chatsManager_) {
chatsManager_->setOnline(false);
}
if (rosterController_) {
rosterController_->setEnabled(false);
}
if (profileController_) {
profileController_->setAvailable(false);
}
if (contactEditController_) {
contactEditController_->setAvailable(false);
}
}
void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error) {
if (!error) {
chatsManager_->setServerDiscoInfo(info);
adHocManager_->setServerDiscoInfo(info);
if (info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
rosterController_->getWindow()->setBlockingCommandAvailable(true);
rosterController_->initBlockingCommand();
}
}
}
void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) {
if (!jid.equals(jid_, JID::WithoutResource) || !vCard) {
return;
}
std::string hash;
if (!vCard->getPhoto().empty()) {
hash = Hexify::hexify(networkFactories_->getCryptoProvider()->getSHA1Hash(vCard->getPhoto()));
}
if (hash != vCardPhotoHash_) {
vCardPhotoHash_ = hash;
if (client_ && client_->isAvailable()) {
sendPresence(statusTracker_->getNextPresence());
}
}
}
void MainController::handleNotificationClicked(const JID& jid) {
assert(chatsManager_);
if (clientInitialized_) {
if (client_->getMUCRegistry()->isMUC(jid)) {
uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(jid));
}
else {
uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(jid)));
}
}
}
void MainController::handleQuitRequest() {
if (client_ && client_->isActive()) {
quitRequested_ = true;
client_->disconnect();
}
else {
resetClient();
loginWindow_->quit();
}
}
#define SERIALIZE_BOOL(option) result += options.option ? "1" : "0"; result += ",";
#define SERIALIZE_INT(option) result += boost::lexical_cast<std::string>(options.option); result += ",";
#define SERIALIZE_STRING(option) result += Base64::encode(createByteArray(options.option)); result += ",";
#define SERIALIZE_SAFE_STRING(option) result += safeByteArrayToString(Base64::encode(options.option)); result += ",";
#define SERIALIZE_URL(option) SERIALIZE_STRING(option.toString())
std::string MainController::serializeClientOptions(const ClientOptions& options) {
std::string result;
SERIALIZE_BOOL(useStreamCompression);
switch (options.useTLS) {
case ClientOptions::NeverUseTLS: result += "1";break;
case ClientOptions::UseTLSWhenAvailable: result += "2";break;
case ClientOptions::RequireTLS: result += "3";break;
}
result += ",";
SERIALIZE_BOOL(allowPLAINWithoutTLS);
SERIALIZE_BOOL(useStreamResumption);
SERIALIZE_BOOL(useAcks);
SERIALIZE_STRING(manualHostname);
SERIALIZE_INT(manualPort);
switch (options.proxyType) {
case ClientOptions::NoProxy: result += "1";break;
case ClientOptions::SystemConfiguredProxy: result += "2";break;
case ClientOptions::SOCKS5Proxy: result += "3";break;
case ClientOptions::HTTPConnectProxy: result += "4";break;
}
result += ",";
SERIALIZE_STRING(manualProxyHostname);
SERIALIZE_INT(manualProxyPort);
SERIALIZE_URL(boshURL);
SERIALIZE_URL(boshHTTPConnectProxyURL);
SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthID);
SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthPassword);
return result;
}
#define CHECK_PARSE_LENGTH if (i >= segments.size()) {return result;}
#define PARSE_INT_RAW(defaultValue) CHECK_PARSE_LENGTH intVal = defaultValue; try {intVal = boost::lexical_cast<int>(segments[i]);} catch(const boost::bad_lexical_cast&) {};i++;
#define PARSE_STRING_RAW CHECK_PARSE_LENGTH stringVal = byteArrayToString(Base64::decode(segments[i]));i++;
#define PARSE_BOOL(option, defaultValue) PARSE_INT_RAW(defaultValue); result.option = (intVal == 1);
#define PARSE_INT(option, defaultValue) PARSE_INT_RAW(defaultValue); result.option = intVal;
#define PARSE_STRING(option) PARSE_STRING_RAW; result.option = stringVal;
#define PARSE_SAFE_STRING(option) PARSE_STRING_RAW; result.option = SafeString(createSafeByteArray(stringVal));
#define PARSE_URL(option) {PARSE_STRING_RAW; result.option = URL::fromString(stringVal);}
ClientOptions MainController::parseClientOptions(const std::string& optionString) {
ClientOptions result;
size_t i = 0;
int intVal = 0;
std::string stringVal;
std::vector<std::string> segments = String::split(optionString, ',');
PARSE_BOOL(useStreamCompression, 1);
PARSE_INT_RAW(-1);
switch (intVal) {
case 1: result.useTLS = ClientOptions::NeverUseTLS;break;
case 2: result.useTLS = ClientOptions::UseTLSWhenAvailable;break;
case 3: result.useTLS = ClientOptions::RequireTLS;break;
default:;
}
PARSE_BOOL(allowPLAINWithoutTLS, 0);
PARSE_BOOL(useStreamResumption, 0);
PARSE_BOOL(useAcks, 1);
PARSE_STRING(manualHostname);
PARSE_INT(manualPort, -1);
PARSE_INT_RAW(-1);
switch (intVal) {
case 1: result.proxyType = ClientOptions::NoProxy;break;
case 2: result.proxyType = ClientOptions::SystemConfiguredProxy;break;
case 3: result.proxyType = ClientOptions::SOCKS5Proxy;break;
case 4: result.proxyType = ClientOptions::HTTPConnectProxy;break;
}
PARSE_STRING(manualProxyHostname);
PARSE_INT(manualProxyPort, -1);
PARSE_URL(boshURL);
PARSE_URL(boshHTTPConnectProxyURL);
PARSE_SAFE_STRING(boshHTTPConnectProxyAuthID);
PARSE_SAFE_STRING(boshHTTPConnectProxyAuthPassword);
return result;
}
}
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 4c71268..5ebbdd3 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -1,110 +1,111 @@
Import("env")
import Version
################################################################################
# Flags
################################################################################
if env["SCONS_STAGE"] == "flags" :
env["SWIFT_CONTROLLERS_FLAGS"] = {
"LIBPATH": [Dir(".")],
"LIBS": ["SwiftControllers"]
}
################################################################################
# Build
################################################################################
if env["SCONS_STAGE"] == "build" :
myenv = env.Clone()
myenv.BuildVersion("BuildVersion.h", project = "swift")
myenv.UseFlags(env["SWIFTEN_FLAGS"])
myenv.UseFlags(env["SWIFTEN_DEP_FLAGS"])
myenv.StaticLibrary("SwiftControllers", [
"Chat/ChatController.cpp",
"Chat/ChatControllerBase.cpp",
"Chat/ChatsManager.cpp",
"Chat/MUCController.cpp",
"Chat/MUCSearchController.cpp",
"Chat/UserSearchController.cpp",
"Chat/ChatMessageParser.cpp",
"ContactSuggester.cpp",
"MainController.cpp",
"ProfileController.cpp",
"ShowProfileController.cpp",
"ContactEditController.cpp",
"FileTransfer/FileTransferController.cpp",
"FileTransfer/FileTransferOverview.cpp",
"FileTransfer/FileTransferProgressInfo.cpp",
"Roster/RosterController.cpp",
"Roster/RosterGroupExpandinessPersister.cpp",
"Roster/ContactRosterItem.cpp",
"Roster/GroupRosterItem.cpp",
"Roster/RosterItem.cpp",
"Roster/Roster.cpp",
"Roster/RosterVCardProvider.cpp",
"Roster/TableRoster.cpp",
"EventWindowController.cpp",
"SoundEventController.cpp",
"SystemTrayController.cpp",
"XMLConsoleController.cpp",
"HistoryViewController.cpp",
"HistoryController.cpp",
"FileTransferListController.cpp",
"BlockListController.cpp",
"StatusTracker.cpp",
"PresenceNotifier.cpp",
"EventNotifier.cpp",
"AdHocManager.cpp",
"AdHocController.cpp",
"XMPPEvents/EventController.cpp",
"UIEvents/UIEvent.cpp",
"UIInterfaces/XMLConsoleWidget.cpp",
"UIInterfaces/ChatListWindow.cpp",
+ "UIInterfaces/HighlightEditorWindow.cpp",
"PreviousStatusStore.cpp",
"ProfileSettingsProvider.cpp",
"Settings/SettingsProviderHierachy.cpp",
"Settings/XMLSettingsProvider.cpp",
"Storages/CertificateStorageFactory.cpp",
"Storages/CertificateStorage.cpp",
"Storages/CertificateFileStorage.cpp",
"Storages/CertificateMemoryStorage.cpp",
"Storages/AvatarFileStorage.cpp",
"Storages/FileStorages.cpp",
"Storages/RosterFileStorage.cpp",
"Storages/CapsFileStorage.cpp",
"Storages/VCardFileStorage.cpp",
"StatusUtil.cpp",
"Translator.cpp",
"XMPPURIController.cpp",
"ChatMessageSummarizer.cpp",
"SettingConstants.cpp",
"WhiteboardManager.cpp",
"StatusCache.cpp",
"HighlightAction.cpp",
"HighlightEditorController.cpp",
"HighlightManager.cpp",
"HighlightRule.cpp",
"Highlighter.cpp",
"ContactsFromXMPPRoster.cpp",
"ContactProvider.cpp",
"Contact.cpp"
])
env.Append(UNITTEST_SOURCES = [
File("Roster/UnitTest/RosterControllerTest.cpp"),
File("Roster/UnitTest/RosterTest.cpp"),
File("Roster/UnitTest/LeastCommonSubsequenceTest.cpp"),
File("Roster/UnitTest/TableRosterTest.cpp"),
File("UnitTest/PreviousStatusStoreTest.cpp"),
File("UnitTest/PresenceNotifierTest.cpp"),
File("Chat/UnitTest/ChatsManagerTest.cpp"),
File("Chat/UnitTest/MUCControllerTest.cpp"),
File("Chat/UnitTest/ChatMessageParserTest.cpp"),
File("UnitTest/MockChatWindow.cpp"),
File("UnitTest/ChatMessageSummarizerTest.cpp"),
File("Settings/UnitTest/SettingsProviderHierachyTest.cpp"),
File("UnitTest/HighlightRuleTest.cpp"),
File("UnitTest/ContactSuggesterTest.cpp")
])
diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp
new file mode 100644
index 0000000..f90903b
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h>
+
+namespace Swift {
+
+HighlightEditorWindow::HighlightEditorWindow()
+{
+}
+
+HighlightEditorWindow::~HighlightEditorWindow()
+{
+}
+
+}
diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h
new file mode 100644
index 0000000..83ae959
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <vector>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swift/Controllers/Contact.h>
+
+namespace Swift {
+
+class HighlightManager;
+
+class HighlightEditorWindow {
+public:
+ HighlightEditorWindow();
+ virtual ~HighlightEditorWindow();
+ virtual void show() = 0;
+ virtual void setHighlightManager(HighlightManager *highlightManager) = 0;
+ virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) = 0;
+
+public:
+ boost::signal<void (const std::string&)> onContactSuggestionsRequested;
+};
+
+}
diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h
new file mode 100644
index 0000000..e0aaaef
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+ class HighlightEditorWindow;
+
+ class HighlightEditorWindowFactory {
+ public :
+ virtual ~HighlightEditorWindowFactory() {}
+
+ virtual HighlightEditorWindow* createHighlightEditorWindow() = 0;
+ };
+}
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index 990dc98..54fa7ce 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -1,49 +1,49 @@
/*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010-2014 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/EventWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/MainWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h>
#include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h>
#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
-#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h>
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h>
namespace Swift {
class UIFactory :
public ChatListWindowFactory,
public ChatWindowFactory,
public HistoryWindowFactory,
public EventWindowFactory,
public LoginWindowFactory,
public MainWindowFactory,
public MUCSearchWindowFactory,
public XMLConsoleWidgetFactory,
public UserSearchWindowFactory,
public JoinMUCWindowFactory,
public ProfileWindowFactory,
public ContactEditWindowFactory,
public AdHocCommandWindowFactory,
public FileTransferListWidgetFactory,
public WhiteboardWindowFactory,
- public HighlightEditorWidgetFactory,
+ public HighlightEditorWindowFactory,
public BlockListEditorWidgetFactory {
public:
virtual ~UIFactory() {}
};
}
diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp
index ec81227..c988b8d 100644
--- a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp
+++ b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp
@@ -1,318 +1,324 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#include <vector>
#include <string>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <Swift/Controllers/HighlightRule.h>
using namespace Swift;
class HighlightRuleTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(HighlightRuleTest);
CPPUNIT_TEST(testEmptyRuleNeverMatches);
CPPUNIT_TEST(testKeyword);
CPPUNIT_TEST(testNickKeyword);
CPPUNIT_TEST(testNickWithoutOtherKeywords);
CPPUNIT_TEST(testSender);
CPPUNIT_TEST(testSenderAndKeyword);
CPPUNIT_TEST(testWholeWords);
CPPUNIT_TEST(testCase);
CPPUNIT_TEST(testWholeWordsAndCase);
CPPUNIT_TEST(testMUC);
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
std::vector<std::string> keywords;
keywords.push_back("keyword1");
keywords.push_back("KEYWORD2");
- std::vector<std::string>senders;
+ std::vector<std::string> senders;
senders.push_back("sender1");
senders.push_back("SENDER2");
emptyRule = new HighlightRule();
keywordRule = new HighlightRule();
keywordRule->setKeywords(keywords);
keywordChatRule = new HighlightRule();
keywordChatRule->setKeywords(keywords);
keywordChatRule->setMatchChat(true);
keywordNickChatRule = new HighlightRule();
keywordNickChatRule->setKeywords(keywords);
keywordNickChatRule->setNickIsKeyword(true);
keywordNickChatRule->setMatchChat(true);
nickChatRule = new HighlightRule();
nickChatRule->setNickIsKeyword(true);
nickChatRule->setMatchChat(true);
nickRule = new HighlightRule();
nickRule->setNickIsKeyword(true);
senderRule = new HighlightRule();
senderRule->setSenders(senders);
senderChatRule = new HighlightRule();
senderChatRule->setSenders(senders);
senderChatRule->setMatchChat(true);
senderKeywordChatRule = new HighlightRule();
senderKeywordChatRule->setSenders(senders);
senderKeywordChatRule->setKeywords(keywords);
senderKeywordChatRule->setMatchChat(true);
senderKeywordNickChatRule = new HighlightRule();
senderKeywordNickChatRule->setSenders(senders);
senderKeywordNickChatRule->setKeywords(keywords);
senderKeywordNickChatRule->setNickIsKeyword(true);
senderKeywordNickChatRule->setMatchChat(true);
senderKeywordNickWordChatRule = new HighlightRule();
senderKeywordNickWordChatRule->setSenders(senders);
senderKeywordNickWordChatRule->setKeywords(keywords);
senderKeywordNickWordChatRule->setNickIsKeyword(true);
senderKeywordNickWordChatRule->setMatchWholeWords(true);
senderKeywordNickWordChatRule->setMatchChat(true);
senderKeywordNickCaseChatRule = new HighlightRule();
senderKeywordNickCaseChatRule->setSenders(senders);
senderKeywordNickCaseChatRule->setKeywords(keywords);
senderKeywordNickCaseChatRule->setNickIsKeyword(true);
senderKeywordNickCaseChatRule->setMatchCase(true);
senderKeywordNickCaseChatRule->setMatchChat(true);
senderKeywordNickCaseWordChatRule = new HighlightRule();
senderKeywordNickCaseWordChatRule->setSenders(senders);
senderKeywordNickCaseWordChatRule->setKeywords(keywords);
senderKeywordNickCaseWordChatRule->setNickIsKeyword(true);
senderKeywordNickCaseWordChatRule->setMatchCase(true);
senderKeywordNickCaseWordChatRule->setMatchWholeWords(true);
senderKeywordNickCaseWordChatRule->setMatchChat(true);
senderKeywordNickMUCRule = new HighlightRule();
senderKeywordNickMUCRule->setSenders(senders);
senderKeywordNickMUCRule->setKeywords(keywords);
senderKeywordNickMUCRule->setNickIsKeyword(true);
senderKeywordNickMUCRule->setMatchMUC(true);
}
void tearDown() {
delete emptyRule;
delete keywordRule;
delete keywordChatRule;
delete keywordNickChatRule;
delete nickChatRule;
delete nickRule;
delete senderRule;
delete senderChatRule;
delete senderKeywordChatRule;
delete senderKeywordNickChatRule;
delete senderKeywordNickWordChatRule;
delete senderKeywordNickCaseChatRule;
delete senderKeywordNickCaseWordChatRule;
delete senderKeywordNickMUCRule;
}
void testEmptyRuleNeverMatches() {
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::MUCMessage), false);
}
void testKeyword() {
CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false);
- CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(keywordRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "sender contains keyword1", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), true);
- CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), true);
- CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), true);
- CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), false);
}
void testNickKeyword() {
CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "sender contains nick", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains mixed-case NiCk", "sender", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false);
}
void testNickWithoutOtherKeywords() {
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(nickRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "sender contains nick but it does't matter", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains mixed-case NiCk", "from", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true);
// there are no keywords in this rule and empty nick is not treated as a keyword, so we don't check for keywords to get a match
CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), true);
}
void testSender() {
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body contains sender1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc sender1 xyz", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcsender1xyz", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "SENDer1", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc SENDer1 xyz", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcSENDer1xyz", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender2", "nick", HighlightRule::ChatMessage), true);
}
void testSenderAndKeyword() {
CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);
}
void testWholeWords() {
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), true);
}
void testCase() {
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true);
- CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false);
}
void testWholeWordsAndCase() {
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true);
+ CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false);
}
void testMUC() {
CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);
- CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), true);
+ CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), false);
CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::MUCMessage), true);
}
private:
HighlightRule* emptyRule;
HighlightRule* keywordRule;
HighlightRule* keywordChatRule;
HighlightRule* keywordNickChatRule;
HighlightRule* nickChatRule;
HighlightRule* nickRule;
HighlightRule* senderRule;
HighlightRule* senderChatRule;
HighlightRule* senderKeywordChatRule;
HighlightRule* senderKeywordNickChatRule;
HighlightRule* senderKeywordNickWordChatRule;
HighlightRule* senderKeywordNickCaseChatRule;
HighlightRule* senderKeywordNickCaseWordChatRule;
HighlightRule* senderKeywordNickMUCRule;
};
CPPUNIT_TEST_SUITE_REGISTRATION(HighlightRuleTest);
diff --git a/Swift/QtUI/QtHighlightEditor.cpp b/Swift/QtUI/QtHighlightEditor.cpp
new file mode 100644
index 0000000..3900cf9
--- /dev/null
+++ b/Swift/QtUI/QtHighlightEditor.cpp
@@ -0,0 +1,524 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cassert>
+
+#include <boost/lexical_cast.hpp>
+
+#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h>
+#include <Swift/Controllers/HighlightManager.cpp>
+#include <Swift/QtUI/QtHighlightEditor.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtSettingsProvider.h>
+
+#include <QTreeWidgetItem>
+#include <QFileDialog>
+
+namespace Swift {
+
+QtHighlightEditor::QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent)
+ : QWidget(parent), settings_(settings), previousRow_(-1)
+{
+ ui_.setupUi(this);
+
+ connect(ui_.listWidget, SIGNAL(currentRowChanged(int)), SLOT(onCurrentRowChanged(int)));
+
+ connect(ui_.newButton, SIGNAL(clicked()), SLOT(onNewButtonClicked()));
+ connect(ui_.deleteButton, SIGNAL(clicked()), SLOT(onDeleteButtonClicked()));
+
+ connect(ui_.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(onApplyButtonClick()));
+ connect(ui_.buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(onCancelButtonClick()));
+ connect(ui_.buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(onOkButtonClick()));
+
+ connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect()));
+ connect(ui_.defaultColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect()));
+ connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(colorCustomSelect()));
+
+ connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect()));
+ connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect()));
+ connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(soundCustomSelect()));
+
+ /* replace the static line-edit control with the roster autocompleter */
+ ui_.dummySenderName->setVisible(false);
+ jid_ = new QtSuggestingJIDInput(this, settings);
+ ui_.senderName->addWidget(jid_);
+ jid_->onUserSelected.connect(boost::bind(&QtHighlightEditor::handleOnUserSelected, this, _1));
+
+ /* handle autocomplete */
+ connect(jid_, SIGNAL(textEdited(QString)), SLOT(handleContactSuggestionRequested(QString)));
+
+ /* we need to be notified if any of the state changes so that we can update our textual rule description */
+ connect(ui_.chatRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.roomRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.nickIsKeyword, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.allMsgRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.senderRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(jid_, SIGNAL(textChanged(const QString&)), SLOT(widgetClick()));
+ connect(ui_.keywordRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.keyword, SIGNAL(textChanged(const QString&)), SLOT(widgetClick()));
+ connect(ui_.matchPartialWords, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.matchCase, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.defaultColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+ connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
+
+ /* allow selection of a custom sound file */
+ connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(selectSoundFile()));
+
+ /* if these are not needed, then they should be removed */
+ ui_.moveUpButton->setVisible(false);
+ ui_.moveDownButton->setVisible(false);
+
+ setWindowTitle(tr("Highlight Rules"));
+}
+
+QtHighlightEditor::~QtHighlightEditor()
+{
+}
+
+std::string formatShortDescription(const HighlightRule &rule)
+{
+ const std::string chatOrRoom = (rule.getMatchChat() ? "chat" : "room");
+
+ std::vector<std::string> senders = rule.getSenders();
+ std::vector<std::string> keywords = rule.getKeywords();
+
+ if (senders.empty() && keywords.empty() && !rule.getNickIsKeyword()) {
+ return std::string("All ") + chatOrRoom + " messages.";
+ }
+
+ if (rule.getNickIsKeyword()) {
+ return std::string("All ") + chatOrRoom + " messages that mention my name.";
+ }
+
+ if (!senders.empty()) {
+ return std::string("All ") + chatOrRoom + " messages from " + senders[0] + ".";
+ }
+
+ if (!keywords.empty()) {
+ return std::string("All ") + chatOrRoom + " messages mentioning the keyword '" + keywords[0] + "'.";
+ }
+
+ return "Unknown Rule";
+}
+
+void QtHighlightEditor::show()
+{
+ highlightManager_->loadSettings();
+
+ populateList();
+
+ if (ui_.listWidget->count()) {
+ selectRow(0);
+ }
+
+ /* prepare default states */
+ widgetClick();
+
+ QWidget::show();
+ QWidget::activateWindow();
+}
+
+void QtHighlightEditor::setHighlightManager(HighlightManager* highlightManager)
+{
+ highlightManager_ = highlightManager;
+}
+
+void QtHighlightEditor::setContactSuggestions(const std::vector<Contact::ref>& suggestions)
+{
+ jid_->setSuggestions(suggestions);
+}
+
+void QtHighlightEditor::colorOtherSelect()
+{
+ ui_.foregroundColor->setEnabled(false);
+ ui_.backgroundColor->setEnabled(false);
+}
+
+void QtHighlightEditor::colorCustomSelect()
+{
+ ui_.foregroundColor->setEnabled(true);
+ ui_.backgroundColor->setEnabled(true);
+}
+
+void QtHighlightEditor::soundOtherSelect()
+{
+ ui_.soundFile->setEnabled(false);
+ ui_.soundFileButton->setEnabled(false);
+}
+
+void QtHighlightEditor::soundCustomSelect()
+{
+ ui_.soundFile->setEnabled(true);
+ ui_.soundFileButton->setEnabled(true);
+}
+
+void QtHighlightEditor::onNewButtonClicked()
+{
+ int row = getSelectedRow() + 1;
+ populateList();
+ HighlightRule newRule;
+ newRule.setMatchChat(true);
+ highlightManager_->insertRule(row, newRule);
+ QListWidgetItem *item = new QListWidgetItem();
+ item->setText(P2QSTRING(formatShortDescription(newRule)));
+ ui_.listWidget->insertItem(row, item);
+ selectRow(row);
+}
+
+void QtHighlightEditor::onDeleteButtonClicked()
+{
+ int selectedRow = getSelectedRow();
+ assert(selectedRow>=0 && selectedRow<ui_.listWidget->count());
+ delete ui_.listWidget->takeItem(selectedRow);
+ highlightManager_->removeRule(selectedRow);
+
+ if (!ui_.listWidget->count()) {
+ disableDialog();
+ ui_.deleteButton->setEnabled(false);
+ } else {
+ if (selectedRow == ui_.listWidget->count()) {
+ selectRow(ui_.listWidget->count() - 1);
+ } else {
+ selectRow(selectedRow);
+ }
+ }
+}
+
+void QtHighlightEditor::onCurrentRowChanged(int currentRow)
+{
+ ui_.deleteButton->setEnabled(currentRow != -1);
+ ui_.moveUpButton->setEnabled(currentRow != -1 && currentRow != 0);
+ ui_.moveDownButton->setEnabled(currentRow != -1 && currentRow != (ui_.listWidget->count()-1));
+
+ if (previousRow_ != -1) {
+ if (ui_.listWidget->count() > previousRow_) {
+ highlightManager_->setRule(previousRow_, ruleFromDialog());
+ }
+ }
+
+ if (currentRow != -1) {
+ HighlightRule rule = highlightManager_->getRule(currentRow);
+ ruleToDialog(rule);
+ if (ui_.listWidget->currentItem()) {
+ ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule)));
+ }
+ }
+
+ /* grey the dialog if we have nothing selected */
+ if (currentRow == -1) {
+ disableDialog();
+ }
+
+ previousRow_ = currentRow;
+}
+
+void QtHighlightEditor::onApplyButtonClick()
+{
+ selectRow(getSelectedRow()); /* force save */
+ highlightManager_->storeSettings();
+}
+
+void QtHighlightEditor::onCancelButtonClick()
+{
+ close();
+}
+
+void QtHighlightEditor::onOkButtonClick()
+{
+ onApplyButtonClick();
+ close();
+}
+
+void QtHighlightEditor::setChildWidgetStates()
+{
+ /* disable appropriate radio button child widgets */
+
+ if (ui_.chatRadio->isChecked()) {
+ if (ui_.nickIsKeyword->isChecked()) {
+ /* switch to another choice before we disable this button */
+ ui_.allMsgRadio->setChecked(true);
+ }
+ ui_.nickIsKeyword->setEnabled(false);
+ } else if (ui_.roomRadio->isChecked()) {
+ ui_.nickIsKeyword->setEnabled(true);
+ } else { /* chats and rooms */
+ ui_.nickIsKeyword->setEnabled(true);
+ }
+
+ if (ui_.senderRadio->isChecked()) {
+ jid_->setEnabled(true);
+ } else {
+ jid_->setEnabled(false);
+ }
+
+ if (ui_.keywordRadio->isChecked()) {
+ ui_.keyword->setEnabled(true);
+ ui_.matchPartialWords->setEnabled(true);
+ ui_.matchCase->setEnabled(true);
+ } else {
+ ui_.keyword->setEnabled(false);
+ ui_.matchPartialWords->setEnabled(false);
+ ui_.matchCase->setEnabled(false);
+ }
+
+ if (ui_.chatRadio->isChecked()) {
+ ui_.allMsgRadio->setText(tr("Apply to all chat messages"));
+ } else {
+ ui_.allMsgRadio->setText(tr("Apply to all room messages"));
+ }
+}
+
+void QtHighlightEditor::widgetClick()
+{
+ setChildWidgetStates();
+
+ HighlightRule rule = ruleFromDialog();
+
+ if (ui_.listWidget->currentItem()) {
+ ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule)));
+ }
+}
+
+void QtHighlightEditor::disableDialog()
+{
+ ui_.chatRadio->setEnabled(false);
+ ui_.roomRadio->setEnabled(false);
+ ui_.allMsgRadio->setEnabled(false);
+ ui_.nickIsKeyword->setEnabled(false);
+ ui_.senderRadio->setEnabled(false);
+ ui_.dummySenderName->setEnabled(false);
+ ui_.keywordRadio->setEnabled(false);
+ ui_.keyword->setEnabled(false);
+ ui_.matchPartialWords->setEnabled(false);
+ ui_.matchCase->setEnabled(false);
+ ui_.noColorRadio->setEnabled(false);
+ ui_.defaultColorRadio->setEnabled(false);
+ ui_.customColorRadio->setEnabled(false);
+ ui_.foregroundColor->setEnabled(false);
+ ui_.backgroundColor->setEnabled(false);
+ ui_.noSoundRadio->setEnabled(false);
+ ui_.defaultSoundRadio->setEnabled(false);
+ ui_.customSoundRadio->setEnabled(false);
+ ui_.soundFile->setEnabled(false);
+ ui_.soundFileButton->setEnabled(false);
+}
+
+void QtHighlightEditor::handleContactSuggestionRequested(const QString& text)
+{
+ std::string stdText = Q2PSTRING(text);
+ onContactSuggestionsRequested(stdText);
+}
+
+void QtHighlightEditor::selectSoundFile()
+{
+ QString path = QFileDialog::getOpenFileName(this, tr("Select sound file..."), QString(), "Sounds (*.wav)");
+ ui_.soundFile->setText(path);
+}
+
+void QtHighlightEditor::handleOnUserSelected(const JID& jid) {
+ /* this might seem like it should be standard behaviour for the suggesting input box, but is not desirable in all cases */
+ jid_->setText(P2QSTRING(jid.toString()));
+}
+
+void QtHighlightEditor::populateList()
+{
+ previousRow_ = -1;
+ ui_.listWidget->clear();
+ HighlightRulesListPtr rules = highlightManager_->getRules();
+ for (size_t i = 0; i < rules->getSize(); ++i) {
+ const HighlightRule& rule = rules->getRule(i);
+ QListWidgetItem *item = new QListWidgetItem();
+ item->setText(P2QSTRING(formatShortDescription(rule)));
+ ui_.listWidget->addItem(item);
+ }
+}
+
+void QtHighlightEditor::selectRow(int row)
+{
+ for (int i = 0; i < ui_.listWidget->count(); ++i) {
+ if (i == row) {
+ ui_.listWidget->item(i)->setSelected(true);
+ onCurrentRowChanged(i);
+ } else {
+ ui_.listWidget->item(i)->setSelected(false);
+ }
+ }
+ ui_.listWidget->setCurrentRow(row);
+}
+
+int QtHighlightEditor::getSelectedRow() const
+{
+ for (int i = 0; i < ui_.listWidget->count(); ++i) {
+ if (ui_.listWidget->item(i)->isSelected()) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+HighlightRule QtHighlightEditor::ruleFromDialog()
+{
+ HighlightRule rule;
+
+ if (ui_.chatRadio->isChecked()) {
+ rule.setMatchChat(true);
+ rule.setMatchMUC(false);
+ } else {
+ rule.setMatchChat(false);
+ rule.setMatchMUC(true);
+ }
+
+ if (ui_.senderRadio->isChecked()) {
+ QString senderName = jid_->text();
+ if (!senderName.isEmpty()) {
+ std::vector<std::string> senders;
+ senders.push_back(Q2PSTRING(senderName));
+ rule.setSenders(senders);
+ }
+ }
+
+ if (ui_.keywordRadio->isChecked()) {
+ QString keywordString = ui_.keyword->text();
+ if (!keywordString.isEmpty()) {
+ std::vector<std::string> keywords;
+ keywords.push_back(Q2PSTRING(keywordString));
+ rule.setKeywords(keywords);
+ }
+ }
+
+ rule.setNickIsKeyword(ui_.nickIsKeyword->isChecked());
+ rule.setMatchWholeWords(!ui_.matchPartialWords->isChecked());
+ rule.setMatchCase(ui_.matchCase->isChecked());
+
+ HighlightAction& action = rule.getAction();
+
+ if (ui_.noColorRadio->isChecked()) {
+ action.setHighlightText(false);
+ action.setTextColor("");
+ action.setTextBackground("");
+ } else if (ui_.defaultColorRadio->isChecked()) {
+ action.setHighlightText(true);
+ action.setTextColor("");
+ action.setTextBackground("");
+ } else {
+ action.setHighlightText(true);
+ action.setTextColor(Q2PSTRING(ui_.foregroundColor->getColor().name()));
+ action.setTextBackground(Q2PSTRING(ui_.backgroundColor->getColor().name()));
+ }
+
+ if (ui_.noSoundRadio->isChecked()) {
+ action.setPlaySound(false);
+ } else if (ui_.defaultSoundRadio->isChecked()) {
+ action.setPlaySound(true);
+ action.setSoundFile("");
+ } else {
+ action.setPlaySound(true);
+ action.setSoundFile(Q2PSTRING(ui_.soundFile->text()));
+ }
+
+ return rule;
+}
+
+void QtHighlightEditor::ruleToDialog(const HighlightRule& rule)
+{
+ ui_.chatRadio->setEnabled(true);
+ ui_.roomRadio->setEnabled(true);
+
+ if (rule.getMatchMUC()) {
+ ui_.chatRadio->setChecked(false);
+ ui_.roomRadio->setChecked(true);
+ } else {
+ ui_.chatRadio->setChecked(true);
+ ui_.roomRadio->setChecked(false);
+ }
+
+ ui_.allMsgRadio->setEnabled(true);
+ ui_.allMsgRadio->setChecked(true); /* this is the default radio button */
+ jid_->setText("");
+ ui_.keyword->setText("");
+ ui_.matchPartialWords->setChecked(false);
+ ui_.matchCase->setChecked(false);
+
+ ui_.nickIsKeyword->setEnabled(true);
+ if (rule.getNickIsKeyword()) {
+ ui_.nickIsKeyword->setChecked(true);
+ }
+
+ ui_.senderRadio->setEnabled(true);
+ std::vector<std::string> senders = rule.getSenders();
+ if (!senders.empty()) {
+ ui_.senderRadio->setChecked(true);
+ jid_->setText(P2QSTRING(senders[0]));
+ }
+
+ ui_.keywordRadio->setEnabled(true);
+ std::vector<std::string> keywords = rule.getKeywords();
+ if (!keywords.empty()) {
+ ui_.keywordRadio->setChecked(true);
+ ui_.keyword->setText(P2QSTRING(keywords[0]));
+ ui_.matchPartialWords->setChecked(!rule.getMatchWholeWords());
+ ui_.matchCase->setChecked(rule.getMatchCase());
+ }
+
+ const HighlightAction& action = rule.getAction();
+
+ ui_.noColorRadio->setEnabled(true);
+ ui_.defaultColorRadio->setEnabled(true);
+ ui_.customColorRadio->setEnabled(true);
+ if (action.highlightText()) {
+ if (action.getTextColor().empty() && action.getTextBackground().empty()) {
+ ui_.defaultColorRadio->setChecked(true);
+ ui_.foregroundColor->setEnabled(false);
+ ui_.backgroundColor->setEnabled(false);
+ } else {
+ ui_.foregroundColor->setEnabled(true);
+ ui_.backgroundColor->setEnabled(true);
+ QColor foregroundColor(P2QSTRING(action.getTextColor()));
+ ui_.foregroundColor->setColor(foregroundColor);
+ QColor backgroundColor(P2QSTRING(action.getTextBackground()));
+ ui_.backgroundColor->setColor(backgroundColor);
+ ui_.customColorRadio->setChecked(true);
+ }
+ } else {
+ ui_.noColorRadio->setChecked(true);
+ ui_.foregroundColor->setEnabled(false);
+ ui_.backgroundColor->setEnabled(false);
+ }
+
+ ui_.noSoundRadio->setEnabled(true);
+ ui_.defaultSoundRadio->setEnabled(true);
+ ui_.customSoundRadio->setEnabled(true);
+ ui_.soundFile->setText("");
+ ui_.soundFile->setEnabled(false);
+ ui_.soundFileButton->setEnabled(false);
+ if (action.playSound()) {
+ if (action.getSoundFile().empty()) {
+ ui_.defaultSoundRadio->setChecked(true);
+ } else {
+ ui_.customSoundRadio->setChecked(true);
+ ui_.soundFile->setText(P2QSTRING(action.getSoundFile()));
+ ui_.soundFile->setEnabled(true);
+ ui_.soundFileButton->setEnabled(true);
+ }
+ } else {
+ ui_.noSoundRadio->setChecked(true);
+ }
+
+ /* set radio button child option states */
+ setChildWidgetStates();
+}
+
+}
diff --git a/Swift/QtUI/QtHighlightEditor.h b/Swift/QtUI/QtHighlightEditor.h
new file mode 100644
index 0000000..c7db464
--- /dev/null
+++ b/Swift/QtUI/QtHighlightEditor.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/HighlightRule.h>
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h>
+#include <Swift/QtUI/ui_QtHighlightEditor.h>
+
+namespace Swift {
+
+ class QtSettingsProvider;
+ class QtSuggestingJIDInput;
+ class QtWebKitChatView;
+
+ class QtHighlightEditor : public QWidget, public HighlightEditorWindow {
+ Q_OBJECT
+
+ public:
+ QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent = NULL);
+ virtual ~QtHighlightEditor();
+
+ virtual void show();
+ virtual void setHighlightManager(HighlightManager* highlightManager);
+ virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions);
+
+ private slots:
+ void colorOtherSelect();
+ void colorCustomSelect();
+ void soundOtherSelect();
+ void soundCustomSelect();
+ void onNewButtonClicked();
+ void onDeleteButtonClicked();
+ void onCurrentRowChanged(int currentRow);
+ void onApplyButtonClick();
+ void onCancelButtonClick();
+ void onOkButtonClick();
+ void setChildWidgetStates();
+ void widgetClick();
+ void disableDialog();
+ void handleContactSuggestionRequested(const QString& text);
+ void selectSoundFile();
+
+ private:
+ void handleOnUserSelected(const JID& jid);
+ void populateList();
+ void updateChatPreview();
+ void selectRow(int row);
+ int getSelectedRow() const;
+ HighlightRule ruleFromDialog();
+ void ruleToDialog(const HighlightRule& rule);
+
+ Ui::QtHighlightEditor ui_;
+ QtSettingsProvider* settings_;
+ HighlightManager* highlightManager_;
+ QtSuggestingJIDInput* jid_;
+ int previousRow_;
+ };
+
+}
diff --git a/Swift/QtUI/QtHighlightEditor.ui b/Swift/QtUI/QtHighlightEditor.ui
new file mode 100644
index 0000000..09a7297
--- /dev/null
+++ b/Swift/QtUI/QtHighlightEditor.ui
@@ -0,0 +1,446 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtHighlightEditor</class>
+ <widget class="QWidget" name="QtHighlightEditor">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>439</width>
+ <height>836</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>439</width>
+ <height>836</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Incoming messages are checked against the following rules. First rule that matches will be executed.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="listWidget"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="newButton">
+ <property name="text">
+ <string>New Rule</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="deleteButton">
+ <property name="text">
+ <string>Remove Rule</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moveUpButton">
+ <property name="text">
+ <string>Move Up</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moveDownButton">
+ <property name="text">
+ <string>Move Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Apply Rule To</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QRadioButton" name="chatRadio">
+ <property name="text">
+ <string>Chats</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="roomRadio">
+ <property name="text">
+ <string>Rooms</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>246</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Rule Conditions</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QRadioButton" name="allMsgRadio">
+ <property name="text">
+ <string>Apply to all messages</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="nickIsKeyword">
+ <property name="text">
+ <string>Only messages mentioning me</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="senderRadio">
+ <property name="text">
+ <string>Messages from this sender:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="senderName">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinimumSize</enum>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="dummySenderName"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="keywordRadio">
+ <property name="text">
+ <string>Messages containing this keyword:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="keyword"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="matchPartialWords">
+ <property name="text">
+ <string>Match keyword within longer words</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="matchCase">
+ <property name="text">
+ <string>Keyword is case sensitive</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Highlight Action</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QRadioButton" name="noColorRadio">
+ <property name="text">
+ <string>No Highlight</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="defaultColorRadio">
+ <property name="text">
+ <string>Default Color</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="customColorRadio">
+ <property name="text">
+ <string>Custom Color</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="Swift::QtColorToolButton" name="foregroundColor">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Text</string>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Swift::QtColorToolButton" name="backgroundColor">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Background</string>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Sound Action</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QRadioButton" name="noSoundRadio">
+ <property name="text">
+ <string>No Sound</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="defaultSoundRadio">
+ <property name="text">
+ <string>Default Sound</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="customSoundRadio">
+ <property name="text">
+ <string>Custom Sound</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="soundFile">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="readOnly">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="soundFileButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>Swift::QtColorToolButton</class>
+ <extends>QToolButton</extends>
+ <header>QtColorToolButton.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Swift/QtUI/QtHighlightRuleWidget.cpp b/Swift/QtUI/QtHighlightRuleWidget.cpp
deleted file mode 100644
index 9c0df5e..0000000
--- a/Swift/QtUI/QtHighlightRuleWidget.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-#include <QDataWidgetMapper>
-#include <QStringListModel>
-#include <QFileDialog>
-
-#include <Swift/QtUI/QtHighlightRuleWidget.h>
-#include <Swift/QtUI/QtHighlightRulesItemModel.h>
-
-namespace Swift {
-
-QtHighlightRuleWidget::QtHighlightRuleWidget(QWidget* parent)
- : QWidget(parent)
-{
- ui_.setupUi(this);
-
- QStringList applyToItems;
- for (int i = 0; i < QtHighlightRulesItemModel::ApplyToEOL; ++i) {
- applyToItems << QtHighlightRulesItemModel::getApplyToString(i);
- }
- QStringListModel * applyToModel = new QStringListModel(applyToItems, this);
- ui_.applyTo->setModel(applyToModel);
-
- connect(ui_.highlightText, SIGNAL(toggled(bool)), SLOT(onHighlightTextToggled(bool)));
- connect(ui_.customColors, SIGNAL(toggled(bool)), SLOT(onCustomColorsToggled(bool)));
- connect(ui_.playSound, SIGNAL(toggled(bool)), SLOT(onPlaySoundToggled(bool)));
- connect(ui_.customSound, SIGNAL(toggled(bool)), SLOT(onCustomSoundToggled(bool)));
- connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(onSoundFileButtonClicked()));
-
- mapper_ = new QDataWidgetMapper(this);
- hasValidIndex_ = false;
- model_ = NULL;
-}
-
-QtHighlightRuleWidget::~QtHighlightRuleWidget()
-{
-}
-
-/** Widget does not gain ownership over the model */
-void QtHighlightRuleWidget::setModel(QtHighlightRulesItemModel* model)
-{
- model_ = model;
- mapper_->setModel(model_);
-}
-
-void QtHighlightRuleWidget::setActiveIndex(const QModelIndex& index)
-{
- if (index.isValid()) {
- if (!hasValidIndex_) {
- mapper_->addMapping(ui_.applyTo, QtHighlightRulesItemModel::ApplyTo, "currentIndex");
- mapper_->addMapping(ui_.senders, QtHighlightRulesItemModel::Sender, "plainText");
- mapper_->addMapping(ui_.keywords, QtHighlightRulesItemModel::Keyword, "plainText");
- mapper_->addMapping(ui_.nickIsKeyword, QtHighlightRulesItemModel::NickIsKeyword);
- mapper_->addMapping(ui_.matchCase, QtHighlightRulesItemModel::MatchCase);
- mapper_->addMapping(ui_.matchWholeWords, QtHighlightRulesItemModel::MatchWholeWords);
- mapper_->addMapping(ui_.highlightText, QtHighlightRulesItemModel::HighlightText);
- mapper_->addMapping(ui_.foreground, QtHighlightRulesItemModel::TextColor, "color");
- mapper_->addMapping(ui_.background, QtHighlightRulesItemModel::TextBackground, "color");
- mapper_->addMapping(ui_.playSound, QtHighlightRulesItemModel::PlaySound);
- mapper_->addMapping(ui_.soundFile, QtHighlightRulesItemModel::SoundFile);
- }
- mapper_->setCurrentModelIndex(index);
- ui_.customColors->setChecked(ui_.foreground->getColor().isValid() || ui_.background->getColor().isValid());
- ui_.customSound->setChecked(!ui_.soundFile->text().isEmpty());
- ui_.applyTo->focusWidget();
- } else {
- if (hasValidIndex_) {
- mapper_->clearMapping();
- }
- }
-
- hasValidIndex_ = index.isValid();
-}
-
-void QtHighlightRuleWidget::onCustomColorsToggled(bool enabled)
-{
- if (!enabled) {
- ui_.foreground->setColor(QColor());
- ui_.background->setColor(QColor());
- }
- ui_.foreground->setEnabled(enabled);
- ui_.background->setEnabled(enabled);
-}
-
-void QtHighlightRuleWidget::onCustomSoundToggled(bool enabled)
-{
- if (enabled) {
- if (ui_.soundFile->text().isEmpty()) {
- onSoundFileButtonClicked();
- }
- } else {
- ui_.soundFile->clear();
- }
- ui_.soundFile->setEnabled(enabled);
- ui_.soundFileButton->setEnabled(enabled);
-}
-
-void QtHighlightRuleWidget::onSoundFileButtonClicked()
-{
- QString s = QFileDialog::getOpenFileName(this, tr("Choose sound file"), QString(), tr("Sound files (*.wav)"));
- if (!s.isEmpty()) {
- ui_.soundFile->setText(s);
- }
-}
-
-void QtHighlightRuleWidget::onHighlightTextToggled(bool enabled)
-{
- ui_.customColors->setEnabled(enabled);
-}
-
-void QtHighlightRuleWidget::onPlaySoundToggled(bool enabled)
-{
- ui_.customSound->setEnabled(enabled);
-}
-
-void QtHighlightRuleWidget::save()
-{
- if (hasValidIndex_) {
- mapper_->submit();
- }
-}
-
-void QtHighlightRuleWidget::revert()
-{
- if (hasValidIndex_) {
- mapper_->revert();
- }
-}
-
-}
diff --git a/Swift/QtUI/QtHighlightRuleWidget.h b/Swift/QtUI/QtHighlightRuleWidget.h
deleted file mode 100644
index 8a59a14..0000000
--- a/Swift/QtUI/QtHighlightRuleWidget.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-#pragma once
-
-#include <QWidget>
-#include <QModelIndex>
-
-#include <Swift/QtUI/ui_QtHighlightRuleWidget.h>
-
-class QDataWidgetMapper;
-
-namespace Swift {
-
- class QtHighlightRulesItemModel;
-
- class QtHighlightRuleWidget : public QWidget
- {
- Q_OBJECT
-
- public:
- explicit QtHighlightRuleWidget(QWidget* parent = NULL);
- ~QtHighlightRuleWidget();
-
- void setModel(QtHighlightRulesItemModel* model);
-
- public slots:
- void setActiveIndex(const QModelIndex&);
- void save();
- void revert();
-
- private slots:
- void onHighlightTextToggled(bool);
- void onCustomColorsToggled(bool);
- void onPlaySoundToggled(bool);
- void onCustomSoundToggled(bool);
- void onSoundFileButtonClicked();
-
- private:
- QDataWidgetMapper * mapper_;
- QtHighlightRulesItemModel * model_;
- bool hasValidIndex_;
- Ui::QtHighlightRuleWidget ui_;
- };
-
-}
diff --git a/Swift/QtUI/QtHighlightRuleWidget.ui b/Swift/QtUI/QtHighlightRuleWidget.ui
deleted file mode 100644
index 9c465f9..0000000
--- a/Swift/QtUI/QtHighlightRuleWidget.ui
+++ /dev/null
@@ -1,260 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>QtHighlightRuleWidget</class>
- <widget class="QWidget" name="QtHighlightRuleWidget">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>361</width>
- <height>524</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Rule conditions</string>
- </property>
- <layout class="QFormLayout" name="formLayout">
- <property name="fieldGrowthPolicy">
- <enum>QFormLayout::ExpandingFieldsGrow</enum>
- </property>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Choose when this rule should be applied.
-If you want to provide more than one sender or keyword, input them in separate lines.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0" colspan="2">
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>&amp;Apply to:</string>
- </property>
- <property name="buddy">
- <cstring>applyTo</cstring>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QComboBox" name="applyTo"/>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>&amp;Senders:</string>
- </property>
- <property name="buddy">
- <cstring>senders</cstring>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QPlainTextEdit" name="senders"/>
- </item>
- <item row="4" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>&amp;Keywords:</string>
- </property>
- <property name="buddy">
- <cstring>keywords</cstring>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QPlainTextEdit" name="keywords"/>
- </item>
- <item row="5" column="1">
- <widget class="QCheckBox" name="nickIsKeyword">
- <property name="text">
- <string>Treat &amp;nick as a keyword (in MUC)</string>
- </property>
- </widget>
- </item>
- <item row="6" column="1">
- <widget class="QCheckBox" name="matchWholeWords">
- <property name="text">
- <string>Match whole &amp;words</string>
- </property>
- </widget>
- </item>
- <item row="7" column="1">
- <widget class="QCheckBox" name="matchCase">
- <property name="text">
- <string>Match &amp;case</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Actions</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QCheckBox" name="highlightText">
- <property name="text">
- <string>&amp;Highlight text</string>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>28</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QCheckBox" name="customColors">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Custom c&amp;olors:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="Swift::QtColorToolButton" name="foreground">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>&amp;Foreground</string>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextBesideIcon</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="Swift::QtColorToolButton" name="background">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>&amp;Background</string>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextBesideIcon</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QCheckBox" name="playSound">
- <property name="text">
- <string>&amp;Play sound</string>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>28</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QCheckBox" name="customSound">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Custom soun&amp;d:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="soundFile">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="soundFileButton">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>101</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>Swift::QtColorToolButton</class>
- <extends>QToolButton</extends>
- <header>QtColorToolButton.h</header>
- </customwidget>
- </customwidgets>
- <resources/>
- <connections/>
-</ui>
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 701170c..b0c1492 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -1,179 +1,179 @@
/*
* Copyright (c) 2010-2014 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/QtUI/QtUIFactory.h>
#include <QSplitter>
#include <Swift/QtUI/QtXMLConsoleWidget.h>
#include <Swift/QtUI/QtChatTabs.h>
#include <Swift/QtUI/QtMainWindow.h>
#include <Swift/QtUI/QtLoginWindow.h>
#include <Swift/QtUI/QtSystemTray.h>
#include <Swift/QtUI/QtSettingsProvider.h>
#include <Swift/QtUI/QtMainWindow.h>
#include <Swift/QtUI/QtChatWindow.h>
#include <Swift/QtUI/QtJoinMUCWindow.h>
#include <Swift/QtUI/QtChatWindowFactory.h>
#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/MUCSearch/QtMUCSearchWindow.h>
#include <Swift/QtUI/UserSearch/QtUserSearchWindow.h>
#include <Swift/QtUI/QtProfileWindow.h>
#include <Swift/QtUI/QtContactEditWindow.h>
#include <Swift/QtUI/QtAdHocCommandWindow.h>
#include <Swift/QtUI/QtFileTransferListWidget.h>
-#include <Swift/QtUI/QtHighlightEditorWidget.h>
+#include <Swift/QtUI/QtHighlightEditor.h>
#include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h>
#include <Swift/Controllers/Settings/SettingsProviderHierachy.h>
#include <Swift/QtUI/QtUISettingConstants.h>
#include <Swift/QtUI/QtHistoryWindow.h>
#include <Swiften/Whiteboard/WhiteboardSession.h>
#include <Swift/QtUI/QtSingleWindow.h>
#include <Swift/QtUI/QtBlockListEditorWindow.h>
namespace Swift {
QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist), enableAdHocCommandOnJID_(enableAdHocCommandOnJID) {
chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE);
historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE);
}
XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() {
QtXMLConsoleWidget* widget = new QtXMLConsoleWidget();
tabs->addTab(widget);
if (!tabs->isVisible()) {
tabs->show();
}
widget->show();
return widget;
}
HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) {
QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream);
tabs->addTab(window);
if (!tabs->isVisible()) {
tabs->show();
}
connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int)));
window->handleFontResized(historyFontSize_);
window->show();
return window;
}
void QtUIFactory::handleHistoryWindowFontResized(int size) {
historyFontSize_ = size;
settings->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size);
}
FileTransferListWidget* QtUIFactory::createFileTransferListWidget() {
QtFileTransferListWidget* widget = new QtFileTransferListWidget();
tabs->addTab(widget);
if (!tabs->isVisible()) {
tabs->show();
}
widget->show();
return widget;
}
MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) {
lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), statusCache, emoticonsExist_, enableAdHocCommandOnJID_);
return lastMainWindow;
}
LoginWindow* QtUIFactory::createLoginWindow(UIEventStream* eventStream) {
loginWindow = new QtLoginWindow(eventStream, settings, timerFactory_);
if (netbookSplitter) {
netbookSplitter->insertAtFront(loginWindow);
}
connect(systemTray, SIGNAL(clicked()), loginWindow, SLOT(toggleBringToFront()));
#ifndef SWIFT_MOBILE
QVariant loginWindowGeometryVariant = qtOnlySettings->getQSettings()->value("loginWindowGeometry");
if (loginWindowGeometryVariant.isValid()) {
loginWindow->restoreGeometry(loginWindowGeometryVariant.toByteArray());
}
connect(loginWindow, SIGNAL(geometryChanged()), this, SLOT(handleLoginWindowGeometryChanged()));
if (startMinimized) loginWindow->hide();
#endif
return loginWindow;
}
void QtUIFactory::handleLoginWindowGeometryChanged() {
qtOnlySettings->getQSettings()->setValue("loginWindowGeometry", loginWindow->saveGeometry());
}
EventWindow* QtUIFactory::createEventWindow() {
return lastMainWindow->getEventWindow();
}
ChatListWindow* QtUIFactory::createChatListWindow(UIEventStream*) {
return lastMainWindow->getChatListWindow();
}
MUCSearchWindow* QtUIFactory::createMUCSearchWindow() {
return new QtMUCSearchWindow();
}
ChatWindow* QtUIFactory::createChatWindow(const JID& contact, UIEventStream* eventStream) {
QtChatWindow* window = dynamic_cast<QtChatWindow*>(chatWindowFactory->createChatWindow(contact, eventStream));
chatWindows.push_back(window);
std::vector<QPointer<QtChatWindow> > deletions;
foreach (QPointer<QtChatWindow> existingWindow, chatWindows) {
if (existingWindow.isNull()) {
deletions.push_back(existingWindow);
} else {
connect(window, SIGNAL(fontResized(int)), existingWindow, SLOT(handleFontResized(int)));
connect(existingWindow, SIGNAL(fontResized(int)), window, SLOT(handleFontResized(int)));
}
}
foreach (QPointer<QtChatWindow> deletedWindow, deletions) {
chatWindows.erase(std::remove(chatWindows.begin(), chatWindows.end(), deletedWindow), chatWindows.end());
}
connect(window, SIGNAL(fontResized(int)), this, SLOT(handleChatWindowFontResized(int)));
window->handleFontResized(chatFontSize);
return window;
}
void QtUIFactory::handleChatWindowFontResized(int size) {
chatFontSize = size;
settings->storeSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE, size);
}
UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) {
return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings);
}
JoinMUCWindow* QtUIFactory::createJoinMUCWindow(UIEventStream* uiEventStream) {
return new QtJoinMUCWindow(uiEventStream);
}
ProfileWindow* QtUIFactory::createProfileWindow() {
return new QtProfileWindow();
}
ContactEditWindow* QtUIFactory::createContactEditWindow() {
return new QtContactEditWindow();
}
WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession) {
return new QtWhiteboardWindow(whiteboardSession);
}
-HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() {
- return new QtHighlightEditorWidget();
+HighlightEditorWindow* QtUIFactory::createHighlightEditorWindow() {
+ return new QtHighlightEditor(qtOnlySettings);
}
BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() {
return new QtBlockListEditorWindow();
}
AdHocCommandWindow* QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) {
return new QtAdHocCommandWindow(command);
}
}
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index 4c50572..9c07e76 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -1,78 +1,78 @@
/*
* Copyright (c) 2010-2014 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <QObject>
#include <QPointer>
#include <Swift/Controllers/UIInterfaces/UIFactory.h>
class QSplitter;
namespace Swift {
class QtSettingsProvider;
class SettingsProviderHierachy;
class QtChatTabs;
class QtSystemTray;
class QtLoginWindow;
class QtMainWindow;
class QtChatTheme;
class QtChatWindowFactory;
class QtChatWindow;
class TimerFactory;
class historyWindow_;
class WhiteboardSession;
class StatusCache;
class QtSingleWindow;
class QtUIFactory : public QObject, public UIFactory {
Q_OBJECT
public:
QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID);
virtual XMLConsoleWidget* createXMLConsoleWidget();
virtual HistoryWindow* createHistoryWindow(UIEventStream*);
virtual MainWindow* createMainWindow(UIEventStream* eventStream);
virtual LoginWindow* createLoginWindow(UIEventStream* eventStream);
virtual EventWindow* createEventWindow();
virtual ChatListWindow* createChatListWindow(UIEventStream*);
virtual MUCSearchWindow* createMUCSearchWindow();
virtual ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);
virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups);
virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream);
virtual ProfileWindow* createProfileWindow();
virtual ContactEditWindow* createContactEditWindow();
virtual FileTransferListWidget* createFileTransferListWidget();
virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession);
- virtual HighlightEditorWidget* createHighlightEditorWidget();
+ virtual HighlightEditorWindow* createHighlightEditorWindow();
virtual BlockListEditorWidget* createBlockListEditorWidget();
virtual AdHocCommandWindow* createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
private slots:
void handleLoginWindowGeometryChanged();
void handleChatWindowFontResized(int);
void handleHistoryWindowFontResized(int);
private:
SettingsProviderHierachy* settings;
QtSettingsProvider* qtOnlySettings;
QtChatTabs* tabs;
QtSingleWindow* netbookSplitter;
QtSystemTray* systemTray;
QtChatWindowFactory* chatWindowFactory;
TimerFactory* timerFactory_;
QtMainWindow* lastMainWindow;
QtLoginWindow* loginWindow;
StatusCache* statusCache;
std::vector<QPointer<QtChatWindow> > chatWindows;
bool startMinimized;
int chatFontSize;
int historyFontSize_;
bool emoticonsExist_;
bool enableAdHocCommandOnJID_;
};
}
diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp
index 23bc099..1486293 100644
--- a/Swift/QtUI/QtWebKitChatView.cpp
+++ b/Swift/QtUI/QtWebKitChatView.cpp
@@ -1,942 +1,945 @@
/*
* Copyright (c) 2010-2014 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include "QtWebKitChatView.h"
#include <QtDebug>
#include <QEventLoop>
#include <QFile>
#include <QDesktopServices>
#include <QVBoxLayout>
#include <QWebFrame>
#include <QKeyEvent>
#include <QStackedWidget>
#include <QTimer>
#include <QMessageBox>
#include <QApplication>
#include <QInputDialog>
#include <QFileDialog>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/FileSize.h>
#include <Swiften/StringCodecs/Base64.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/QtUI/QtWebView.h>
#include <Swift/QtUI/QtChatWindow.h>
#include <Swift/QtUI/QtChatWindowJSBridge.h>
#include <Swift/QtUI/QtScaledAvatarCache.h>
#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/QtUtilities.h>
#include <Swift/QtUI/MessageSnippet.h>
#include <Swift/QtUI/SystemMessageSnippet.h>
namespace Swift {
const QString QtWebKitChatView::ButtonWhiteboardSessionCancel = QString("whiteboard-cancel");
const QString QtWebKitChatView::ButtonWhiteboardSessionAcceptRequest = QString("whiteboard-acceptrequest");
const QString QtWebKitChatView::ButtonWhiteboardShowWindow = QString("whiteboard-showwindow");
const QString QtWebKitChatView::ButtonFileTransferCancel = QString("filetransfer-cancel");
const QString QtWebKitChatView::ButtonFileTransferSetDescription = QString("filetransfer-setdescription");
const QString QtWebKitChatView::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest");
const QString QtWebKitChatView::ButtonFileTransferAcceptRequest = QString("filetransfer-acceptrequest");
const QString QtWebKitChatView::ButtonMUCInvite = QString("mucinvite");
QtWebKitChatView::QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QtChatView(parent), window_(window), eventStream_(eventStream), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll), previousMessageKind_(PreviosuMessageWasNone), previousMessageWasSelf_(false), showEmoticons_(false), insertingLastLine_(false), idCounter_(0) {
theme_ = theme;
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0,0,0,0);
webView_ = new QtWebView(this);
connect(webView_, SIGNAL(linkClicked(const QUrl&)), SLOT(handleLinkClicked(const QUrl&)));
connect(webView_, SIGNAL(loadFinished(bool)), SLOT(handleViewLoadFinished(bool)));
connect(webView_, SIGNAL(gotFocus()), SIGNAL(gotFocus()));
connect(webView_, SIGNAL(clearRequested()), SLOT(handleClearRequested()));
connect(webView_, SIGNAL(fontGrowRequested()), SLOT(increaseFontSize()));
connect(webView_, SIGNAL(fontShrinkRequested()), SLOT(decreaseFontSize()));
#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC)
/* To give a border on Linux, where it looks bad without */
QStackedWidget* stack = new QStackedWidget(this);
stack->addWidget(webView_);
stack->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
stack->setLineWidth(2);
mainLayout->addWidget(stack);
#else
mainLayout->addWidget(webView_);
#endif
#ifdef SWIFT_EXPERIMENTAL_FT
setAcceptDrops(true);
#endif
webPage_ = new QWebPage(this);
webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
if (Log::getLogLevel() == Log::debug) {
webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
}
webView_->setPage(webPage_);
connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard()));
connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&)));
viewReady_ = false;
isAtBottom_ = true;
resetView();
jsBridge = new QtChatWindowJSBridge();
addToJSEnvironment("chatwindow", jsBridge);
connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString,QString,QString)));
}
QtWebKitChatView::~QtWebKitChatView() {
delete jsBridge;
}
void QtWebKitChatView::handleClearRequested() {
QMessageBox messageBox(this);
messageBox.setWindowTitle(tr("Clear log"));
messageBox.setText(tr("You are about to clear the contents of your chat log."));
messageBox.setInformativeText(tr("Are you sure?"));
messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
messageBox.setDefaultButton(QMessageBox::Yes);
int button = messageBox.exec();
if (button == QMessageBox::Yes) {
logCleared();
resetView();
}
}
void QtWebKitChatView::handleKeyPressEvent(QKeyEvent* event) {
webView_->keyPressEvent(event);
}
void QtWebKitChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) {
if (viewReady_) {
addToDOM(snippet);
} else {
/* If this asserts, the previous queuing code was necessary and should be reinstated */
assert(false);
}
}
void QtWebKitChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) {
// save scrollbar maximum value
if (!topMessageAdded_) {
scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
}
topMessageAdded_ = true;
QWebElement continuationElement = firstElement_.findFirst("#insert");
bool insert = snippet->getAppendToPrevious();
bool fallback = continuationElement.isNull();
boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
QWebElement newElement = snippetToDOM(newSnippet);
if (insert && !fallback) {
Q_ASSERT(!continuationElement.isNull());
continuationElement.replace(newElement);
} else {
continuationElement.removeFromDocument();
topInsertPoint_.prependOutside(newElement);
}
firstElement_ = newElement;
if (lastElement_.isNull()) {
lastElement_ = firstElement_;
}
if (fontSizeSteps_ != 0) {
double size = 1.0 + 0.2 * fontSizeSteps_;
QString sizeString(QString().setNum(size, 'g', 3) + "em");
const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("font-size", sizeString);
}
}
}
QWebElement QtWebKitChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) {
QWebElement newElement = newInsertPoint_.clone();
newElement.setInnerXml(snippet->getContent());
Q_ASSERT(!newElement.isNull());
return newElement;
}
void QtWebKitChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
//qDebug() << snippet->getContent();
rememberScrolledToBottom();
bool insert = snippet->getAppendToPrevious();
QWebElement continuationElement = lastElement_.findFirst("#insert");
bool fallback = insert && continuationElement.isNull();
boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
QWebElement newElement = snippetToDOM(newSnippet);
if (insert && !fallback) {
Q_ASSERT(!continuationElement.isNull());
continuationElement.replace(newElement);
} else {
continuationElement.removeFromDocument();
newInsertPoint_.prependOutside(newElement);
}
lastElement_ = newElement;
if (fontSizeSteps_ != 0) {
double size = 1.0 + 0.2 * fontSizeSteps_;
QString sizeString(QString().setNum(size, 'g', 3) + "em");
const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("font-size", sizeString);
}
}
//qDebug() << "-----------------";
//qDebug() << webPage_->mainFrame()->toHtml();
}
void QtWebKitChatView::addLastSeenLine() {
/* if the line is added we should break the snippet */
insertingLastLine_ = true;
if (lineSeparator_.isNull()) {
lineSeparator_ = newInsertPoint_.clone();
lineSeparator_.setInnerXml(QString("<hr/>"));
newInsertPoint_.prependOutside(lineSeparator_);
}
else {
QWebElement lineSeparatorC = lineSeparator_.clone();
lineSeparatorC.removeFromDocument();
}
newInsertPoint_.prependOutside(lineSeparator_);
}
void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const ChatWindow::TimestampBehaviour timestampBehaviour) {
assert(viewReady_);
rememberScrolledToBottom();
assert(!lastElement_.isNull());
QWebElement replace = lastElement_.findFirst("span.swift_message");
assert(!replace.isNull());
QString old = lastElement_.toOuterXml();
replace.setInnerXml(ChatSnippet::escape(newMessage));
if (timestampBehaviour == ChatWindow::UpdateTimestamp) {
replace = lastElement_.findFirst("span.swift_time");
assert(!replace.isNull());
replace.setInnerXml(ChatSnippet::timeToEscapedString(QDateTime::currentDateTime()));
}
}
void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const QString& note) {
rememberScrolledToBottom();
replaceLastMessage(newMessage, ChatWindow::KeepTimestamp);
QWebElement replace = lastElement_.findFirst("span.swift_time");
assert(!replace.isNull());
replace.setInnerXml(ChatSnippet::escape(note));
}
QString QtWebKitChatView::getLastSentMessage() {
return lastElement_.toPlainText();
}
void QtWebKitChatView::addToJSEnvironment(const QString& name, QObject* obj) {
webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj);
}
void QtWebKitChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) {
rememberScrolledToBottom();
QWebElement message = document_.findFirst("#" + id);
if (!message.isNull()) {
QWebElement replaceContent = message.findFirst("span.swift_inner_message");
assert(!replaceContent.isNull());
QString old = replaceContent.toOuterXml();
replaceContent.setInnerXml(ChatSnippet::escape(newMessage));
QWebElement replaceTime = message.findFirst("span.swift_time");
assert(!replaceTime.isNull());
old = replaceTime.toOuterXml();
replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime))));
}
else {
qWarning() << "Trying to replace element with id " << id << " but it's not there.";
}
}
void QtWebKitChatView::showEmoticons(bool show) {
showEmoticons_ = show;
{
const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("display", show ? "inline" : "none");
}
}
{
const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("display", show ? "none" : "inline");
}
}
}
void QtWebKitChatView::copySelectionToClipboard() {
if (!webPage_->selectedText().isEmpty()) {
webPage_->triggerAction(QWebPage::Copy);
}
}
void QtWebKitChatView::setAckXML(const QString& id, const QString& xml) {
QWebElement message = document_.findFirst("#" + id);
/* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */
if (message.isNull()) return;
QWebElement ackElement = message.findFirst("span.swift_ack");
assert(!ackElement.isNull());
ackElement.setInnerXml(xml);
}
void QtWebKitChatView::setReceiptXML(const QString& id, const QString& xml) {
QWebElement message = document_.findFirst("#" + id);
if (message.isNull()) return;
QWebElement receiptElement = message.findFirst("span.swift_receipt");
assert(!receiptElement.isNull());
receiptElement.setInnerXml(xml);
}
void QtWebKitChatView::displayReceiptInfo(const QString& id, bool showIt) {
QWebElement message = document_.findFirst("#" + id);
if (message.isNull()) return;
QWebElement receiptElement = message.findFirst("span.swift_receipt");
assert(!receiptElement.isNull());
receiptElement.setStyleProperty("display", showIt ? "inline" : "none");
}
void QtWebKitChatView::rememberScrolledToBottom() {
isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1);
}
void QtWebKitChatView::scrollToBottom() {
isAtBottom_ = true;
webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical));
webView_->update(); /* Work around redraw bug in some versions of Qt. */
}
void QtWebKitChatView::handleFrameSizeChanged() {
if (topMessageAdded_) {
// adjust new scrollbar position
int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_);
topMessageAdded_ = false;
}
if (isAtBottom_ && !disableAutoScroll_) {
scrollToBottom();
}
}
void QtWebKitChatView::handleLinkClicked(const QUrl& url) {
QDesktopServices::openUrl(url);
}
void QtWebKitChatView::handleViewLoadFinished(bool ok) {
Q_ASSERT(ok);
viewReady_ = true;
}
void QtWebKitChatView::increaseFontSize(int numSteps) {
//qDebug() << "Increasing";
fontSizeSteps_ += numSteps;
emit fontResized(fontSizeSteps_);
}
void QtWebKitChatView::decreaseFontSize() {
fontSizeSteps_--;
if (fontSizeSteps_ < 0) {
fontSizeSteps_ = 0;
}
emit fontResized(fontSizeSteps_);
}
void QtWebKitChatView::resizeFont(int fontSizeSteps) {
fontSizeSteps_ = fontSizeSteps;
double size = 1.0 + 0.2 * fontSizeSteps_;
QString sizeString(QString().setNum(size, 'g', 3) + "em");
//qDebug() << "Setting to " << sizeString;
const QWebElementCollection spans = document_.findAll("span.swift_resizable");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("font-size", sizeString);
}
webView_->setFontSizeIsMinimal(size == 1.0);
}
void QtWebKitChatView::resetView() {
lastElement_ = QWebElement();
firstElement_ = lastElement_;
topMessageAdded_ = false;
scrollBarMaximum_ = 0;
QString pageHTML = theme_->getTemplate();
pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3");
pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getBase());
if (pageHTML.count("%@") > 3) {
pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getMainCSS());
}
pageHTML.replace(pageHTML.indexOf("%@"), 2, "Variants/Blue on Green.css");
pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*headerSnippet.getContent()*/);
pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*footerSnippet.getContent()*/);
QEventLoop syncLoop;
connect(webView_, SIGNAL(loadFinished(bool)), &syncLoop, SLOT(quit()));
webPage_->mainFrame()->setHtml(pageHTML);
while (!viewReady_) {
QTimer t;
t.setSingleShot(true);
connect(&t, SIGNAL(timeout()), &syncLoop, SLOT(quit()));
t.start(50);
syncLoop.exec();
}
document_ = webPage_->mainFrame()->documentElement();
resetTopInsertPoint();
QWebElement chatElement = document_.findFirst("#Chat");
newInsertPoint_ = chatElement.clone();
newInsertPoint_.setOuterXml("<div id='swift_insert'/>");
chatElement.appendInside(newInsertPoint_);
Q_ASSERT(!newInsertPoint_.isNull());
scrollToBottom();
connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection);
}
static QWebElement findElementWithID(QWebElement document, QString elementName, QString id) {
QWebElementCollection elements = document.findAll(elementName);
Q_FOREACH(QWebElement element, elements) {
if (element.attribute("id") == id) {
return element;
}
}
return QWebElement();
}
void QtWebKitChatView::setFileTransferProgress(QString id, const int percentageDone) {
QWebElement ftElement = findElementWithID(document_, "div", id);
if (ftElement.isNull()) {
SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!" << std::endl;
return;
}
QWebElement progressBar = ftElement.findFirst("div.progressbar");
progressBar.setStyleProperty("width", QString::number(percentageDone) + "%");
QWebElement progressBarValue = ftElement.findFirst("div.progressbar-value");
progressBarValue.setInnerXml(QString::number(percentageDone) + " %");
}
void QtWebKitChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) {
QWebElement ftElement = findElementWithID(document_, "div", id);
if (ftElement.isNull()) {
SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id) << std::endl;
return;
}
QString newInnerHTML = "";
if (state == ChatWindow::WaitingForAccept) {
newInnerHTML = tr("Waiting for other side to accept the transfer.") + "<br/>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
}
if (state == ChatWindow::Negotiating) {
// replace with text "Negotiaging" + Cancel button
newInnerHTML = tr("Negotiating...") + "<br/>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
}
else if (state == ChatWindow::Transferring) {
// progress bar + Cancel Button
newInnerHTML = "<div style=\"position: relative; width: 90%; height: 20px; border: 2px solid grey; -webkit-border-radius: 10px;\">"
"<div class=\"progressbar\" style=\"width: 0%; height: 100%; background: #AAA; -webkit-border-radius: 6px;\">"
"<div class=\"progressbar-value\" style=\"position: absolute; top: 0px; left: 0px; width: 100%; text-align: center; padding-top: 2px;\">"
"0%"
"</div>"
"</div>"
"</div>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
}
else if (state == ChatWindow::Canceled) {
newInnerHTML = tr("Transfer has been canceled!");
}
else if (state == ChatWindow::Finished) {
// text "Successful transfer"
newInnerHTML = tr("Transfer completed successfully.");
}
else if (state == ChatWindow::FTFailed) {
newInnerHTML = tr("Transfer failed.");
}
ftElement.setInnerXml(newInnerHTML);
}
void QtWebKitChatView::setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state) {
QWebElement divElement = findElementWithID(document_, "div", id);
QString newInnerHTML;
if (state == ChatWindow::WhiteboardAccepted) {
newInnerHTML = tr("Started whiteboard chat") + "<br/>" + buildChatWindowButton(tr("Show whiteboard"), ButtonWhiteboardShowWindow, id);
} else if (state == ChatWindow::WhiteboardTerminated) {
newInnerHTML = tr("Whiteboard chat has been canceled");
} else if (state == ChatWindow::WhiteboardRejected) {
newInnerHTML = tr("Whiteboard chat request has been rejected");
}
divElement.setInnerXml(newInnerHTML);
}
void QtWebKitChatView::setMUCInvitationJoined(QString id) {
QWebElement divElement = findElementWithID(document_, "div", id);
QWebElement buttonElement = findElementWithID(divElement, "input", "mucinvite");
if (!buttonElement.isNull()) {
buttonElement.setAttribute("value", tr("Return to room"));
}
}
void QtWebKitChatView::handleScrollRequested(int, int dy, const QRect&) {
rememberScrolledToBottom();
int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy;
emit scrollRequested(pos);
if (pos == 0) {
emit scrollReachedTop();
}
else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) {
emit scrollReachedBottom();
}
}
int QtWebKitChatView::getSnippetPositionByDate(const QDate& date) {
QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate));
return message.geometry().top();
}
void QtWebKitChatView::resetTopInsertPoint() {
QWebElement continuationElement = firstElement_.findFirst("#insert");
continuationElement.removeFromDocument();
firstElement_ = QWebElement();
topInsertPoint_.removeFromDocument();
QWebElement chatElement = document_.findFirst("#Chat");
topInsertPoint_ = chatElement.clone();
topInsertPoint_.setOuterXml("<div id='swift_insert'/>");
chatElement.prependInside(topInsertPoint_);
}
std::string QtWebKitChatView::addMessage(
const ChatWindow::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 addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message));
}
+QString QtWebKitChatView::getHighlightSpanStart(const std::string& text, const std::string& background) {
+ QString ecsapeColor = QtUtilities::htmlEscape(P2QSTRING(text));
+ QString escapeBackground = QtUtilities::htmlEscape(P2QSTRING(background));
+ if (ecsapeColor.isEmpty()) {
+ ecsapeColor = "black";
+ }
+ if (escapeBackground.isEmpty()) {
+ escapeBackground = "yellow";
+ }
+ return QString("<span style=\"color: %1; background: %2\">").arg(ecsapeColor).arg(escapeBackground);
+}
+
+QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) {
+ return getHighlightSpanStart(highlight.getTextColor(), highlight.getTextBackground());
+}
+
QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) {
QString result;
foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) {
boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
boost::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart;
boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart;
boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart;
if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text));
text.replace("\n","<br/>");
result += text;
continue;
}
if ((uriPart = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) {
QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target));
result += "<a href='" + uri + "' >" + uri + "</a>";
continue;
}
if ((emoticonPart = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(part))) {
QString textStyle = showEmoticons_ ? "style='display:none'" : "";
QString imageStyle = showEmoticons_ ? "" : "style='display:none'";
result += "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + P2QSTRING(emoticonPart->imagePath) + "'/></span><span class='swift_emoticon_text' " + textStyle + ">" + QtUtilities::htmlEscape(P2QSTRING(emoticonPart->alternativeText)) + "</span>";
continue;
}
if ((highlightPart = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) {
- //FIXME: Maybe do something here. Anything, really.
+ QString spanStart = getHighlightSpanStart(highlightPart->foregroundColor, highlightPart->backgroundColor);
+ result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + "</span>";
continue;
}
}
return result;
}
-
-QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) {
- QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor()));
- QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground()));
- if (color.isEmpty()) {
- color = "black";
- }
- if (background.isEmpty()) {
- background = "yellow";
- }
-
- return QString("<span style=\"color: %1; background: %2\">").arg(color).arg(background);
-}
-
std::string QtWebKitChatView::addMessage(
const QString& message,
const std::string& senderName,
bool senderIsSelf,
boost::shared_ptr<SecurityLabel> label,
const std::string& avatarPath,
const QString& style,
const boost::posix_time::ptime& time,
const HighlightAction& highlight,
ChatSnippet::Direction direction) {
QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str());
QString htmlString;
if (label) {
htmlString = QString("<span style=\"border: thin dashed grey; padding-left: .5em; padding-right: .5em; color: %1; background-color: %2; font-size: 90%; margin-right: .5em; \" class='swift_label'>").arg(QtUtilities::htmlEscape(P2QSTRING(label->getForegroundColor()))).arg(QtUtilities::htmlEscape(P2QSTRING(label->getBackgroundColor())));
htmlString += QString("%1</span> ").arg(QtUtilities::htmlEscape(P2QSTRING(label->getDisplayMarking())));
}
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ;
bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf);
QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++);
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction));
previousMessageWasSelf_ = senderIsSelf;
previousSenderName_ = P2QSTRING(senderName);
previousMessageKind_ = PreviousMessageWasMessage;
return id;
}
std::string QtWebKitChatView::addAction(const ChatWindow::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 addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message));
}
static QString encodeButtonArgument(const QString& str) {
return QtUtilities::htmlEscape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str)))));
}
static QString decodeButtonArgument(const QString& str) {
return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str))));
}
QString QtWebKitChatView::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3, const QString& arg4, const QString& arg5) {
QRegExp regex("[A-Za-z][A-Za-z0-9\\-\\_]+");
Q_ASSERT(regex.exactMatch(id));
QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\", \"%6\", \"%7\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)).arg(encodeButtonArgument(arg4)).arg(encodeButtonArgument(arg5));
return html;
}
std::string QtWebKitChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) {
SWIFT_LOG(debug) << "addFileTransfer" << std::endl;
QString ft_id = QString("ft%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
QString actionText;
QString htmlString;
QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes));
if (senderIsSelf) {
// outgoing
actionText = tr("Send file");
htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" +
"<div id='" + ft_id + "'>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
buildChatWindowButton(tr("Set Description"), ButtonFileTransferSetDescription, ft_id) +
buildChatWindowButton(tr("Send"), ButtonFileTransferSendRequest, ft_id) +
"</div>";
} else {
// incoming
actionText = tr("Receiving file");
htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" +
"<div id='" + ft_id + "'>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) +
"</div>";
}
//addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time());
bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf);
QString qAvatarPath = "qrc:/icons/avatar.png";
std::string id = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++);
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
previousMessageWasSelf_ = senderIsSelf;
previousSenderName_ = P2QSTRING(senderName);
previousMessageKind_ = PreviousMessageWasFileTransfer;
return Q2PSTRING(ft_id);
}
void QtWebKitChatView::setFileTransferProgress(std::string id, const int percentageDone) {
setFileTransferProgress(P2QSTRING(id), percentageDone);
}
void QtWebKitChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg) {
setFileTransferStatus(P2QSTRING(id), state, P2QSTRING(msg));
}
std::string QtWebKitChatView::addWhiteboardRequest(const QString& contact, bool senderIsSelf) {
QString wb_id = QString("wb%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
QString htmlString;
QString actionText;
if (senderIsSelf) {
actionText = tr("Starting whiteboard chat");
htmlString = "<div id='" + wb_id + "'>" + actionText + "<br />"+
buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
"</div>";
} else {
actionText = tr("%1 would like to start a whiteboard chat");
htmlString = "<div id='" + wb_id + "'>" + actionText.arg(QtUtilities::htmlEscape(contact)) + ": <br/>" +
buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
buildChatWindowButton(tr("Accept"), ButtonWhiteboardSessionAcceptRequest, wb_id) +
"</div>";
}
QString qAvatarPath = "qrc:/icons/avatar.png";
std::string id = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++);
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(contact), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, false, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
previousMessageWasSelf_ = false;
previousSenderName_ = contact;
return Q2PSTRING(wb_id);
}
void QtWebKitChatView::setWhiteboardSessionStatus(const std::string& id, const ChatWindow::WhiteboardSessionState state) {
setWhiteboardSessionStatus(P2QSTRING(id), state);
}
void QtWebKitChatView::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3, QString encodedArgument4, QString encodedArgument5) {
QString arg1 = decodeButtonArgument(encodedArgument1);
QString arg2 = decodeButtonArgument(encodedArgument2);
QString arg3 = decodeButtonArgument(encodedArgument3);
QString arg4 = decodeButtonArgument(encodedArgument4);
QString arg5 = decodeButtonArgument(encodedArgument5);
if (id.startsWith(ButtonFileTransferCancel)) {
QString ft_id = arg1;
window_->onFileTransferCancel(Q2PSTRING(ft_id));
}
else if (id.startsWith(ButtonFileTransferSetDescription)) {
QString ft_id = arg1;
bool ok = false;
QString text = QInputDialog::getText(this, tr("File transfer description"),
tr("Description:"), QLineEdit::Normal, "", &ok);
if (ok) {
descriptions_[ft_id] = text;
}
}
else if (id.startsWith(ButtonFileTransferSendRequest)) {
QString ft_id = arg1;
QString text = descriptions_.find(ft_id) == descriptions_.end() ? QString() : descriptions_[ft_id];
window_->onFileTransferStart(Q2PSTRING(ft_id), Q2PSTRING(text));
}
else if (id.startsWith(ButtonFileTransferAcceptRequest)) {
QString ft_id = arg1;
QString filename = arg2;
QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename);
if (!path.isEmpty()) {
window_->onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path));
}
}
else if (id.startsWith(ButtonWhiteboardSessionAcceptRequest)) {
QString id = arg1;
setWhiteboardSessionStatus(id, ChatWindow::WhiteboardAccepted);
window_->onWhiteboardSessionAccept();
}
else if (id.startsWith(ButtonWhiteboardSessionCancel)) {
QString id = arg1;
setWhiteboardSessionStatus(id, ChatWindow::WhiteboardTerminated);
window_->onWhiteboardSessionCancel();
}
else if (id.startsWith(ButtonWhiteboardShowWindow)) {
QString id = arg1;
window_->onWhiteboardWindowShow();
}
else if (id.startsWith(ButtonMUCInvite)) {
QString roomJID = arg1;
QString password = arg2;
QString elementID = arg3;
QString isImpromptu = arg4;
QString isContinuation = arg5;
eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, false, isImpromptu.contains("true"), isContinuation.contains("true")));
setMUCInvitationJoined(elementID);
}
else {
SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl;
}
}
void QtWebKitChatView::addErrorMessage(const ChatWindow::ChatMessage& errorMessage) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString errorMessageHTML(chatMessageToHTML(errorMessage));
addMessageBottom(boost::make_shared<SystemMessageSnippet>("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_, ChatSnippet::getDirection(errorMessage)));
previousMessageWasSelf_ = false;
previousMessageKind_ = PreviousMessageWasSystem;
}
void QtWebKitChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString messageHTML = chatMessageToHTML(message);
addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
previousMessageKind_ = PreviousMessageWasSystem;
}
void QtWebKitChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", highlight);
}
void QtWebKitChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
replaceMessage(chatMessageToHTML(message), id, time, "", highlight);
}
void QtWebKitChatView::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight) {
if (!id.empty()) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString messageHTML(message);
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd;
replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
}
else {
std::cerr << "Trying to replace a message with no id";
}
}
void QtWebKitChatView::addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString messageHTML = chatMessageToHTML(message);
addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
previousMessageKind_ = PreviousMessageWasPresence;
}
void QtWebKitChatView::replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour timestampBehaviour) {
replaceLastMessage(chatMessageToHTML(message), timestampBehaviour);
}
void QtWebKitChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString message;
if (isImpromptu) {
message = QObject::tr("You've been invited to join a chat.") + "\n";
} else {
message = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + "\n";
}
QString htmlString = message;
if (!reason.empty()) {
htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "\n";
}
if (!direct) {
htmlString += QObject::tr("This person may not have really sent this invitation!") + "\n";
}
htmlString = chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING(htmlString)));
QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
htmlString += "<div id='" + id + "'>" +
buildChatWindowButton(chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id, QtUtilities::htmlEscape(isImpromptu ? "true" : "false"), QtUtilities::htmlEscape(isContinuation ? "true" : "false")) +
"</div>";
bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false);
QString qAvatarPath = "qrc:/icons/avatar.png";
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id, ChatSnippet::getDirection(message)));
previousMessageWasSelf_ = false;
previousSenderName_ = P2QSTRING(senderName);
previousMessageKind_ = PreviousMessageWasMUCInvite;
}
void QtWebKitChatView::setAckState(std::string const& id, ChatWindow::AckState state) {
QString xml;
switch (state) {
case ChatWindow::Pending:
xml = "<img src='qrc:/icons/throbber.gif' title='" + tr("This message has not been received by your server yet.") + "'/>";
displayReceiptInfo(P2QSTRING(id), false);
break;
case ChatWindow::Received:
xml = "";
displayReceiptInfo(P2QSTRING(id), true);
break;
case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' title='" + tr("This message may not have been transmitted.") + "'/>"; break;
}
setAckXML(P2QSTRING(id), xml);
}
void QtWebKitChatView::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) {
QString xml;
switch (state) {
case ChatWindow::ReceiptReceived:
xml = "<img src='qrc:/icons/check.png' title='" + tr("The receipt for this message has been received.") + "'/>";
break;
case ChatWindow::ReceiptRequested:
xml = "<img src='qrc:/icons/warn.png' title='" + tr("The receipt for this message has not yet been received. The recipient(s) might not have received this message.") + "'/>";
break;
case ChatWindow::ReceiptFailed:
xml = "<img src='qrc:/icons/error.png' title='" + tr("Failed to transmit message to the receipient(s).") + "'/>";
}
setReceiptXML(P2QSTRING(id), xml);
}
bool QtWebKitChatView::appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) {
bool result = previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName)));
if (insertingLastLine_) {
insertingLastLine_ = false;
return false;
}
return result;
}
ChatSnippet::Direction QtWebKitChatView::getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
if (direction == ChatWindow::DefaultDirection) {
return QCoreApplication::translate("QApplication", "QT_LAYOUT_DIRECTION") == "RTL" ? ChatSnippet::RTL : ChatSnippet::LTR;
}
else {
return ChatSnippet::getDirection(message);
}
}
// void QtWebKitChatView::setShowEmoticons(bool value) {
// showEmoticons_ = value;
// }
}
diff --git a/Swift/QtUI/QtWebKitChatView.h b/Swift/QtUI/QtWebKitChatView.h
index fb6e4da..925ceeb 100644
--- a/Swift/QtUI/QtWebKitChatView.h
+++ b/Swift/QtUI/QtWebKitChatView.h
@@ -1,187 +1,188 @@
/*
- * Copyright (c) 2010-2013 Remko Tronçon
+ * Copyright (c) 2010-2014 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <QString>
#include <QWidget>
#include <QList>
#include <QWebElement>
#include <boost/shared_ptr.hpp>
#include <Swiften/Base/Override.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/QtUI/ChatSnippet.h>
#include <Swift/QtUI/QtChatView.h>
class QWebPage;
class QUrl;
class QDate;
namespace Swift {
class QtWebView;
class QtChatTheme;
class QtChatWindowJSBridge;
class UIEventStream;
class QtChatWindow;
class QtWebKitChatView : public QtChatView {
Q_OBJECT
public:
static const QString ButtonWhiteboardSessionCancel;
static const QString ButtonWhiteboardSessionAcceptRequest;
static const QString ButtonWhiteboardShowWindow;
static const QString ButtonFileTransferCancel;
static const QString ButtonFileTransferSetDescription;
static const QString ButtonFileTransferSendRequest;
static const QString ButtonFileTransferAcceptRequest;
static const QString ButtonMUCInvite;
public:
QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false);
~QtWebKitChatView();
/** Add message to window.
* @return id of added message (for acks).
*/
virtual std::string addMessage(const ChatWindow::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) SWIFTEN_OVERRIDE;
/** Adds action to window.
* @return id of added message (for acks);
*/
virtual std::string addAction(const ChatWindow::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) SWIFTEN_OVERRIDE;
virtual void addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE;
virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE;
virtual void addErrorMessage(const ChatWindow::ChatMessage& message) SWIFTEN_OVERRIDE;
virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE;
virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE;
void replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour timestampBehaviour);
void setAckState(const std::string& id, ChatWindow::AckState state);
virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) SWIFTEN_OVERRIDE;
virtual void setFileTransferProgress(std::string, const int percentageDone) SWIFTEN_OVERRIDE;
virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") SWIFTEN_OVERRIDE;
virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) SWIFTEN_OVERRIDE;
virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) SWIFTEN_OVERRIDE;
virtual void setWhiteboardSessionStatus(const std::string& id, const ChatWindow::WhiteboardSessionState state) SWIFTEN_OVERRIDE;
virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) SWIFTEN_OVERRIDE;
virtual void showEmoticons(bool show) SWIFTEN_OVERRIDE;
void addMessageTop(boost::shared_ptr<ChatSnippet> snippet);
void addMessageBottom(boost::shared_ptr<ChatSnippet> snippet);
int getSnippetPositionByDate(const QDate& date); // FIXME : This probably shouldn't have been public
void addLastSeenLine();
private: // previously public, now private
void replaceLastMessage(const QString& newMessage, const ChatWindow::TimestampBehaviour timestampBehaviour);
void replaceLastMessage(const QString& newMessage, const QString& note);
void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time);
void rememberScrolledToBottom();
void setAckXML(const QString& id, const QString& xml);
void setReceiptXML(const QString& id, const QString& xml);
void displayReceiptInfo(const QString& id, bool showIt);
QString getLastSentMessage();
void addToJSEnvironment(const QString&, QObject*);
void setFileTransferProgress(QString id, const int percentageDone);
void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg);
void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state);
void setMUCInvitationJoined(QString id);
signals:
void gotFocus();
void fontResized(int);
void logCleared();
void scrollRequested(int pos);
void scrollReachedTop();
void scrollReachedBottom();
public slots:
void copySelectionToClipboard();
void handleLinkClicked(const QUrl&);
void resetView();
void resetTopInsertPoint();
void increaseFontSize(int numSteps = 1);
void decreaseFontSize();
void resizeFont(int fontSizeSteps);
void scrollToBottom();
void handleKeyPressEvent(QKeyEvent* event);
private slots:
void handleViewLoadFinished(bool);
void handleFrameSizeChanged();
void handleClearRequested();
void handleScrollRequested(int dx, int dy, const QRect& rectToScroll);
void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5);
private:
enum PreviousMessageKind {
PreviosuMessageWasNone,
PreviousMessageWasMessage,
PreviousMessageWasSystem,
PreviousMessageWasPresence,
PreviousMessageWasFileTransfer,
PreviousMessageWasMUCInvite
};
std::string addMessage(
const QString& message,
const std::string& senderName,
bool senderIsSelf,
boost::shared_ptr<SecurityLabel> label,
const std::string& avatarPath,
const QString& style,
const boost::posix_time::ptime& time,
const HighlightAction& highlight,
ChatSnippet::Direction direction);
void replaceMessage(
const QString& message,
const std::string& id,
const boost::posix_time::ptime& time,
const QString& style,
const HighlightAction& highlight);
bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf);
static ChatSnippet::Direction getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction);
- QString chatMessageToHTML(const ChatWindow::ChatMessage& message);
+ QString getHighlightSpanStart(const std::string& text, const std::string& background);
QString getHighlightSpanStart(const HighlightAction& highlight);
+ QString chatMessageToHTML(const ChatWindow::ChatMessage& message);
static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString());
private:
void headerEncode();
void messageEncode();
void addToDOM(boost::shared_ptr<ChatSnippet> snippet);
QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet);
QtChatWindow* window_;
UIEventStream* eventStream_;
bool viewReady_;
bool isAtBottom_;
bool topMessageAdded_;
int scrollBarMaximum_;
QtWebView* webView_;
QWebPage* webPage_;
int fontSizeSteps_;
QtChatTheme* theme_;
QWebElement newInsertPoint_;
QWebElement topInsertPoint_;
QWebElement lineSeparator_;
QWebElement lastElement_;
QWebElement firstElement_;
QWebElement document_;
bool disableAutoScroll_;
QtChatWindowJSBridge* jsBridge;
PreviousMessageKind previousMessageKind_;
bool previousMessageWasSelf_;
bool showEmoticons_;
bool insertingLastLine_;
int idCounter_;
QString previousSenderName_;
std::map<QString, QString> descriptions_;
};
}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index dd7d0c3..26e738a 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -1,444 +1,441 @@
import os, shutil, datetime, re, time
import Version
def generateDefaultTheme(dir) :
sourceDir = dir.abspath
result = "<!-- WARNING: This file is automatically generated. Any changes will be overwritten. -->\n"
result += "<RCC version =\"1.0\">"
result += "<qresource prefix=\"/themes/Default\">"
for (path, dirs, files) in os.walk(sourceDir) :
for file in files :
filePath = os.path.join(path,file)
result += "<file alias=\"%(alias)s\">%(path)s</file>" % {
"alias": filePath[len(sourceDir)+1:],
"path": filePath
}
result += "</qresource>"
result += "</RCC>"
return result
Import("env")
myenv = env.Clone()
# Disable warnings that affect Qt
myenv["CXXFLAGS"] = filter(lambda x : x != "-Wfloat-equal", myenv["CXXFLAGS"])
if "clang" in env["CC"] :
myenv.Append(CXXFLAGS = ["-Wno-float-equal", "-Wno-shorten-64-to-32", "-Wno-missing-prototypes", "-Wno-unreachable-code", "-Wno-disabled-macro-expansion", "-Wno-unused-private-field", "-Wno-extra-semi", "-Wno-duplicate-enum", "-Wno-missing-variable-declarations", "-Wno-conversion", "-Wno-undefined-reinterpret-cast"])
myenv.UseFlags(env["SWIFT_CONTROLLERS_FLAGS"])
myenv.UseFlags(env["SWIFTOOLS_FLAGS"])
if myenv["HAVE_XSS"] :
myenv.UseFlags(env["XSS_FLAGS"])
if env["PLATFORM"] == "posix" :
myenv.Append(LIBS = ["X11"])
if myenv["HAVE_SPARKLE"] :
myenv.UseFlags(env["SPARKLE_FLAGS"])
myenv.UseFlags(env["SWIFTEN_FLAGS"])
myenv.UseFlags(env["SWIFTEN_DEP_FLAGS"])
if myenv.get("HAVE_BREAKPAD") :
myenv.UseFlags(env["BREAKPAD_FLAGS"])
if myenv.get("HAVE_GROWL", False) :
myenv.UseFlags(myenv["GROWL_FLAGS"])
myenv.Append(CPPDEFINES = ["HAVE_GROWL"])
if myenv["swift_mobile"] :
myenv.Append(CPPDEFINES = ["SWIFT_MOBILE"])
if myenv.get("HAVE_SNARL", False) :
myenv.UseFlags(myenv["SNARL_FLAGS"])
myenv.Append(CPPDEFINES = ["HAVE_SNARL"])
if myenv.get("HAVE_HUNSPELL", True):
myenv.Append(CPPDEFINES = ["HAVE_HUNSPELL"])
myenv.UseFlags(myenv["HUNSPELL_FLAGS"])
if env["PLATFORM"] == "win32" :
myenv.Append(LIBS = ["cryptui"])
myenv.UseFlags(myenv["PLATFORM_FLAGS"])
myenv.Tool("qt4", toolpath = ["#/BuildTools/SCons/Tools"])
myenv.Tool("nsis", toolpath = ["#/BuildTools/SCons/Tools"])
myenv.Tool("wix", toolpath = ["#/BuildTools/SCons/Tools"])
myenv.Tool("textfile", toolpath = ["#/BuildTools/SCons/Tools"])
qt4modules = ['QtCore', 'QtWebKit', 'QtGui']
if myenv["qt5"] :
qt_version = '5'
qt4modules += ['QtWidgets', 'QtWebKitWidgets', 'QtMultimedia']
else :
qt_version = '4'
if env["PLATFORM"] == "posix" :
qt4modules += ["QtDBus"]
if env["PLATFORM"] != "win32" and env["PLATFORM"] != "darwin" :
qt4modules += ["QtNetwork"]
myenv.EnableQt4Modules(qt4modules, debug = False, version = qt_version)
myenv.Append(CPPPATH = ["."])
if env["PLATFORM"] == "win32" :
#myenv.Append(LINKFLAGS = ["/SUBSYSTEM:CONSOLE"])
myenv.Append(LINKFLAGS = ["/SUBSYSTEM:WINDOWS"])
myenv.Append(LIBS = "qtmain")
if myenv.get("HAVE_SCHANNEL", 0) :
myenv.Append(LIBS = "Cryptui")
myenv.Append(CPPDEFINES = "HAVE_SCHANNEL")
if env["debug"] and not env["optimize"]:
myenv.Append(LINKFLAGS = ["/NODEFAULTLIB:msvcrt"])
myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("#/Swift/resources/themes/Default"))))
sources = [
"main.cpp",
"QtAboutWidget.cpp",
"QtSpellCheckerWindow.cpp",
"QtAvatarWidget.cpp",
"QtUIFactory.cpp",
"QtChatWindowFactory.cpp",
"QtClickableLabel.cpp",
"QtLoginWindow.cpp",
"QtMainWindow.cpp",
"QtProfileWindow.cpp",
"QtBlockListEditorWindow.cpp",
"QtNameWidget.cpp",
"QtSettingsProvider.cpp",
"QtStatusWidget.cpp",
"QtScaledAvatarCache.cpp",
"QtSwift.cpp",
"QtURIHandler.cpp",
"QtChatWindow.cpp",
"QtChatView.cpp",
"QtWebKitChatView.cpp",
"QtPlainChatView.cpp",
"QtChatTheme.cpp",
"QtChatTabs.cpp",
"QtSoundPlayer.cpp",
"QtSystemTray.cpp",
"QtCachedImageScaler.cpp",
"QtTabbable.cpp",
"QtTabWidget.cpp",
"QtTextEdit.cpp",
"QtXMLConsoleWidget.cpp",
"QtHistoryWindow.cpp",
"QtFileTransferListWidget.cpp",
"QtFileTransferListItemModel.cpp",
"QtAdHocCommandWindow.cpp",
"QtAdHocCommandWithJIDWindow.cpp",
"QtUtilities.cpp",
"QtBookmarkDetailWindow.cpp",
"QtAddBookmarkWindow.cpp",
"QtEditBookmarkWindow.cpp",
"QtContactEditWindow.cpp",
"QtContactEditWidget.cpp",
"QtSingleWindow.cpp",
- "QtHighlightEditorWidget.cpp",
- "QtHighlightRulesItemModel.cpp",
- "QtHighlightRuleWidget.cpp",
+ "QtHighlightEditor.cpp",
"QtColorToolButton.cpp",
"QtClosableLineEdit.cpp",
"ChatSnippet.cpp",
"MessageSnippet.cpp",
"SystemMessageSnippet.cpp",
"QtElidingLabel.cpp",
"QtFormWidget.cpp",
"QtFormResultItemModel.cpp",
"QtLineEdit.cpp",
"QtJoinMUCWindow.cpp",
"QtConnectionSettingsWindow.cpp",
"Roster/RosterModel.cpp",
"Roster/QtTreeWidget.cpp",
# "Roster/QtTreeWidgetItem.cpp",
"Roster/RosterDelegate.cpp",
"Roster/GroupItemDelegate.cpp",
"Roster/DelegateCommons.cpp",
"Roster/QtFilterWidget.cpp",
"Roster/QtRosterWidget.cpp",
"Roster/QtOccupantListWidget.cpp",
"Roster/RosterTooltip.cpp",
"EventViewer/EventModel.cpp",
"EventViewer/EventDelegate.cpp",
"EventViewer/TwoLineDelegate.cpp",
"EventViewer/QtEventWindow.cpp",
"EventViewer/QtEvent.cpp",
"ChatList/QtChatListWindow.cpp",
"ChatList/ChatListModel.cpp",
"ChatList/ChatListDelegate.cpp",
"ChatList/ChatListMUCItem.cpp",
"ChatList/ChatListRecentItem.cpp",
"ChatList/ChatListWhiteboardItem.cpp",
"MUCSearch/QtMUCSearchWindow.cpp",
"MUCSearch/MUCSearchModel.cpp",
"MUCSearch/MUCSearchRoomItem.cpp",
"MUCSearch/MUCSearchEmptyItem.cpp",
"MUCSearch/MUCSearchDelegate.cpp",
"UserSearch/ContactListDelegate.cpp",
"UserSearch/ContactListModel.cpp",
"UserSearch/QtContactListWidget.cpp",
"UserSearch/QtSuggestingJIDInput.cpp",
"UserSearch/QtUserSearchFirstPage.cpp",
"UserSearch/QtUserSearchFirstMultiJIDPage.cpp",
"UserSearch/QtUserSearchFieldsPage.cpp",
"UserSearch/QtUserSearchResultsPage.cpp",
"UserSearch/QtUserSearchDetailsPage.cpp",
"UserSearch/QtUserSearchWindow.cpp",
"UserSearch/UserSearchModel.cpp",
"UserSearch/UserSearchDelegate.cpp",
"Whiteboard/FreehandLineItem.cpp",
"Whiteboard/GView.cpp",
"Whiteboard/TextDialog.cpp",
"Whiteboard/QtWhiteboardWindow.cpp",
"Whiteboard/ColorWidget.cpp",
"QtSubscriptionRequestWindow.cpp",
"QtRosterHeader.cpp",
"QtWebView.cpp",
"qrc_DefaultTheme.cc",
"qrc_Swift.cc",
"QtChatWindowJSBridge.cpp",
"QtMUCConfigurationWindow.cpp",
"QtAffiliationEditor.cpp",
"QtUISettingConstants.cpp",
"QtURLValidator.cpp",
"QtResourceHelper.cpp"
]
# QtVCardWidget
sources.extend([
"QtVCardWidget/QtCloseButton.cpp",
"QtVCardWidget/QtRemovableItemDelegate.cpp",
"QtVCardWidget/QtResizableLineEdit.cpp",
"QtVCardWidget/QtTagComboBox.cpp",
"QtVCardWidget/QtVCardHomeWork.cpp",
"QtVCardWidget/QtVCardAddressField.cpp",
"QtVCardWidget/QtVCardAddressLabelField.cpp",
"QtVCardWidget/QtVCardBirthdayField.cpp",
"QtVCardWidget/QtVCardDescriptionField.cpp",
"QtVCardWidget/QtVCardInternetEMailField.cpp",
"QtVCardWidget/QtVCardJIDField.cpp",
"QtVCardWidget/QtVCardOrganizationField.cpp",
"QtVCardWidget/QtVCardPhotoAndNameFields.cpp",
"QtVCardWidget/QtVCardRoleField.cpp",
"QtVCardWidget/QtVCardTelephoneField.cpp",
"QtVCardWidget/QtVCardTitleField.cpp",
"QtVCardWidget/QtVCardURLField.cpp",
"QtVCardWidget/QtVCardGeneralField.cpp",
"QtVCardWidget/QtVCardWidget.cpp"
])
myenv.Uic4("QtVCardWidget/QtVCardPhotoAndNameFields.ui")
myenv.Uic4("QtVCardWidget/QtVCardWidget.ui")
myenv.Uic4("QtProfileWindow.ui")
# Determine the version
myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift")
if env["PLATFORM"] == "win32" :
swift_windows_version = Version.convertToWindowsVersion(myenv["SWIFT_VERSION"])
myenv["SWIFT_VERSION_MAJOR"] = swift_windows_version[0]
myenv["SWIFT_VERSION_MINOR"] = swift_windows_version[1]
myenv["SWIFT_VERSION_PATCH"] = swift_windows_version[2]
if env["PLATFORM"] == "win32" :
res_env = myenv.Clone()
res_env.Append(CPPDEFINES = [
("SWIFT_COPYRIGHT_YEAR", "\"\\\"2010-%s\\\"\"" % str(time.localtime()[0])),
("SWIFT_VERSION_MAJOR", "${SWIFT_VERSION_MAJOR}"),
("SWIFT_VERSION_MINOR", "${SWIFT_VERSION_MINOR}"),
("SWIFT_VERSION_PATCH", "${SWIFT_VERSION_PATCH}"),
])
res = res_env.RES("#/Swift/resources/Windows/Swift.rc")
# For some reason, SCons isn't picking up the dependency correctly
# Adding it explicitly until i figure out why
myenv.Depends(res, "../Controllers/BuildVersion.h")
sources += [
"WinUIHelpers.cpp",
"CAPICertificateSelector.cpp",
"WindowsNotifier.cpp",
"#/Swift/resources/Windows/Swift.res"
]
if env["PLATFORM"] == "posix" :
sources += [
"FreeDesktopNotifier.cpp",
"QtDBUSURIHandler.cpp",
]
if env["PLATFORM"] == "darwin" :
sources += ["CocoaApplicationActivateHelper.mm"]
sources += ["CocoaUIHelpers.mm"]
if env["PLATFORM"] == "darwin" or env["PLATFORM"] == "win32" :
swiftProgram = myenv.Program("Swift", sources)
else :
sources += ["QtCertificateViewerDialog.cpp"];
myenv.Uic4("QtCertificateViewerDialog.ui");
swiftProgram = myenv.Program("swift-im", sources)
if env["PLATFORM"] != "darwin" and env["PLATFORM"] != "win32" :
openURIProgram = myenv.Program("swift-open-uri", "swift-open-uri.cpp")
else :
openURIProgram = []
myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui")
myenv.Uic4("UserSearch/QtUserSearchWizard.ui")
myenv.Uic4("UserSearch/QtUserSearchFirstPage.ui")
myenv.Uic4("UserSearch/QtUserSearchFirstMultiJIDPage.ui")
myenv.Uic4("UserSearch/QtUserSearchFieldsPage.ui")
myenv.Uic4("UserSearch/QtUserSearchResultsPage.ui")
myenv.Uic4("QtBookmarkDetailWindow.ui")
myenv.Uic4("QtAffiliationEditor.ui")
myenv.Uic4("QtJoinMUCWindow.ui")
myenv.Uic4("QtHistoryWindow.ui")
myenv.Uic4("QtConnectionSettings.ui")
-myenv.Uic4("QtHighlightRuleWidget.ui")
-myenv.Uic4("QtHighlightEditorWidget.ui")
+myenv.Uic4("QtHighlightEditor.ui")
myenv.Uic4("QtBlockListEditorWindow.ui")
myenv.Uic4("QtSpellCheckerWindow.ui")
myenv.Qrc("DefaultTheme.qrc")
myenv.Qrc("Swift.qrc")
# Resources
commonResources = {
"": ["#/Swift/resources/sounds"]
}
myenv["TEXTFILESUFFIX"] = ""
myenv.MyTextfile(target = "COPYING", source = [myenv.File("../../COPYING.gpl"), myenv.File("../../COPYING.thirdparty")], LINESEPARATOR = "\n\n========\n\n\n")
################################################################################
# Translation
################################################################################
# Collect available languages
translation_languages = []
for file in os.listdir(Dir("#/Swift/Translations").abspath) :
if file.startswith("swift_") and file.endswith(".ts") :
translation_languages.append(file[6:-3])
# Generate translation modules
translation_sources = [env.File("#/Swift/Translations/swift.ts").abspath]
translation_modules = []
for lang in translation_languages :
translation_resource = "#/Swift/resources/translations/swift_" + lang + ".qm"
translation_source = "#/Swift/Translations/swift_" + lang + ".ts"
translation_sources.append(env.File(translation_source).abspath)
translation_modules.append(env.File(translation_resource).abspath)
myenv.Qm(translation_resource, translation_source)
commonResources["translations"] = commonResources.get("translations", []) + [translation_resource]
# LUpdate translation (if requested)
if ARGUMENTS.get("update_translations", False) :
myenv.Precious(translation_sources)
remove_obsolete_option = ""
if ARGUMENTS.get("remove_obsolete_translations", False) :
remove_obsolete_option = " -no-obsolete"
for translation_source in filter(lambda x: not x.endswith("_en.ts"), translation_sources) :
t = myenv.Command([translation_source], [], [myenv.Action("$QT4_LUPDATE -I " + env.Dir("#").abspath + remove_obsolete_option + " -silent -codecfortr utf-8 -recursive Swift -ts " + translation_source, cmdstr = "$QT4_LUPDATECOMSTR")])
myenv.AlwaysBuild(t)
# NSIS installation script
if env["PLATFORM"] == "win32" :
nsis_translation_install_script = ""
nsis_translation_uninstall_script = ""
for lang in translation_languages :
nsis_translation_install_script += "File \"..\\..\\QtUI\\Swift\\translations\\swift_" + lang + ".qm\"\n"
nsis_translation_uninstall_script += "delete $INSTDIR\\translations\\swift_" + lang + ".qm\n"
myenv.WriteVal("../Packaging/nsis/translations-install.nsh", myenv.Value(nsis_translation_install_script))
myenv.WriteVal("../Packaging/nsis/translations-uninstall.nsh", myenv.Value(nsis_translation_uninstall_script))
################################################################################
if env["PLATFORM"] == "darwin" :
frameworks = []
if env["HAVE_SPARKLE"] :
frameworks.append(env["SPARKLE_FRAMEWORK"])
if env["HAVE_GROWL"] :
frameworks.append(env["GROWL_FRAMEWORK"])
commonResources[""] = commonResources.get("", []) + ["#/Swift/resources/MacOSX/Swift.icns"]
app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True)
if env["DIST"] :
myenv.Command(["#/Packages/Swift/Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"])
dsym = myenv.Command(["Swift-${SWIFT_VERSION}.dSYM"], ["Swift"], ["dsymutil -o ${TARGET} ${SOURCE}"])
myenv.Command(["#/Packages/Swift/Swift-${SWIFT_VERSION}.dSYM.zip"], dsym, ["cd ${SOURCE.dir} && zip -r ${TARGET.abspath} ${SOURCE.name}"])
if env.get("SWIFT_INSTALLDIR", "") :
env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram + openURIProgram)
env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm")
icons_path = os.path.join(env["SWIFT_INSTALLDIR"], "share", "icons", "hicolor")
env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm")
env.InstallAs(os.path.join(icons_path, "scalable", "apps", "swift.svg"), "#/Swift/resources/logo/logo-icon.svg")
for i in ["16", "22", "24", "64", "128"] :
env.InstallAs(os.path.join(icons_path, i + "x" + i, "apps", "swift.png"), "#/Swift/resources/logo/logo-icon-" + i + ".png")
env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "applications"), "#/Swift/resources/swift.desktop")
for dir, resource in commonResources.items() :
env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "swift", dir), resource)
if env["PLATFORM"] == "win32" :
if env["DIST"] or ARGUMENTS.get("dump_trace") :
commonResources[""] = commonResources.get("", []) + [
#os.path.join(env["OPENSSL_DIR"], "bin", "ssleay32.dll"),
#os.path.join(env["OPENSSL_DIR"], "bin", "libeay32.dll"),
"#/Swift/resources/images",
]
if env["SWIFTEN_DLL"] :
commonResources[""] = commonResources.get("", []) + ["#/Swiften/${SWIFTEN_LIBRARY_FILE}"]
qtplugins = {}
qtplugins["imageformats"] = ["gif", "ico", "jpeg", "mng", "svg", "tiff"]
qtlibs = ["QtCore", "QtGui", "QtNetwork", "QtWebKit", "QtXMLPatterns"]
if qt_version == '4' :
qtlibs.append("phonon")
qtlibs = [lib + '4' for lib in qtlibs]
else :
qtlibs += ['QtQuick', 'QtQml', 'QtPositioning', 'QtMultimedia', 'QtSql', 'QtSensors', 'QtWidgets', 'QtWebKitWidgets', 'QtMultimediaWidgets', 'QtOpenGL', 'QtPrintSupport']
qtlibs = [lib.replace('Qt', 'Qt5') for lib in qtlibs]
qtlibs += ['icuin51', 'icuuc51', 'icudt51', 'libGLESv2', 'libEGL']
qtplugins["platforms"] = ['windows']
qtplugins["accessible"] = ["taccessiblewidgets"]
windowsBundleFiles = myenv.WindowsBundle("Swift",
resources = commonResources,
qtplugins = qtplugins,
qtlibs = qtlibs,
qtversion = qt_version)
if env["DIST"] :
#myenv.Append(NSIS_OPTIONS = [
# "/DmsvccRedistributableDir=\"" + env["vcredist"] + "\"",
# "/DbuildVersion=" + myenv["SWIFT_VERSION"]
# ])
#myenv.Nsis("../Packaging/nsis/swift.nsi")
if env["SCONS_STAGE"] == "build" and env.get("wix_bindir", None):
def convertToRTF(env, target, source) :
infile = open(source[0].abspath, 'r')
outfile = open(target[0].abspath, 'w')
outfile.write('{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\fs16\\f0\\pard\n')
for line in infile:
for char in line.decode("utf-8") :
if ord(char) > 127 :
# FIXME: This is incorrect, because it only works for latin1.
# The correct way is \u<decimal utf16 point>? , but this is more
# work
outfile.write("\\'%X" % ord(char))
else :
outfile.write(char)
outfile.write('\\par ')
outfile.write('}')
outfile.close()
infile.close()
copying = env.Command(["Swift/COPYING.rtf"], ["COPYING"], convertToRTF)
wixvariables = {
'VCCRTFile': env["vcredist"],
'Version': str(myenv["SWIFT_VERSION_MAJOR"]) + "." + str(myenv["SWIFT_VERSION_MINOR"]) + "." + str(myenv["SWIFT_VERSION_PATCH"])
}
wixincludecontent = "<Include>"
for key in wixvariables:
wixincludecontent += "<?define %s = \"%s\" ?>" % (key, wixvariables[key])
wixincludecontent += "</Include>"
myenv.WriteVal("..\\Packaging\\Wix\\variables.wxs", env.Value(wixincludecontent))
myenv["WIX_SOURCE_OBJECT_DIR"] = "Swift\\QtUI\\Swift"
myenv.WiX_Heat('..\\Packaging\\WiX\\gen_files.wxs', windowsBundleFiles + copying)
myenv.WiX_Candle('..\\Packaging\\WiX\\Swift.wixobj', '..\\Packaging\\WiX\\Swift.wxs')
myenv.WiX_Candle('..\\Packaging\\WiX\\gen_files.wixobj', '..\\Packaging\\WiX\\gen_files.wxs')
myenv.WiX_Light('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.msi', ['..\\Packaging\\WiX\\gen_files.wixobj','..\\Packaging\\WiX\\Swift.wixobj'])
if myenv["debug"] :
myenv.InstallAs('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.pdb', "Swift.pdb")