summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2017-01-31 14:57:22 (GMT)
committerEdwin Mons <edwin.mons@isode.com>2017-02-27 14:07:13 (GMT)
commitfc8f5b31c22ed7af4f0e2473f269601a87a0438c (patch)
tree0c59a9debf72247c7409947a3a4cccb6c616dd06 /Swift/Controllers
parentabd81d4a3cf08ffaa1e5265d204cdd80c8c0583b (diff)
downloadswift-fc8f5b31c22ed7af4f0e2473f269601a87a0438c.zip
swift-fc8f5b31c22ed7af4f0e2473f269601a87a0438c.tar.bz2
Redesign highlight logic and processing
The new highlight logic follows a simpler model. It supports: * highlighting of whole words in a message * highlighting messages by sender name * highlighting if the user’s name is mentioned Possible actions for these highlights are text colouring, sound playback of WAV files, and system notifications. In addition the user can decide to receive sound and system notification on general incoming direct and group messages. Redesigned the highlight configuration UI dialog for this new model. ChatMessageParser class now deals with all parsing and marking up the chat message with the matching HighlightActions. Highlighter class has been extended to deal with all sound and system notification highlights that should be emitted by a specified chat message. Moved some tests over to gtest in the process. Test-Information: Tested UI on macOS 10.12.3 with Qt 5.7.1. Manually tested that correct system notification are emitted on mentions, keyword highlights and general messages. Added new unit tests to cover new highlighting behaviour. Change-Id: I1c89e29d81022174187fb44af0d384036ec51594
Diffstat (limited to 'Swift/Controllers')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp9
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp42
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h24
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.cpp167
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.h30
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp8
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp13
-rw-r--r--Swift/Controllers/Chat/MUCController.h4
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp487
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp84
-rw-r--r--Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp10
-rw-r--r--Swift/Controllers/EventNotifier.cpp9
-rw-r--r--Swift/Controllers/HighlightAction.cpp62
-rw-r--r--Swift/Controllers/HighlightAction.h82
-rw-r--r--Swift/Controllers/HighlightManager.cpp148
-rw-r--r--Swift/Controllers/HighlightManager.h81
-rw-r--r--Swift/Controllers/HighlightRule.cpp200
-rw-r--r--Swift/Controllers/HighlightRule.h103
-rw-r--r--Swift/Controllers/Highlighter.cpp51
-rw-r--r--Swift/Controllers/Highlighter.h45
-rw-r--r--Swift/Controllers/Highlighting/HighlightAction.cpp75
-rw-r--r--Swift/Controllers/Highlighting/HighlightAction.h68
-rw-r--r--Swift/Controllers/Highlighting/HighlightConfiguration.cpp36
-rw-r--r--Swift/Controllers/Highlighting/HighlightConfiguration.h81
-rw-r--r--Swift/Controllers/Highlighting/HighlightEditorController.cpp (renamed from Swift/Controllers/HighlightEditorController.cpp)4
-rw-r--r--Swift/Controllers/Highlighting/HighlightEditorController.h (renamed from Swift/Controllers/HighlightEditorController.h)2
-rw-r--r--Swift/Controllers/Highlighting/HighlightManager.cpp102
-rw-r--r--Swift/Controllers/Highlighting/HighlightManager.h64
-rw-r--r--Swift/Controllers/Highlighting/Highlighter.cpp113
-rw-r--r--Swift/Controllers/Highlighting/Highlighter.h38
-rw-r--r--Swift/Controllers/MainController.cpp6
-rw-r--r--Swift/Controllers/SConscript110
-rw-r--r--Swift/Controllers/SettingConstants.cpp3
-rw-r--r--Swift/Controllers/SettingConstants.h9
-rw-r--r--Swift/Controllers/SoundEventController.cpp8
-rw-r--r--Swift/Controllers/SoundEventController.h7
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h41
-rw-r--r--Swift/Controllers/UnitTest/HighlightRuleTest.cpp324
-rw-r--r--Swift/Controllers/XMPPEvents/MessageEvent.h22
39 files changed, 1185 insertions, 1587 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index a498067..d1cd1fe 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -1,84 +1,84 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatController.h>
#include <memory>
#include <boost/bind.hpp>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/Algorithm.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/format.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/Disco/FeatureOracle.h>
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swiften/Elements/Idle.h>
#include <Swiften/FileTransfer/FileTransferManager.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <Swift/Controllers/FileTransfer/FileTransferController.h>
-#include <Swift/Controllers/Highlighter.h>
+#include <Swift/Controllers/Highlighting/Highlighter.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/StatusUtil.h>
#include <Swift/Controllers/Translator.h>
#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h>
#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>
#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.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, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider)
- : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
+ : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, nickResolver, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), 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->getAccountPresence(contact) : presenceOracle->getLastPresence(contact);
}
Idle::ref idle;
if (theirPresence && (idle = theirPresence->getPayload<Idle>())) {
startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % Swift::Translator::getInstance()->ptimeToHumanReadableString(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);
@@ -183,63 +183,64 @@ void ChatController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> mess
}
chatStateTracker_->handleMessageReceived(message);
chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>());
// handle XEP-0184 Message Receipts
// incomming receipts
if (std::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_) {
std::shared_ptr<Message> receiptMessage = std::make_shared<Message>();
receiptMessage->setTo(toJID_);
receiptMessage->addPayload(std::make_shared<DeliveryReceipt>(message->getID()));
stanzaChannel_->sendMessage(receiptMessage);
}
}
}
void ChatController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) {
+ highlighter_->handleSystemNotifications(chatMessage, messageEvent);
eventController_->handleIncomingEvent(messageEvent);
if (!messageEvent->getConcluded()) {
- handleHighlightActions(chatMessage);
+ highlighter_->handleSoundNotifications(chatMessage);
}
}
void ChatController::preSendMessageRequest(std::shared_ptr<Message> message) {
chatStateNotifier_->addChatStateRequest(message);
if (userWantsReceipts_ && (contactSupportsReceipts_ != No) && message) {
message->addPayload(std::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() {
boost::optional<ChatWindow::AlertID> newDeliverReceiptAlert;
if (userWantsReceipts_ && (contactSupportsReceipts_ == No)) {
newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts."));
} else if (userWantsReceipts_ && (contactSupportsReceipts_ == Maybe)) {
newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you send."));
} else {
if (deliveryReceiptAlert_) {
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index da9064e..5839d6c 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,77 +1,77 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <map>
#include <memory>
#include <sstream>
#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/Path.h>
#include <Swiften/Base/format.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Elements/Delay.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h>
#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
-#include <Swift/Controllers/HighlightManager.h>
-#include <Swift/Controllers/Highlighter.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
+#include <Swift/Controllers/Highlighting/Highlighter.h>
#include <Swift/Controllers/Intl.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/EventController.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.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, std::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) {
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::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();
+ highlighter_ = highlightManager->createHighlighter(nickResolver);
ChatControllerBase::setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
createDayChangeTimer();
}
ChatControllerBase::~ChatControllerBase() {
if (dateChangeTimer_) {
dateChangeTimer_->onTick.disconnect(boost::bind(&ChatControllerBase::handleDayChangeTick, this));
dateChangeTimer_->stop();
}
delete highlighter_;
delete chatWindow_;
}
void ChatControllerBase::handleLogCleared() {
cancelReplaces();
}
ChatWindow* ChatControllerBase::detachChatWindow() {
ChatWindow* chatWindow = chatWindow_;
chatWindow_ = nullptr;
return chatWindow;
}
void ChatControllerBase::handleCapsChanged(const JID& jid) {
if (jid.compare(toJID_, JID::WithoutResource) == 0) {
handleBareJIDCapsChanged(jid);
}
}
@@ -177,84 +177,66 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool
}
void ChatControllerBase::handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
if (catalog && !error) {
if (catalog->getItems().size() == 0) {
chatWindow_->setSecurityLabelsEnabled(false);
labelsEnabled_ = false;
} else {
labelsEnabled_ = true;
chatWindow_->setAvailableSecurityLabels(catalog->getItems());
chatWindow_->setSecurityLabelsEnabled(true);
}
} else {
labelsEnabled_ = false;
chatWindow_->setSecurityLabelsError();
}
}
void ChatControllerBase::showChatWindow() {
chatWindow_->show();
}
void ChatControllerBase::activateChatWindow() {
chatWindow_->activate();
}
bool ChatControllerBase::hasOpenWindow() const {
return chatWindow_ && chatWindow_->isVisible();
}
-ChatWindow::ChatMessage ChatControllerBase::buildChatWindowChatMessage(const std::string& message, bool senderIsSelf, const HighlightAction& fullMessageHighlightAction) {
+ChatWindow::ChatMessage ChatControllerBase::buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf) {
ChatWindow::ChatMessage chatMessage;
- chatMessage = chatMessageParser_->parseMessageBody(message, highlighter_->getNick(), senderIsSelf);
- chatMessage.setFullMessageHighlightAction(fullMessageHighlightAction);
+ chatMessage = chatMessageParser_->parseMessageBody(message, senderName, senderIsSelf);
return chatMessage;
}
-void ChatControllerBase::handleHighlightActions(const ChatWindow::ChatMessage& chatMessage) {
- std::set<std::string> playedSounds;
- if (chatMessage.getFullMessageHighlightAction().playSound()) {
- highlighter_->handleHighlightAction(chatMessage.getFullMessageHighlightAction());
- playedSounds.insert(chatMessage.getFullMessageHighlightAction().getSoundFile());
- }
- for (std::shared_ptr<ChatWindow::ChatMessagePart> part : chatMessage.getParts()) {
- std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightMessage = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part);
- if (highlightMessage && highlightMessage->action.playSound()) {
- if (playedSounds.find(highlightMessage->action.getSoundFile()) == playedSounds.end()) {
- highlighter_->handleHighlightAction(highlightMessage->action);
- playedSounds.insert(highlightMessage->action.getSoundFile());
- }
- }
- }
-}
-
void ChatControllerBase::updateMessageCount() {
chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
onUnreadCountChanged();
}
std::string ChatControllerBase::addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, const std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time) {
if (chatMessage.isMeCommand()) {
return chatWindow_->addAction(chatMessage, senderName, senderIsSelf, label, pathToString(avatarPath), time);
}
else {
return chatWindow_->addMessage(chatMessage, senderName, senderIsSelf, label, pathToString(avatarPath), time);
}
}
void ChatControllerBase::replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time) {
if (chatMessage.isMeCommand()) {
chatWindow_->replaceWithAction(chatMessage, id, time);
}
else {
chatWindow_->replaceMessage(chatMessage, id, time);
}
}
bool ChatControllerBase::isFromContact(const JID& from) {
return from.toBare() == toJID_.toBare();
}
void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) {
preHandleIncomingMessage(messageEvent);
if (messageEvent->isReadable() && !messageEvent->getConcluded()) {
@@ -287,79 +269,73 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes
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<std::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);
}
std::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
- HighlightAction fullMessageHighlight;
- if (!isIncomingMessageFromMe(message)) {
- fullMessageHighlight = highlighter_->findFirstFullMessageMatchAction(body, senderHighlightNameFromMessage(from));
- }
-
std::shared_ptr<Replace> replace = message->getPayload<Replace>();
bool senderIsSelf = isIncomingMessageFromMe(message);
if (replace) {
// Should check if the user has a previous message
std::map<JID, std::string>::iterator lastMessage;
lastMessage = lastMessagesUIID_.find(from);
if (lastMessage != lastMessagesUIID_.end()) {
- chatMessage = buildChatWindowChatMessage(body, senderIsSelf, fullMessageHighlight);
+ chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf);
replaceMessage(chatMessage, lastMessagesUIID_[from], timeStamp);
}
}
else {
- chatMessage = buildChatWindowChatMessage(body, senderIsSelf, fullMessageHighlight);
+ chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf);
addMessageHandleIncomingMessage(from, chatMessage, senderIsSelf, label, timeStamp);
}
logMessage(body, from, selfJID_, timeStamp, true);
}
chatWindow_->show();
updateMessageCount();
postHandleIncomingMessage(messageEvent, chatMessage);
}
void ChatControllerBase::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) {
lastMessagesUIID_[from] = addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp);
}
std::string ChatControllerBase::getErrorMessage(std::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");
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 4255c19..7f118bd 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -1,130 +1,130 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/optional.hpp>
#include <boost/signals2.hpp>
#include <Swiften/Base/IDGenerator.h>
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/Elements/ErrorPayload.h>
#include <Swiften/Elements/SecurityLabelsCatalog.h>
#include <Swiften/Elements/Stanza.h>
#include <Swiften/JID/JID.h>
#include <Swiften/MUC/MUCRegistry.h>
#include <Swiften/Network/Timer.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Presence/PresenceOracle.h>
#include <Swiften/Queries/IQRouter.h>
-#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
#include <Swift/Controllers/HistoryController.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
namespace Swift {
- class IQRouter;
- class StanzaChannel;
- class ChatWindowFactory;
+ class AutoAcceptMUCInviteDecider;
class AvatarManager;
- class UIEventStream;
- class EventController;
+ class ChatMessageParser;
+ class ChatWindowFactory;
class EntityCapsProvider;
+ class EventController;
class HighlightManager;
class Highlighter;
- class ChatMessageParser;
- class AutoAcceptMUCInviteDecider;
+ class IQRouter;
+ class NickResolver;
+ class StanzaChannel;
+ class UIEventStream;
class ChatControllerBase : public boost::signals2::trackable {
public:
virtual ~ChatControllerBase();
void showChatWindow();
void activateChatWindow();
bool hasOpenWindow() const;
virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info);
virtual void handleIncomingOwnMessage(std::shared_ptr<Message> /*message*/) {}
void handleIncomingMessage(std::shared_ptr<MessageEvent> message);
std::string addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time);
void replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time);
virtual void setOnline(bool online);
void setEnabled(bool enabled);
virtual void setToJID(const JID& jid) {toJID_ = jid;}
/** Used for determining when something is recent.*/
boost::signals2::signal<void (const std::string& /*activity*/)> onActivity;
boost::signals2::signal<void ()> onUnreadCountChanged;
boost::signals2::signal<void ()> onWindowClosed;
int getUnreadCount();
const JID& getToJID() {return toJID_;}
void handleCapsChanged(const JID& jid);
void setCanStartImpromptuChats(bool supportsImpromptu);
virtual ChatWindow* detachChatWindow();
boost::signals2::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, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
+ ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
/**
* Pass the Message appended, and the stanza used to send it.
*/
virtual void postSendMessage(const std::string&, std::shared_ptr<Stanza>) {}
virtual std::string senderDisplayNameFromMessage(const JID& from) = 0;
virtual std::string senderHighlightNameFromMessage(const JID& from) = 0;
virtual bool isIncomingMessageFromMe(std::shared_ptr<Message>) = 0;
virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) {}
virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time);
virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage&) {}
virtual void preSendMessageRequest(std::shared_ptr<Message>) {}
virtual bool isFromContact(const JID& from);
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const = 0;
virtual void dayTicked() {}
virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
std::string getErrorMessage(std::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;
- ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, bool senderIsSelf, const HighlightAction& fullMessageHighlightAction);
- void handleHighlightActions(const ChatWindow::ChatMessage& chatMessage);
+ ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf);
void updateMessageCount();
private:
IDGenerator idGenerator_;
std::string lastSentMessageStanzaID_;
void createDayChangeTimer();
void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
void handleAllMessagesRead();
void handleSecurityLabelsCatalogResponse(std::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<std::shared_ptr<StanzaEvent> > unreadMessages_;
std::vector<std::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_;
diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp
index ec7df6c..1a822a1 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.cpp
+++ b/Swift/Controllers/Chat/ChatMessageParser.cpp
@@ -1,197 +1,264 @@
/*
- * Copyright (c) 2013-2016 Isode Limited.
+ * Copyright (c) 2013-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
#include <Swiften/Base/Regex.h>
#include <Swiften/Base/String.h>
#include <SwifTools/Linkify.h>
namespace Swift {
- ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode)
- : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) {
+ ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, std::shared_ptr<HighlightConfiguration> highlightConfiguration, Mode mode) : emoticons_(emoticons), highlightConfiguration_(highlightConfiguration), mode_(mode) {
}
typedef std::pair<std::string, std::string> StringPair;
- ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) {
+ ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& senderNickname, bool senderIsSelf) {
ChatWindow::ChatMessage parsedMessage;
+
std::string remaining = body;
if (boost::starts_with(body, "/me ")) {
remaining = String::getSplittedAtFirst(body, ' ').second;
parsedMessage.setIsMeCommand(true);
}
/* 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(std::make_shared<ChatWindow::ChatURIMessagePart>(part));
}
else {
parsedMessage.append(std::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, nick);
+ // Highlight keywords and own mentions.
+ parsedMessage = splitHighlight(parsedMessage);
+
+ // Highlight full message events like, specific sender, general
+ // incoming group message, or general incoming direct message.
+ parsedMessage = fullMessageHighlight(parsedMessage, senderNickname);
}
return parsedMessage;
}
ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) {
ChatWindow::ChatMessage parsedMessage = message;
std::string regexString;
/* Parse two, emoticons */
for (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;
- for (std::shared_ptr<ChatWindow::ChatMessagePart> part : parsedMessage.getParts()) {
+ for (const auto& part : parsedMessage.getParts()) {
std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
if ((textPart = std::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(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
}
std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = std::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(std::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.setParts(newMessage.getParts());
}
+
return parsedMessage;
}
- ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick) {
- 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 */
- } else if (rule.getAction().getTextBackground().empty() && rule.getAction().getTextColor().empty()) {
- continue; /* do not try to highlight text, if no highlight color is specified */
+ ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message) {
+ auto keywordToRegEx = [](const std::string& keyword, bool matchCaseSensitive) {
+ std::string escaped = Regex::escape(keyword);
+ boost::regex::flag_type flags = boost::regex::normal;
+ if (!matchCaseSensitive) {
+ flags |= boost::regex::icase;
}
- const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick);
- for (const boost::regex& regex : keywordRegex) {
- ChatWindow::ChatMessage newMessage;
- for (std::shared_ptr<ChatWindow::ChatMessagePart> part : parsedMessage.getParts()) {
- std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
- if ((textPart = std::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(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
- }
- std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = std::make_shared<ChatWindow::ChatHighlightingMessagePart>();
- highlightPart->text = match.str();
- highlightPart->action = rule.getAction();
- newMessage.append(highlightPart);
- start = matchEnd;
- }
- if (start != text.end()) {
- /* If there's plain text after the last emoticon, record it */
- newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
+ return boost::regex("\\b" + escaped + "\\b", flags);
+ };
+
+ auto highlightKeywordInChatMessage = [&](const ChatWindow::ChatMessage& message, const std::string& keyword, bool matchCaseSensitive, const HighlightAction& action) {
+ ChatWindow::ChatMessage resultMessage;
+
+ for (const auto& part : message.getParts()) {
+ std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+ if ((textPart = std::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, keywordToRegEx(keyword, matchCaseSensitive))) {
+ 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 */
+ resultMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
}
+ std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = std::make_shared<ChatWindow::ChatHighlightingMessagePart>();
+ highlightPart->text = match.str();
+ highlightPart->action = action;
+ resultMessage.append(highlightPart);
+ start = matchEnd;
}
- catch (std::runtime_error) {
- /* Basically too expensive to compute the regex results and it gave up, so pass through as text */
- newMessage.append(part);
+ if (start != text.end()) {
+ /* If there's plain text after the last emoticon, record it */
+ resultMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
}
- } else {
- newMessage.append(part);
}
+ catch (std::runtime_error) {
+ /* Basically too expensive to compute the regex results and it gave up, so pass through as text */
+ resultMessage.append(part);
+ }
+ } else {
+ resultMessage.append(part);
}
- parsedMessage.setParts(newMessage.getParts());
}
+ return resultMessage;
+ };
+
+ ChatWindow::ChatMessage parsedMessage = message;
+
+ // detect mentions of own nickname
+ HighlightAction ownMentionKeywordAction = highlightConfiguration_->ownMentionAction;
+ ownMentionKeywordAction.setSoundFilePath(boost::optional<std::string>());
+ ownMentionKeywordAction.setSystemNotificationEnabled(false);
+ if (!getNick().empty() && !highlightConfiguration_->ownMentionAction.isEmpty()) {
+ auto nicknameHighlightedMessage = highlightKeywordInChatMessage(parsedMessage, nick_, false, ownMentionKeywordAction);
+ auto highlightedParts = nicknameHighlightedMessage.getParts();
+ auto ownNicknamePart = std::find_if(highlightedParts.begin(), highlightedParts.end(), [&](std::shared_ptr<ChatWindow::ChatMessagePart>& part){
+ auto highlightPart = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part);
+ if (highlightPart && highlightPart->text == nick_) {
+ return true;
+ }
+ return false;
+ });
+ if (ownNicknamePart != highlightedParts.end()) {
+ parsedMessage.setHighlightActionOwnMention(highlightConfiguration_->ownMentionAction);
+ }
+ parsedMessage.setParts(nicknameHighlightedMessage.getParts());
+ }
+
+ // detect keywords
+ for (const auto& keywordHighlight : highlightConfiguration_->keywordHighlights) {
+ if (keywordHighlight.keyword.empty() || keywordHighlight.action.isEmpty()) {
+ continue;
+ }
+ auto newMessage = highlightKeywordInChatMessage(parsedMessage, keywordHighlight.keyword, keywordHighlight.matchCaseSensitive, keywordHighlight.action);
+ parsedMessage.setParts(newMessage.getParts());
}
return parsedMessage;
}
+
+ ChatWindow::ChatMessage ChatMessageParser::fullMessageHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& sender) {
+ auto fullHighlightedMessage = parsedMessage;
+
+ // contact highlighting
+ for (const auto& contactHighlight : highlightConfiguration_->contactHighlights) {
+ if (sender == contactHighlight.name) {
+ fullHighlightedMessage.setHighlightActionSender(contactHighlight.action);
+ break;
+ }
+ }
+
+ // general incoming messages
+ HighlightAction groupAction;
+ HighlightAction chatAction;
+
+ switch (mode_) {
+ case Mode::GroupChat:
+ groupAction.setSoundFilePath(highlightConfiguration_->playSoundOnIncomingGroupchatMessages ? boost::optional<std::string>("") : boost::optional<std::string>());
+ groupAction.setSystemNotificationEnabled(highlightConfiguration_->showNotificationOnIncomingGroupchatMessages);
+ fullHighlightedMessage.setHighlightActionGroupMessage(groupAction);
+ break;
+
+ case Mode::Chat:
+ chatAction.setSoundFilePath(highlightConfiguration_->playSoundOnIncomingDirectMessages ? boost::optional<std::string>("") : boost::optional<std::string>());
+ chatAction.setSystemNotificationEnabled(highlightConfiguration_->showNotificationOnIncomingDirectMessages);
+ fullHighlightedMessage.setHighlightActonDirectMessage(chatAction);
+ break;
+ }
+
+ return fullHighlightedMessage;
+ }
}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h
index 4bed669..de5eac9 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.h
+++ b/Swift/Controllers/Chat/ChatMessageParser.h
@@ -1,26 +1,44 @@
/*
- * Copyright (c) 2013-2014 Isode Limited.
+ * Copyright (c) 2013-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
+#include <memory>
#include <string>
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
+ /**
+ * @brief The ChatMessageParser class takes an emoticon map, a \ref HighlightConfiguration, and a boolean that indicates if the message context is in a MUC or not.
+ * The class handles parsing a message string and identifies emoticons, URLs, and various highlights.
+ */
class ChatMessageParser {
public:
- ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false);
- ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false);
+ enum class Mode { Chat, GroupChat };
+
+ public:
+ ChatMessageParser(const std::map<std::string, std::string>& emoticons, std::shared_ptr<HighlightConfiguration> highlightConfiguration, Mode mode = Mode::Chat);
+
+ void setNick(const std::string& nick) { nick_ = nick; }
+ std::string getNick() const { return nick_; }
+
+ ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& sender = "", bool senderIsSelf = false);
+
private:
ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage);
- ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick);
+ ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage);
+ ChatWindow::ChatMessage fullMessageHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& sender);
+
+ private:
std::map<std::string, std::string> emoticons_;
- HighlightRulesListPtr highlightRules_;
- bool mucMode_;
+ std::shared_ptr<HighlightConfiguration> highlightConfiguration_;
+ Mode mode_;
+ std::string nick_;
};
}
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index f55df1e..fc96701 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,32 +1,32 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/Chat/ChatsManager.h>
#include <memory>
#include <boost/algorithm/string.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/optional.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Disco/DiscoServiceWalker.h>
#include <Swiften/Disco/FeatureOracle.h>
#include <Swiften/Elements/CarbonsReceived.h>
#include <Swiften/Elements/CarbonsSent.h>
#include <Swiften/Elements/ChatState.h>
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
@@ -719,61 +719,61 @@ void ChatsManager::setOnline(bool enabled) {
localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
localMUCServiceFinderWalker_->beginWalk();
}
if (chatListWindow_) {
chatListWindow_->setBookmarksEnabled(enabled);
}
}
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())) {
for (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());
- std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */
+ std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getConfiguration(), ChatMessageParser::Mode::Chat); /* a message parser that knows this is a chat (not a room/MUC) */
ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_);
chatControllers_[contact] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
controller->onWindowClosed.connect(boost::bind(&ChatsManager::handleChatClosed, this, contact));
controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3));
updatePresenceReceivingStateOnChatController(contact);
controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty());
return controller;
}
ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) {
ChatController* controller = getChatControllerIfExists(contact);
return controller ? controller : createNewChatController(contact);
}
ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) {
if (chatControllers_.find(contact) == chatControllers_.end()) {
if (mucRegistry_->isMUC(contact.toBare())) {
return nullptr;
}
//Need to look for an unbound window to bind first
JID bare(contact.toBare());
if (chatControllers_.find(bare) != chatControllers_.end()) {
if (rebindIfNeeded) {
rebindControllerJID(bare, contact);
}
else {
return chatControllers_[bare];
@@ -808,62 +808,62 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti
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()) {
if (stanzaChannel_->isAvailable()) {
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 = nullptr;
SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = nullptr;
if (reuseChatwindow) {
chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);
}
- std::shared_ptr<ChatMessageParser> chatMessageParser = std::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_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_, mucBookmarkManager_);
+ std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat); /* 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_, nickResolver_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_, mucBookmarkManager_);
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->onUserNicknameChanged.connect(boost::bind(&ChatsManager::handleUserNicknameChanged, this, controller, _1, _2));
controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true));
controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
if (!stanzaChannel_->isAvailable()) {
/* When online, the MUC is added to the registry in MUCImpl::internalJoin. This method is not
* called when Swift is offline, so we add it here as only MUCs in the registry are rejoined
* when going back online.
*/
mucRegistry_->addMUC(mucJID.toBare());
}
handleChatActivity(mucJID.toBare(), "", true);
}
mucControllers_[mucJID]->showChatWindow();
return muc;
}
void ChatsManager::handleSearchMUCRequest() {
mucSearchController_->openSearchWindow();
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index df54d73..c476cf3 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -7,162 +7,162 @@
#include <Swift/Controllers/Chat/MUCController.h>
#include <algorithm>
#include <memory>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/format.h>
#include <Swiften/Base/Tristate.h>
#include <Swiften/Client/BlockList.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Elements/Delay.h>
#include <Swiften/Elements/Thread.h>
#include <Swiften/MUC/MUC.h>
#include <Swiften/MUC/MUCBookmark.h>
#include <Swiften/MUC/MUCBookmarkManager.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/Highlighting/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/SetMUC.h>
#include <Swift/Controllers/Roster/ItemOperations/SetPresence.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/RequestChangeBlockStateUIEvent.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 {
class MUCBookmarkPredicate {
public:
MUCBookmarkPredicate(const JID& mucJID) : roomJID_(mucJID) { }
bool operator()(const MUCBookmark& operand) {
return operand.getRoom() == roomJID_;
}
private:
JID roomJID_;
};
/**
* 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,
+ NickResolver* nickResolver,
PresenceOracle* presenceOracle,
AvatarManager* avatarManager,
UIEventStream* uiEventStream,
bool useDelayForLatency,
TimerFactory* timerFactory,
EventController* eventController,
EntityCapsProvider* entityCapsProvider,
XMPPRoster* xmppRoster,
HistoryController* historyController,
MUCRegistry* mucRegistry,
HighlightManager* highlightManager,
ClientBlockListManager* clientBlockListManager,
std::shared_ptr<ChatMessageParser> chatMessageParser,
bool isImpromptu,
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,
VCardManager* vcardManager,
MUCBookmarkManager* mucBookmarkManager) :
- 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), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) {
+ ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), nickResolver, 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), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) {
parting_ = true;
joined_ = false;
lastWasPresence_ = false;
shouldJoinOnReconnect_ = true;
doneGettingHistory_ = false;
xmppRoster_ = xmppRoster;
subject_ = "";
isInitialJoin_ = true;
roster_ = std::unique_ptr<Roster>(new Roster(false, true));
rosterVCardProvider_ = new RosterVCardProvider(roster_.get(), vcardManager, JID::WithResource);
completer_ = new TabComplete();
chatWindow_->setRosterModel(roster_.get());
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));
chatWindow_->onUnblockUserRequest.connect(boost::bind(&MUCController::handleUnblockUserRequest, this));
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_->onOccupantNicknameChanged.connect(boost::bind(&MUCController::handleOccupantNicknameChanged, this, _1, _2));
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_);
+ chatMessageParser_->setNick(nick_);
if (timerFactory && stanzaChannel_->isAvailable()) {
loginCheckTimer_ = std::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
loginCheckTimer_->start();
}
else {
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You are currently offline. You will enter this room when you are connected.")), ChatWindow::DefaultDirection);
}
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());
}
if (stanzaChannel->isAvailable()) {
MUCController::setOnline(true);
}
if (avatarManager_ != nullptr) {
avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
}
MUCController::handleBareJIDCapsChanged(muc->getJID());
eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1));
// setup handling of MUC bookmark changes
mucBookmarkManagerBookmarkAddedConnection_ = (mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&MUCController::handleMUCBookmarkAdded, this, _1)));
mucBookmarkManagerBookmarkRemovedConnection_ = (mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&MUCController::handleMUCBookmarkRemoved, this, _1)));
@@ -566,64 +566,65 @@ void MUCController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messa
}
isInitialJoin_ = false;
chatWindow_->setSubject(message->getSubject());
doneGettingHistory_ = true;
subject_ = message->getSubject();
}
if (!doneGettingHistory_ && !message->getPayload<Delay>()) {
doneGettingHistory_ = true;
}
if (!doneGettingHistory_) {
checkDuplicates(message);
messageEvent->conclude();
}
}
void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) {
if (from.isBare()) {
chatWindow_->addSystemMessage(message, ChatWindow::DefaultDirection);
}
else {
ChatControllerBase::addMessageHandleIncomingMessage(from, message, senderIsSelf, label, time);
}
}
void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) {
std::shared_ptr<Message> message = messageEvent->getStanza();
if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) {
if (messageTargetsMe(message) || isImpromptu_) {
+ highlighter_->handleSystemNotifications(chatMessage, messageEvent);
eventController_->handleIncomingEvent(messageEvent);
}
if (!messageEvent->getConcluded()) {
- handleHighlightActions(chatMessage);
+ highlighter_->handleSoundNotifications(chatMessage);
}
}
}
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);
@@ -1084,61 +1085,61 @@ void MUCController::addRecentLogs() {
}
}
void MUCController::checkDuplicates(std::shared_ptr<Message> newMessage) {
std::string body = newMessage->getBody().get_value_or("");
JID jid = newMessage->getFrom();
boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp();
for (const auto& message : boost::adaptors::reverse(joinContext_)) {
boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset());
if (time && time < messageTime) {
break;
}
if (time && time != messageTime) {
continue;
}
if (message.getFromJID() != jid) {
continue;
}
if (message.getMessage() != body) {
continue;
}
// Mark the message as unreadable
newMessage->setBody("");
}
}
void MUCController::setNick(const std::string& nick) {
nick_ = nick;
- highlighter_->setNick(nick_);
+ chatMessageParser_->setNick(nick);
}
Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) {
Form::ref result = std::make_shared<Form>(Form::SubmitType);
std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"};
std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4);
for (const auto& field : roomConfigurationForm->getFields()) {
std::shared_ptr<FormField> resultField;
if (field->getName() == "muc#roomconfig_enablelogging") {
resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
}
if (field->getName() == "muc#roomconfig_persistentroom") {
resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
}
if (field->getName() == "muc#roomconfig_publicroom") {
resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
}
if (field->getName() == "muc#roomconfig_whois") {
resultField = std::make_shared<FormField>(FormField::ListSingleType, "anyone");
}
if (field->getName() == "FORM_TYPE") {
resultField = std::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig");
}
if (resultField) {
impromptuConfigsMissing.erase(field->getName());
resultField->setName(field->getName());
result->addField(resultField);
}
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 7ec2eb4..6244f6d 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -1,87 +1,87 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
#include <map>
#include <memory>
#include <set>
#include <string>
#include <boost/signals2.hpp>
#include <boost/signals2/connection.hpp>
#include <Swiften/Base/Override.h>
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/Elements/MUCOccupant.h>
#include <Swiften/Elements/Message.h>
#include <Swiften/JID/JID.h>
#include <Swiften/MUC/MUC.h>
#include <Swiften/Network/Timer.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;
class ClientBlockListManager;
class MUCBookmarkManager;
class MUCBookmark;
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* xmppRoster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager);
+ MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* xmppRoster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager);
virtual ~MUCController();
boost::signals2::signal<void ()> onUserLeft;
boost::signals2::signal<void ()> onUserJoined;
boost::signals2::signal<void ()> onImpromptuConfigCompleted;
boost::signals2::signal<void (const std::string&, const std::string& )> onUserNicknameChanged;
virtual void setOnline(bool online) SWIFTEN_OVERRIDE;
virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE;
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);
static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname);
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:
virtual void preSendMessageRequest(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE;
virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE;
virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE;
virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE;
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message> message) const SWIFTEN_OVERRIDE;
virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) SWIFTEN_OVERRIDE;
virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE;
virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE;
virtual void cancelReplaces() SWIFTEN_OVERRIDE;
virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
index bc72b33..163a38a 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
@@ -1,281 +1,292 @@
/*
- * Copyright (c) 2013-2016 Isode Limited.
+ * Copyright (c) 2013-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
-#include <cppunit/extensions/HelperMacros.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
-#include <hippomocks.h>
+#include <gtest/gtest.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.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(testNoColourNoHighlight);
- CPPUNIT_TEST_SUITE_END();
-
-public:
- void setUp() {
+// Common test state
+class ChatMessageParserTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
smile1_ = ":)";
smile1Path_ = "/blah/smile1.png";
smile2_ = ":(";
smile2Path_ = "/blah/smile2.jpg";
emoticons_[smile1_] = smile1Path_;
emoticons_[smile2_] = smile2Path_;
}
- void tearDown() {
+ virtual void TearDown() {
emoticons_.clear();
}
- void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+ static void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+ ASSERT_LT(index, result.getParts().size());
std::shared_ptr<ChatWindow::ChatTextMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]);
- CPPUNIT_ASSERT_EQUAL(text, part->text);
+ ASSERT_EQ(text, part->text);
}
- void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) {
+ static void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) {
+ ASSERT_LT(index, result.getParts().size());
std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]);
- CPPUNIT_ASSERT(!!part);
- CPPUNIT_ASSERT_EQUAL(text, part->alternativeText);
- CPPUNIT_ASSERT_EQUAL(path, part->imagePath);
+ ASSERT_NE(nullptr, part);
+ ASSERT_EQ(text, part->alternativeText);
+ ASSERT_EQ(path, part->imagePath);
}
-#define assertHighlight(RESULT, INDEX, TEXT, EXPECTED_HIGHLIGHT) \
- { \
- std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(RESULT.getParts()[INDEX]); \
- CPPUNIT_ASSERT_EQUAL(std::string(TEXT), part->text); \
- CPPUNIT_ASSERT(EXPECTED_HIGHLIGHT == part->action); \
- }
-
- void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+ static void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+ ASSERT_LT(index, result.getParts().size());
std::shared_ptr<ChatWindow::ChatURIMessagePart> part = std::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);
- rule.getAction().setTextBackground("white");
- return rule;
- }
-
- static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord)
- {
- std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>();
- list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord));
- return list;
- }
-
- static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2)
- {
- std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>();
- list->addRule(rule1);
- list->addRule(rule2);
- return list;
- }
-
- static HighlightRulesListPtr ruleListWithNickHighlight(bool withHighlightColour = true)
- {
- HighlightRule rule;
- rule.setMatchChat(true);
- rule.setNickIsKeyword(true);
- rule.setMatchCase(true);
- rule.setMatchWholeWords(true);
- if (withHighlightColour) {
- rule.getAction().setTextBackground("white");
- }
- std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>();
- list->addRule(rule);
- return list;
- }
-
- void testFullBody() {
- const std::string no_special_message = "a message with no special content";
- ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>());
- ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message);
- assertText(result, 0, no_special_message);
-
- HighlightRulesListPtr highlightRuleList = ruleListFromKeyword("trigger", false, false);
- testling = ChatMessageParser(emoticons_, highlightRuleList);
- 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, " ");
- assertHighlight(result, 4, "trigger", highlightRuleList->getRule(0).getAction());
- assertText(result, 5, " ");
- assertEmoticon(result, 6, smile1_, smile1Path_);
- assertText(result, 7, " ");
- 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", highlightRuleList->getRule(0).getAction());
- 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", highlightRuleList->getRule(0).getAction());
-
- testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
- result = testling.parseMessageBody("partialTrIgGeRmatch");
- assertText(result, 0, "partial");
- assertHighlight(result, 1, "TrIgGeR", highlightRuleList->getRule(0).getAction());
- 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", highlightRuleList->getRule(0).getAction());
- assertText(result, 2, " two ");
- assertHighlight(result, 3, "three", highlightRuleList->getRule(0).getAction());
-
- 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", highlightRuleList->getRule(0).getAction());
- assertText(result, 2, " two ");
- assertHighlight(result, 3, "tHrEe", highlightRuleList->getRule(0).getAction());
-
- 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", highlightRuleList->getRule(0).getAction());
- 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", highlightRuleList->getRule(0).getAction());
- assertText(result, 2, "two");
- assertHighlight(result, 3, "three", highlightRuleList->getRule(0).getAction());
-
- testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false)));
- result = testling.parseMessageBody("zeroOnEtwoThReE");
- assertText(result, 0, "zeroOnEtwo");
- assertHighlight(result, 1, "ThReE", highlightRuleList->getRule(0).getAction());
-
- testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false)));
- result = testling.parseMessageBody("zeroonetwothree");
- assertText(result, 0, "zeroonetwo");
- assertHighlight(result, 1, "three", highlightRuleList->getRule(0).getAction());
-
- testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true)));
- result = testling.parseMessageBody("zeroonetwothree");
- assertText(result, 0, "zeroonetwothree");
-
- testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
- result = testling.parseMessageBody("Alice", "Alice");
- assertHighlight(result, 0, "Alice", highlightRuleList->getRule(0).getAction());
-
- testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
- result = testling.parseMessageBody("TextAliceText", "Alice");
- assertText(result, 0, "TextAliceText");
-
- testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
- result = testling.parseMessageBody("Text Alice Text", "Alice");
- assertText(result, 0, "Text ");
- assertHighlight(result, 1, "Alice", highlightRuleList->getRule(0).getAction());
- assertText(result, 2, " Text");
-
- testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
- result = testling.parseMessageBody("Alice Text", "Alice");
- assertHighlight(result, 0, "Alice", highlightRuleList->getRule(0).getAction());
- assertText(result, 1, " Text");
-
- testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
- result = testling.parseMessageBody("Text Alice", "Alice");
- assertText(result, 0, "Text ");
- assertHighlight(result, 1, "Alice", highlightRuleList->getRule(0).getAction());
- }
-
- void testOneEmoticon() {
- ChatMessageParser testling(emoticons_, std::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_, std::make_shared<HighlightManager::HighlightRulesList>());
- ChatWindow::ChatMessage result = testling.parseMessageBody(":)");
- assertEmoticon(result, 0, smile1_, smile1Path_);
- }
-
- void testHiddenEmoticon() {
- ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>());
- ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a");
- assertText(result, 0, "b:)a");
- }
-
- void testEndlineEmoticon() {
- ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>());
- ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)");
- assertText(result, 0, "Lazy");
- assertEmoticon(result, 1, smile1_, smile1Path_);
+ ASSERT_EQ(text, part->target);
}
- void testBoundedEmoticons() {
- ChatMessageParser testling(emoticons_, std::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 assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const HighlightAction& action) {
+ ASSERT_LT(index, result.getParts().size());
+ std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]);
+ ASSERT_EQ(std::string(text), part->text);
+ ASSERT_EQ(action, part->action);
}
- void testEmoticonParenthesis() {
- ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>());
- ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))");
- assertText(result, 0, "(Like this ");
- assertEmoticon(result, 1, smile1_, smile1Path_);
- assertText(result, 2, ")");
+ static const std::shared_ptr<HighlightConfiguration> highlightConfigFromKeyword(const std::string& keyword, bool matchCase) {
+ std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>();
+ HighlightConfiguration::KeywordHightlight keywordHighlight;
+ keywordHighlight.keyword = keyword;
+ keywordHighlight.matchCaseSensitive = matchCase;
+ keywordHighlight.action.setFrontColor(std::string("#121212"));
+ config->keywordHighlights.push_back(keywordHighlight);
+ return config;
}
- void testNoColourNoHighlight() {
- ChatMessageParser testling(emoticons_, ruleListWithNickHighlight(false));
- ChatWindow::ChatMessage result = testling.parseMessageBody("Alice", "Alice");
- assertText(result, 0, "Alice");
+ static const std::shared_ptr<HighlightConfiguration> mergeHighlightConfig(const std::shared_ptr<HighlightConfiguration>& configA, const std::shared_ptr<HighlightConfiguration>& configB) {
+ std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>();
+ config->keywordHighlights.insert(config->keywordHighlights.end(), configA->keywordHighlights.begin(), configA->keywordHighlights.end());
+ config->keywordHighlights.insert(config->keywordHighlights.end(), configB->keywordHighlights.begin(), configB->keywordHighlights.end());
+ return config;
}
-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);
+TEST_F(ChatMessageParserTest, testNoHighlightingWithEmtpyConfiguration) {
+ const std::string no_special_message = "a message with no special content";
+ ChatMessageParser testling(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(no_special_message);
+ assertText(result, 0, no_special_message);
+}
+
+TEST_F(ChatMessageParserTest, testSimpleHighlightAndEmojiAndUrlParsing) {
+ auto highlightConfig = highlightConfigFromKeyword("trigger", false);
+ auto testling = ChatMessageParser(emoticons_, highlightConfig);
+ auto 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, " ");
+ assertHighlight(result, 4, "trigger", highlightConfig->keywordHighlights[0].action);
+ assertText(result, 5, " ");
+ assertEmoticon(result, 6, smile1_, smile1Path_);
+ assertText(result, 7, " ");
+ assertURL(result, 8, "http://wonderland.lit/blah");
+ assertText(result, 9, " ");
+ assertURL(result, 10, "http://denmark.lit");
+ assertText(result, 11, " boom boom");
+}
+
+TEST_F(ChatMessageParserTest, testNoKeywordHighlightAsPartOfLongerWords) {
+ auto testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false));
+ auto result = testling.parseMessageBody("testtriggermessage");
+ assertText(result, 0, "testtriggermessage");
+}
+
+TEST_F(ChatMessageParserTest, testCaseInsensitiveKeyordHighlight) {
+ auto config = highlightConfigFromKeyword("trigger", true);
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("TrIgGeR");
+ assertText(result, 0, "TrIgGeR");
+
+ testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false));
+ result = testling.parseMessageBody("TrIgGeR");
+ assertHighlight(result, 0, "TrIgGeR", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testMultipleKeywordHighlights) {
+ auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false));
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("zero one two three");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "one", config->keywordHighlights[0].action);
+ assertText(result, 2, " two ");
+ assertHighlight(result, 3, "three", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testMultipleCaseInsensitiveKeywordHighlights) {
+ auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false));
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action);
+ assertText(result, 2, " two ");
+ assertHighlight(result, 3, "tHrEe", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testMultipleCaseSensitiveKeywordHighlights) {
+ auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", true));
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero ");
+ assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action);
+ assertText(result, 2, " two tHrEe");
+
+ config = mergeHighlightConfig(highlightConfigFromKeyword("one", true), highlightConfigFromKeyword("three", false));
+ testling = ChatMessageParser(emoticons_, config);
+ result = testling.parseMessageBody("zero oNe two tHrEe");
+ assertText(result, 0, "zero oNe two ");
+ assertHighlight(result, 1, "tHrEe", config->keywordHighlights[0].action);
+}
+
+TEST_F(ChatMessageParserTest, testOneEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(" :) ");
+ assertText(result, 0, " ");
+ assertEmoticon(result, 1, smile1_, smile1Path_);
+ assertText(result, 2, " ");
+}
+
+TEST_F(ChatMessageParserTest, testBareEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(":)");
+ assertEmoticon(result, 0, smile1_, smile1Path_);
+}
+
+TEST_F(ChatMessageParserTest, testHiddenEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody("b:)a");
+ assertText(result, 0, "b:)a");
+}
+
+TEST_F(ChatMessageParserTest, testEndlineEmoticon) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody("Lazy:)");
+ assertText(result, 0, "Lazy");
+ assertEmoticon(result, 1, smile1_, smile1Path_);
+}
+
+TEST_F(ChatMessageParserTest, testBoundedEmoticons) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody(":)Lazy:(");
+ assertEmoticon(result, 0, smile1_, smile1Path_);
+ assertText(result, 1, "Lazy");
+ assertEmoticon(result, 2, smile2_, smile2Path_);
+}
+
+TEST_F(ChatMessageParserTest, testEmoticonParenthesis) {
+ auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>());
+ auto result = testling.parseMessageBody("(Like this :))");
+ assertText(result, 0, "(Like this ");
+ assertEmoticon(result, 1, smile1_, smile1Path_);
+ assertText(result, 2, ")");
+}
+
+TEST_F(ChatMessageParserTest, testSenderAndKeywordHighlighting) {
+ auto config = std::make_shared<HighlightConfiguration>();
+ auto contactHighlight = HighlightConfiguration::ContactHighlight();
+ contactHighlight.action.setFrontColor(std::string("#f0f0f0"));
+ contactHighlight.action.setBackColor(std::string("#0f0f0f"));
+ contactHighlight.name = "Romeo";
+ config->contactHighlights.push_back(contactHighlight);
+ auto keywordHighlight = HighlightConfiguration::KeywordHightlight();
+ keywordHighlight.action.setFrontColor(std::string("#abcdef"));
+ keywordHighlight.action.setBackColor(std::string("#fedcba"));
+ keywordHighlight.keyword = "XMPP";
+ config->keywordHighlights.push_back(keywordHighlight);
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo");
+ assertText(result, 0, "Heard any news about ");
+ assertHighlight(result, 1, "xmpp", keywordHighlight.action);
+ assertText(result, 2, " recently?");
+ ASSERT_EQ(contactHighlight.action, result.getHighlightActionSender());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testKeywordWithEmptyActionIsIgnored) {
+ auto config = std::make_shared<HighlightConfiguration>();
+ auto keywordHighlight = HighlightConfiguration::KeywordHightlight();
+ keywordHighlight.keyword = "XMPP";
+ config->keywordHighlights.push_back(keywordHighlight);
+ auto testling = ChatMessageParser(emoticons_, config);
+ auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo");
+ assertText(result, 0, "Heard any news about xmpp recently?");
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testMeMessageAndOwnMention) {
+ auto config = std::make_shared<HighlightConfiguration>();
+ config->ownMentionAction.setFrontColor(std::string("#f0f0f0"));
+ config->ownMentionAction.setBackColor(std::string("#0f0f0f"));
+ config->ownMentionAction.setSoundFilePath(std::string("someSoundFile.wav"));
+ auto ownMentionActionForPart = config->ownMentionAction;
+ ownMentionActionForPart.setSoundFilePath(boost::optional<std::string>());
+ auto testling = ChatMessageParser(emoticons_, config);
+ testling.setNick("Juliet");
+ auto result = testling.parseMessageBody("/me wonders when Juliet is coming?", "Romeo");
+ assertText(result, 0, "wonders when ");
+ assertHighlight(result, 1, "Juliet", ownMentionActionForPart);
+ assertText(result, 2, " is coming?");
+ ASSERT_EQ(true, result.isMeCommand());
+ ASSERT_EQ(config->ownMentionAction, result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testSoundAndNotificationOnDirectMessage) {
+ auto defaultConfiguration = std::make_shared<HighlightConfiguration>();
+ defaultConfiguration->playSoundOnIncomingDirectMessages = true;
+ defaultConfiguration->showNotificationOnIncomingDirectMessages = true;
+ defaultConfiguration->ownMentionAction.setFrontColor(std::string("black"));
+ defaultConfiguration->ownMentionAction.setBackColor(std::string("yellow"));
+ defaultConfiguration->ownMentionAction.setSoundFilePath(std::string(""));
+ defaultConfiguration->ownMentionAction.setSystemNotificationEnabled(true);
+
+ auto testling = ChatMessageParser(emoticons_, defaultConfiguration, ChatMessageParser::Mode::Chat);
+ auto result = testling.parseMessageBody("I wonder when Juliet is coming?", "Romeo");
+
+ ASSERT_EQ(std::string(""), result.getHighlightActionDirectMessage().getSoundFilePath().get_value_or(std::string("somethingElse")));
+ ASSERT_EQ(true, result.getHighlightActionDirectMessage().isSystemNotificationEnabled());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+}
+
+TEST_F(ChatMessageParserTest, testWithDefaultConfiguration) {
+ DummySettingsProvider settings;
+ HighlightManager manager(&settings);
+ manager.resetToDefaultConfiguration();
+ auto testling = ChatMessageParser(emoticons_, manager.getConfiguration(), ChatMessageParser::Mode::GroupChat);
+ testling.setNick("Juliet");
+ auto result = testling.parseMessageBody("Hello, how is it going?", "Romeo");
+ assertText(result, 0, "Hello, how is it going?");
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionSender());
+
+ result = testling.parseMessageBody("Juliet, seen the new interface design?", "Romeo");
+ auto mentionKeywordAction = manager.getConfiguration()->ownMentionAction;
+ mentionKeywordAction.setSoundFilePath(boost::optional<std::string>());
+ mentionKeywordAction.setSystemNotificationEnabled(false);
+ assertHighlight(result, 0, "Juliet", mentionKeywordAction);
+ assertText(result, 1, ", seen the new interface design?");
+ ASSERT_EQ(manager.getConfiguration()->ownMentionAction, result.getHighlightActionOwnMention());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage());
+ ASSERT_EQ(HighlightAction(), result.getHighlightActionSender());
+}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index cff54f8..80f8346 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -1,32 +1,32 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <map>
#include <set>
#include <boost/bind.hpp>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <hippomocks.h>
#include <Swiften/Avatars/AvatarMemoryStorage.h>
#include <Swiften/Avatars/NullAvatarManager.h>
#include <Swiften/Base/Algorithm.h>
#include <Swiften/Client/Client.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/DummyStanzaChannel.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/Crypto/CryptoProvider.h>
#include <Swiften/Crypto/PlatformCryptoProvider.h>
#include <Swiften/Disco/DummyEntityCapsProvider.h>
#include <Swiften/Elements/CarbonsReceived.h>
#include <Swiften/Elements/CarbonsSent.h>
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swiften/Elements/Forwarded.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
#include <Swiften/Elements/MUCUserPayload.h>
@@ -104,61 +104,61 @@ class ChatsManagerTest : public CppUnit::TestFixture {
public:
void setUp() {
mocks_ = new MockRepository();
jid_ = JID("test@test.com/resource");
stanzaChannel_ = new DummyStanzaChannel();
iqRouter_ = new IQRouter(stanzaChannel_);
eventController_ = new EventController();
chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>();
xmppRoster_ = new XMPPRosterImpl();
mucRegistry_ = new MUCRegistry();
nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, nullptr, mucRegistry_);
presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_);
serverDiscoInfo_ = std::make_shared<DiscoInfo>();
presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_);
uiEventStream_ = new UIEventStream();
entityCapsProvider_ = new DummyEntityCapsProvider();
chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>();
settings_ = new DummySettingsProvider();
profileSettings_ = new ProfileSettingsProvider("a", settings_);
chatListWindow_ = new MockChatListWindow();
ftManager_ = new DummyFileTransferManager();
ftOverview_ = new FileTransferOverview(ftManager_);
avatarManager_ = new NullAvatarManager();
wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsProvider_);
wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_);
highlightManager_ = new HighlightManager(settings_);
- highlightManager_->resetToDefaultRulesList();
+ highlightManager_->resetToDefaultConfiguration();
handledHighlightActions_ = 0;
soundsPlayed_.clear();
highlightManager_->onHighlight.connect(boost::bind(&ChatsManagerTest::handleHighlightAction, this, _1));
crypto_ = PlatformCryptoProvider::create();
vcardStorage_ = new VCardMemoryStorage(crypto_);
vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_);
mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, nullptr, mucRegistry_, entityCapsProvider_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, nullptr, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, vcardManager_);
manager_->setAvatarManager(avatarManager_);
}
void tearDown() {
delete highlightManager_;
delete profileSettings_;
delete avatarManager_;
delete manager_;
delete clientBlockListManager_;
delete vcardManager_;
delete vcardStorage_;
delete crypto_;
delete ftOverview_;
delete ftManager_;
delete wbSessionManager_;
delete wbManager_;
delete directedPresenceSender_;
delete presenceSender_;
delete presenceOracle_;
@@ -759,128 +759,115 @@ public:
stanzaChannel_->onIQReceived(infoResponse);
CPPUNIT_ASSERT_EQUAL(true, window->impromptuMUCSupported_);
manager_->setOnline(false);
CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_);
}
void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) {
JID messageJID("testling@test.com/resource1");
xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from);
MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true);
std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->countSentStanzaOfType<Message>());
xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to);
message->setID("2");
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>());
Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1);
CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr);
}
void testChatControllerHighlightingNotificationTesting() {
- HighlightRule keywordRuleA;
- keywordRuleA.setMatchChat(true);
- std::vector<std::string> keywordsA;
- keywordsA.push_back("Romeo");
- keywordRuleA.setKeywords(keywordsA);
- keywordRuleA.getAction().setTextColor("yellow");
- keywordRuleA.getAction().setPlaySound(true);
- highlightManager_->insertRule(0, keywordRuleA);
-
- HighlightRule keywordRuleB;
- keywordRuleB.setMatchChat(true);
- std::vector<std::string> keywordsB;
- keywordsB.push_back("Juliet");
- keywordRuleB.setKeywords(keywordsB);
- keywordRuleB.getAction().setTextColor("green");
- keywordRuleB.getAction().setPlaySound(true);
- keywordRuleB.getAction().setSoundFile("/tmp/someotherfile.wav");
- highlightManager_->insertRule(0, keywordRuleB);
+ HighlightConfiguration::KeywordHightlight keywordRuleA;
+ keywordRuleA.keyword = "Romeo";
+ keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow"));
+ keywordRuleA.action.setSoundFilePath(boost::optional<std::string>(""));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA);
+
+ HighlightConfiguration::KeywordHightlight keywordRuleB;
+ keywordRuleB.keyword = "Juliet";
+ keywordRuleB.action.setFrontColor(boost::optional<std::string>("green"));
+ keywordRuleB.action.setSoundFilePath(boost::optional<std::string>("/tmp/someotherfile.wav"));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB);
JID messageJID = JID("testling@test.com");
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
std::shared_ptr<Message> message(new Message());
message->setFrom(messageJID);
std::string body("This message should cause two sounds: Juliet and Romeo.");
message->setBody(body);
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(2, handledHighlightActions_);
- CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.getAction().getSoundFile()) != soundsPlayed_.end());
- CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.getAction().getSoundFile()) != soundsPlayed_.end());
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
}
void testChatControllerHighlightingNotificationDeduplicateSounds() {
- HighlightRule keywordRuleA;
- keywordRuleA.setMatchChat(true);
- std::vector<std::string> keywordsA;
- keywordsA.push_back("Romeo");
- keywordRuleA.setKeywords(keywordsA);
- keywordRuleA.getAction().setTextColor("yellow");
- keywordRuleA.getAction().setPlaySound(true);
- highlightManager_->insertRule(0, keywordRuleA);
-
- HighlightRule keywordRuleB;
- keywordRuleB.setMatchChat(true);
- std::vector<std::string> keywordsB;
- keywordsB.push_back("Juliet");
- keywordRuleB.setKeywords(keywordsB);
- keywordRuleB.getAction().setTextColor("green");
- keywordRuleB.getAction().setPlaySound(true);
- highlightManager_->insertRule(0, keywordRuleB);
+ auto keywordRuleA = HighlightConfiguration::KeywordHightlight();
+ keywordRuleA.keyword = "Romeo";
+ keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow"));
+ keywordRuleA.action.setSoundFilePath(boost::optional<std::string>(""));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA);
+
+ auto keywordRuleB = HighlightConfiguration::KeywordHightlight();
+ keywordRuleB.keyword = "Juliet";
+ keywordRuleB.action.setFrontColor(boost::optional<std::string>("green"));
+ keywordRuleB.action.setSoundFilePath(boost::optional<std::string>(""));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB);
JID messageJID = JID("testling@test.com");
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
std::shared_ptr<Message> message(new Message());
message->setFrom(messageJID);
std::string body("This message should cause one sound, because both actions have the same sound: Juliet and Romeo.");
message->setBody(body);
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(1, handledHighlightActions_);
- CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.getAction().getSoundFile()) != soundsPlayed_.end());
- CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.getAction().getSoundFile()) != soundsPlayed_.end());
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
+ CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end());
}
void testChatControllerMeMessageHandling() {
JID messageJID("testling@test.com/resource1");
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
std::shared_ptr<Message> message(new Message());
message->setFrom(messageJID);
std::string body("/me is feeling delighted.");
message->setBody(body);
manager_->handleIncomingMessage(message);
CPPUNIT_ASSERT_EQUAL(std::string("is feeling delighted."), window->bodyFromMessage(window->lastAddedAction_));
}
void testRestartingMUCComponentCrash() {
JID mucJID = JID("teaparty@rooms.wonderland.lit");
JID self = JID("girl@wonderland.lit/rabbithole");
std::string nick = "aLiCe";
MockChatWindow* window;
auto genRemoteMUCPresence = [=]() {
auto presence = Presence::create();
presence->setFrom(mucJID.withResource(nick));
presence->setTo(self);
return presence;
};
@@ -895,66 +882,67 @@ public:
}
{
auto firstPresence = genRemoteMUCPresence();
firstPresence->setType(Presence::Unavailable);
auto userPayload = std::make_shared<MUCUserPayload>();
userPayload->addItem(MUCItem(MUCOccupant::Owner, MUCOccupant::NoRole));
firstPresence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(firstPresence);
}
CPPUNIT_ASSERT_EQUAL(std::string("Couldn't enter room: Unable to enter this room."), MockChatWindow::bodyFromMessage(window->lastAddedErrorMessage_));
{
auto presence = genRemoteMUCPresence();
presence->setType(Presence::Unavailable);
auto userPayload = std::make_shared<MUCUserPayload>();
userPayload->addStatusCode(303);
auto item = MUCItem(MUCOccupant::Owner, self, MUCOccupant::Moderator);
item.nick = nick;
userPayload->addItem(item);
userPayload->addStatusCode(110);
presence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(presence);
}
}
void testChatControllerMeMessageHandlingInMUC() {
JID mucJID("mucroom@rooms.test.com");
std::string nickname = "toodles";
+ //highlightManager_->resetToDefaultConfiguration();
+
// add highlight rule for 'foo'
- HighlightRule fooHighlight;
- fooHighlight.setKeywords({"foo"});
- fooHighlight.setMatchMUC(true);
- fooHighlight.getAction().setTextBackground("green");
- highlightManager_->insertRule(0, fooHighlight);
+ HighlightConfiguration::KeywordHightlight keywordHighlight;
+ keywordHighlight.keyword = "foo";
+ keywordHighlight.action.setBackColor(boost::optional<std::string>("green"));
+ highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordHighlight);
MockChatWindow* window = new MockChatWindow();
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window);
uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname));
auto genRemoteMUCPresence = [=]() {
auto presence = Presence::create();
presence->setFrom(mucJID.withResource(nickname));
presence->setTo(jid_);
return presence;
};
{
auto presence = genRemoteMUCPresence();
auto userPayload = std::make_shared<MUCUserPayload>();
userPayload->addStatusCode(110);
userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator));
presence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(presence);
}
{
auto presence = genRemoteMUCPresence();
presence->setFrom(mucJID.withResource("someDifferentNickname"));
auto userPayload = std::make_shared<MUCUserPayload>();
userPayload->addItem(MUCItem(MUCOccupant::Member, JID("foo@bar.com"), MUCOccupant::Moderator));
presence->addPayload(userPayload);
stanzaChannel_->onPresenceReceived(presence);
}
@@ -1114,62 +1102,62 @@ public:
originalMessage->setFrom(messageJID);
originalMessage->setTo(jid2);
originalMessage->setType(Message::Chat);
originalMessage->addPayload(std::make_shared<DeliveryReceipt>("abcdefg123456"));
auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage);
manager_->handleIncomingMessage(messageWrapper);
CPPUNIT_ASSERT_EQUAL(size_t(2), window->receiptChanges_.size());
CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptReceived, window->receiptChanges_[1].second);
}
}
private:
std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) {
std::shared_ptr<Message> message = std::make_shared<Message>();
message->setFrom(from);
message->setID(id);
message->setBody("This will cause the window to open");
message->addPayload(std::make_shared<DeliveryReceiptRequest>());
return message;
}
size_t st(int i) {
return static_cast<size_t>(i);
}
void handleHighlightAction(const HighlightAction& action) {
handledHighlightActions_++;
- if (action.playSound()) {
- soundsPlayed_.insert(action.getSoundFile());
+ if (action.getSoundFilePath()) {
+ soundsPlayed_.insert(action.getSoundFilePath().get_value_or(""));
}
}
private:
JID jid_;
ChatsManager* manager_;
DummyStanzaChannel* stanzaChannel_;
IQRouter* iqRouter_;
EventController* eventController_;
ChatWindowFactory* chatWindowFactory_;
JoinMUCWindowFactory* joinMUCWindowFactory_;
NickResolver* nickResolver_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
std::shared_ptr<DiscoInfo> serverDiscoInfo_;
XMPPRosterImpl* xmppRoster_;
PresenceSender* presenceSender_;
MockRepository* mocks_;
UIEventStream* uiEventStream_;
ChatListWindowFactory* chatListWindowFactory_;
WhiteboardWindowFactory* whiteboardWindowFactory_;
MUCSearchWindowFactory* mucSearchWindowFactory_;
MUCRegistry* mucRegistry_;
DirectedPresenceSender* directedPresenceSender_;
DummyEntityCapsProvider* entityCapsProvider_;
MUCManager* mucManager_;
DummySettingsProvider* settings_;
ProfileSettingsProvider* profileSettings_;
ChatListWindow* chatListWindow_;
FileTransferOverview* ftOverview_;
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index dad021f..eabf4c5 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -1,32 +1,32 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <boost/algorithm/string.hpp>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <hippomocks.h>
#include <Swiften/Avatars/NullAvatarManager.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/DummyStanzaChannel.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/Crypto/CryptoProvider.h>
#include <Swiften/Crypto/PlatformCryptoProvider.h>
#include <Swiften/Disco/DummyEntityCapsProvider.h>
#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Elements/Thread.h>
#include <Swiften/MUC/MUCBookmarkManager.h>
#include <Swiften/MUC/UnitTest/MockMUC.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Presence/DirectedPresenceSender.h>
#include <Swiften/Presence/PresenceOracle.h>
#include <Swiften/Presence/StanzaChannelPresenceSender.h>
#include <Swiften/Queries/DummyIQChannel.h>
#include <Swiften/Roster/XMPPRoster.h>
#include <Swiften/Roster/XMPPRosterImpl.h>
#include <Swiften/VCards/VCardManager.h>
#include <Swiften/VCards/VCardMemoryStorage.h>
@@ -67,72 +67,74 @@ class MUCControllerTest : public CppUnit::TestFixture {
CPPUNIT_TEST(testHandleChangeSubjectRequest);
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
crypto_ = std::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>();
xmppRoster_ = new XMPPRosterImpl();
presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_);
presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
uiEventStream_ = new UIEventStream();
avatarManager_ = new NullAvatarManager();
TimerFactory* timerFactory = nullptr;
window_ = new MockChatWindow();
mucRegistry_ = new MUCRegistry();
entityCapsProvider_ = new DummyEntityCapsProvider();
settings_ = new DummySettingsProvider();
highlightManager_ = new HighlightManager(settings_);
muc_ = std::make_shared<MockMUC>(mucJID_);
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
- chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true);
+ chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat);
vcardStorage_ = new VCardMemoryStorage(crypto_.get());
vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
+ nickResolver_ = new NickResolver(self_, xmppRoster_, vcardManager_, mucRegistry_);
clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
- controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_);
+ controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, nickResolver_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_);
}
void tearDown() {
delete controller_;
delete mucBookmarkManager_;
delete clientBlockListManager_;
+ delete nickResolver_;
delete vcardManager_;
delete vcardStorage_;
delete highlightManager_;
delete settings_;
delete entityCapsProvider_;
delete eventController_;
delete presenceOracle_;
delete xmppRoster_;
delete mocks_;
delete uiEventStream_;
delete stanzaChannel_;
delete presenceSender_;
delete directedPresenceSender_;
delete iqRouter_;
delete iqChannel_;
delete mucRegistry_;
delete avatarManager_;
}
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);
}
@@ -565,53 +567,53 @@ public:
for (auto 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());
}
}
}
void testHandleChangeSubjectRequest() {
std::string testStr("New Subject");
CPPUNIT_ASSERT_EQUAL(std::string(""), muc_->newSubjectSet_);
window_->onChangeSubjectRequest(testStr);
CPPUNIT_ASSERT_EQUAL(testStr, muc_->newSubjectSet_);
}
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_;
+ NickResolver* nickResolver_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
StanzaChannelPresenceSender* presenceSender_;
DirectedPresenceSender* directedPresenceSender_;
MockRepository* mocks_;
UIEventStream* uiEventStream_;
MockChatWindow* window_;
MUCRegistry* mucRegistry_;
DummyEntityCapsProvider* entityCapsProvider_;
DummySettingsProvider* settings_;
HighlightManager* highlightManager_;
std::shared_ptr<ChatMessageParser> chatMessageParser_;
std::shared_ptr<CryptoProvider> crypto_;
VCardManager* vcardManager_;
VCardMemoryStorage* vcardStorage_;
ClientBlockListManager* clientBlockListManager_;
MUCBookmarkManager* mucBookmarkManager_;
XMPPRoster* xmppRoster_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/EventNotifier.cpp b/Swift/Controllers/EventNotifier.cpp
index 6ea2ea5..f22a58c 100644
--- a/Swift/Controllers/EventNotifier.cpp
+++ b/Swift/Controllers/EventNotifier.cpp
@@ -1,80 +1,77 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/EventNotifier.h>
#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/String.h>
#include <Swiften/Base/format.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/JID/JID.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/XMPPEvents/ErrorEvent.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h>
#include <SwifTools/Notifier/Notifier.h>
namespace Swift {
EventNotifier::EventNotifier(EventController* eventController, Notifier* notifier, AvatarManager* avatarManager, NickResolver* nickResolver) : eventController(eventController), notifier(notifier), avatarManager(avatarManager), nickResolver(nickResolver) {
eventController->onEventQueueEventAdded.connect(boost::bind(&EventNotifier::handleEventAdded, this, _1));
}
EventNotifier::~EventNotifier() {
notifier->purgeCallbacks();
eventController->onEventQueueEventAdded.disconnect(boost::bind(&EventNotifier::handleEventAdded, this, _1));
}
void EventNotifier::handleEventAdded(std::shared_ptr<StanzaEvent> event) {
if (event->getConcluded()) {
return;
}
if (std::shared_ptr<MessageEvent> messageEvent = std::dynamic_pointer_cast<MessageEvent>(event)) {
JID jid = messageEvent->getStanza()->getFrom();
- std::string title = nickResolver->jidToNick(jid);
if (!messageEvent->getStanza()->isError() && !messageEvent->getStanza()->getBody().get_value_or("").empty()) {
JID activationJID = jid;
if (messageEvent->getStanza()->getType() == Message::Groupchat) {
activationJID = jid.toBare();
}
- std::string messageText = messageEvent->getStanza()->getBody().get_value_or("");
- if (boost::starts_with(messageText, "/me ")) {
- messageText = "*" + String::getSplittedAtFirst(messageText, ' ').second + "*";
+ for (const auto& notification : messageEvent->getNotifications()) {
+ notifier->showMessage(Notifier::IncomingMessage, notification.title, notification.message, avatarManager->getAvatarPath(jid), boost::bind(&EventNotifier::handleNotificationActivated, this, activationJID));
}
- notifier->showMessage(Notifier::IncomingMessage, title, messageText, avatarManager->getAvatarPath(jid), boost::bind(&EventNotifier::handleNotificationActivated, this, activationJID));
}
}
else if(std::shared_ptr<SubscriptionRequestEvent> subscriptionEvent = std::dynamic_pointer_cast<SubscriptionRequestEvent>(event)) {
JID jid = subscriptionEvent->getJID();
std::string title = jid;
std::string message = str(format(QT_TRANSLATE_NOOP("", "%1% wants to add you to his/her contact list")) % nickResolver->jidToNick(jid));
notifier->showMessage(Notifier::SystemMessage, title, message, boost::filesystem::path(), boost::function<void()>());
}
else if(std::shared_ptr<ErrorEvent> errorEvent = std::dynamic_pointer_cast<ErrorEvent>(event)) {
notifier->showMessage(Notifier::SystemMessage, QT_TRANSLATE_NOOP("", "Error"), errorEvent->getText(), boost::filesystem::path(), boost::function<void()>());
}
else if (std::shared_ptr<MUCInviteEvent> mucInviteEvent = std::dynamic_pointer_cast<MUCInviteEvent>(event)) {
std::string title = mucInviteEvent->getInviter();
std::string message = str(format(QT_TRANSLATE_NOOP("", "%1% has invited you to enter the %2% room")) % nickResolver->jidToNick(mucInviteEvent->getInviter()) % mucInviteEvent->getRoomJID());
// FIXME: not show avatar or greyed out avatar for mediated invites
notifier->showMessage(Notifier::SystemMessage, title, message, avatarManager->getAvatarPath(mucInviteEvent->getInviter()), boost::bind(&EventNotifier::handleNotificationActivated, this, mucInviteEvent->getInviter()));
}
}
void EventNotifier::handleNotificationActivated(JID jid) {
onNotificationActivated(jid);
}
}
diff --git a/Swift/Controllers/HighlightAction.cpp b/Swift/Controllers/HighlightAction.cpp
deleted file mode 100644
index 3ea2c86..0000000
--- a/Swift/Controllers/HighlightAction.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2015 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#include <Swift/Controllers/HighlightAction.h>
-
-namespace Swift {
-
-void HighlightAction::setHighlightWholeMessage(bool highlightText)
-{
- highlightWholeMessage_ = highlightText;
- if (!highlightWholeMessage_) {
- textColor_.clear();
- textBackground_.clear();
- }
-}
-
-void HighlightAction::setPlaySound(bool playSound)
-{
- playSound_ = playSound;
- if (!playSound_) {
- soundFile_.clear();
- }
-}
-
-bool operator ==(HighlightAction const& a, HighlightAction const& b) {
- if (a.highlightWholeMessage() != b.highlightWholeMessage()) {
- return false;
- }
-
- if (a.getTextColor() != b.getTextColor()) {
- return false;
- }
-
- if (a.getTextBackground() != b.getTextBackground()) {
- return false;
- }
-
- if (a.playSound() != b.playSound()) {
- return false;
- }
-
- if (a.getSoundFile() != b.getSoundFile()) {
- return false;
- }
-
- return true;
-}
-
-bool operator !=(HighlightAction const& a, HighlightAction const& b) {
- return !(a == b);
-}
-
-}
diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h
deleted file mode 100644
index b9d4539..0000000
--- a/Swift/Controllers/HighlightAction.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2014-2015 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#pragma once
-
-#include <string>
-
-#include <boost/archive/text_iarchive.hpp>
-#include <boost/archive/text_oarchive.hpp>
-
-namespace Swift {
-
- class HighlightRule;
-
- class HighlightAction {
- public:
- HighlightAction() : highlightWholeMessage_(false), playSound_(false) {}
-
- /**
- * Gets the flag that indicates the entire message should be highlighted.
- */
- bool highlightWholeMessage() const { return highlightWholeMessage_; }
- void setHighlightWholeMessage(bool highlightText);
-
- /**
- * Gets the foreground highlight color.
- */
- const std::string& getTextColor() const { return textColor_; }
- void setTextColor(const std::string& textColor) { textColor_ = textColor; }
-
- /**
- * Gets the background highlight 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 !highlightWholeMessage_ && !playSound_; }
-
- private:
- friend class boost::serialization::access;
- template<class Archive> void serialize(Archive & ar, const unsigned int version);
-
- bool highlightWholeMessage_;
- std::string textColor_;
- std::string textBackground_;
-
- bool playSound_;
- std::string soundFile_;
- };
-
- bool operator ==(HighlightAction const& a, HighlightAction const& b);
- bool operator !=(HighlightAction const& a, HighlightAction const& b);
-
- template<class Archive>
- void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/)
- {
- ar & highlightWholeMessage_;
- ar & textColor_;
- ar & textBackground_;
- ar & playSound_;
- ar & soundFile_;
- }
-
-}
diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp
deleted file mode 100644
index 9176301..0000000
--- a/Swift/Controllers/HighlightManager.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2014-2016 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#include <Swift/Controllers/HighlightManager.h>
-
-#include <cassert>
-#include <sstream>
-
-#include <boost/algorithm/string.hpp>
-#include <boost/archive/text_iarchive.hpp>
-#include <boost/archive/text_oarchive.hpp>
-#include <boost/bind.hpp>
-#include <boost/numeric/conversion/cast.hpp>
-#include <boost/regex.hpp>
-#include <boost/serialization/vector.hpp>
-
-#include <Swift/Controllers/Highlighter.h>
-#include <Swift/Controllers/SettingConstants.h>
-#include <Swift/Controllers/Settings/SettingsProvider.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_ = std::make_shared<HighlightRulesList>();
- loadSettings();
- handleSettingChangedConnection_ = settings_->onSettingChanged.connect(boost::bind(&HighlightManager::handleSettingChanged, this, _1));
-}
-
-void HighlightManager::handleSettingChanged(const std::string& settingPath) {
- if (!storingSettings_ && SettingConstants::HIGHLIGHT_RULES.getKey() == settingPath) {
- loadSettings();
- }
-}
-
-std::string HighlightManager::rulesToString() const {
- 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 chatNotificationRule;
- chatNotificationRule.setMatchChat(true);
- chatNotificationRule.getAction().setPlaySound(true);
- chatNotificationRule.setMatchWholeWords(true);
- rules.push_back(chatNotificationRule);
-
- HighlightRule selfMentionMUCRule;
- selfMentionMUCRule.setMatchMUC(true);
- selfMentionMUCRule.getAction().setPlaySound(true);
- selfMentionMUCRule.setNickIsKeyword(true);
- selfMentionMUCRule.setMatchCase(true);
- selfMentionMUCRule.setMatchWholeWords(true);
- rules.push_back(selfMentionMUCRule);
-
- return rules;
-}
-
-HighlightRule HighlightManager::getRule(int index) const {
- 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_->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_->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_->getSize());
- rules_->list_.erase(rules_->list_.begin() + index);
-}
-
-void HighlightManager::swapRules(const size_t first, const size_t second) {
- assert(first < rules_->getSize());
- assert(second < rules_->getSize());
- const HighlightRule swap = rules_->getRule(first);
- rules_->setRule(first, rules_->getRule(second));
- rules_->setRule(second, swap);
-}
-
-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);
-}
-
-bool HighlightManager::isDefaultRulesList() const {
- return getDefaultRules() == rules_->list_;
-}
-
-void HighlightManager::resetToDefaultRulesList() {
- rules_->list_ = getDefaultRules();
-}
-
-}
diff --git a/Swift/Controllers/HighlightManager.h b/Swift/Controllers/HighlightManager.h
deleted file mode 100644
index a35e253..0000000
--- a/Swift/Controllers/HighlightManager.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2014-2016 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <boost/signals2.hpp>
-
-#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());
- }
- void setRule(const size_t index, const HighlightRule& rule) {
- list_[index] = rule;
- }
- private:
- std::vector<HighlightRule> list_;
- };
-
- HighlightManager(SettingsProvider* settings);
-
- Highlighter* createHighlighter();
-
- std::shared_ptr<const HighlightManager::HighlightRulesList> getRules() const { return rules_; }
-
- bool isDefaultRulesList() const;
- void resetToDefaultRulesList();
-
- HighlightRule getRule(int index) const;
- void setRule(int index, const HighlightRule& rule);
- void insertRule(int index, const HighlightRule& rule);
- void removeRule(int index);
- void swapRules(const size_t first, const size_t second);
- void storeSettings();
- void loadSettings();
-
- boost::signals2::signal<void (const HighlightAction&)> onHighlight;
-
- private:
- void handleSettingChanged(const std::string& settingPath);
-
- std::string rulesToString() const;
- static std::vector<HighlightRule> getDefaultRules();
-
- private:
- SettingsProvider* settings_;
- bool storingSettings_;
-
- std::shared_ptr<HighlightManager::HighlightRulesList> rules_;
- boost::signals2::scoped_connection handleSettingChangedConnection_;
- };
-
- typedef std::shared_ptr<const HighlightManager::HighlightRulesList> HighlightRulesListPtr;
-
-}
diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp
deleted file mode 100644
index a8cb7e4..0000000
--- a/Swift/Controllers/HighlightRule.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2014-2016 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#include <Swift/Controllers/HighlightRule.h>
-
-#include <algorithm>
-
-#include <boost/algorithm/string.hpp>
-#include <boost/lambda/lambda.hpp>
-
-#include <Swiften/Base/Regex.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();
- for (const auto& k : keywords_) {
- keywordRegex_.push_back(regexFromString(k));
- }
- senderRegex_.clear();
- for (const auto& 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";
-}
-
-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();
-
- if (!matchesKeyword) {
- // check if the nickname matches
- if (nickIsKeyword_ && !nick.empty() && boost::regex_search(body, regexFromString(nick))) {
- matchesKeyword = true;
- }
-
- // check if a keyword matches
- if (!matchesKeyword && !keywords_.empty()) {
- for (const auto& keyword : keywordRegex_) {
- if (boost::regex_search(body, keyword)) {
- matchesKeyword = true;
- break;
- }
- }
- }
- }
-
- for (const auto& 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();
-}
-
-std::vector<boost::regex> HighlightRule::getKeywordRegex(const std::string& nick) const {
- if (nickIsKeyword_) {
- std::vector<boost::regex> regex;
- if (!nick.empty()) {
- regex.push_back(regexFromString(nick));
- }
- return regex;
- } else {
- return keywordRegex_;
- }
-}
-
-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();
-}
-
-bool operator ==(HighlightRule const& a, HighlightRule const& b) {
- if (a.getSenders() != b.getSenders()) {
- return false;
- }
-
- if (a.getKeywords() != b.getKeywords()) {
- return false;
- }
-
- if (a.getNickIsKeyword() != b.getNickIsKeyword()) {
- return false;
- }
-
- if (a.getMatchChat() != b.getMatchChat()) {
- return false;
- }
-
- if (a.getMatchMUC() != b.getMatchMUC()) {
- return false;
- }
-
- if (a.getMatchCase() != b.getMatchCase()) {
- return false;
- }
-
- if (a.getMatchWholeWords() != b.getMatchWholeWords()) {
- return false;
- }
-
- if (a.getAction() != b.getAction()) {
- return false;
- }
-
- return true;
-}
-
-}
diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h
deleted file mode 100644
index bffdc41..0000000
--- a/Swift/Controllers/HighlightRule.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2014-2016 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <boost/archive/text_iarchive.hpp>
-#include <boost/archive/text_oarchive.hpp>
-#include <boost/regex.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_; }
-
- 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>&);
- std::vector<boost::regex> getKeywordRegex(const std::string& nick) const;
-
- 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_;
- };
-
- bool operator ==(HighlightRule const& a, HighlightRule const& b);
-
- 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
deleted file mode 100644
index cea077e..0000000
--- a/Swift/Controllers/Highlighter.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2014-2016 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#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::findFirstFullMessageMatchAction(const std::string& body, const std::string& sender) const
-{
- HighlightAction match;
- 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_) && rule.getAction().highlightWholeMessage()) {
- match = rule.getAction();
- break;
- }
- }
-
- return match;
-}
-
-void Highlighter::handleHighlightAction(const HighlightAction& action)
-{
- manager_->onHighlight(action);
-}
-
-}
diff --git a/Swift/Controllers/Highlighter.h b/Swift/Controllers/Highlighter.h
deleted file mode 100644
index 9ad3339..0000000
--- a/Swift/Controllers/Highlighter.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2016 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <Swift/Controllers/HighlightRule.h>
-
-namespace Swift {
-
- class HighlightManager;
-
- class Highlighter {
- public:
- Highlighter(HighlightManager* manager);
-
- enum Mode { ChatMode, MUCMode };
- void setMode(Mode mode);
-
- void setNick(const std::string& nick) { nick_ = nick; }
- std::string getNick() const { return nick_; }
-
- HighlightAction findFirstFullMessageMatchAction(const std::string& body, const std::string& sender) const;
-
- void handleHighlightAction(const HighlightAction& action);
-
- private:
- HighlightManager* manager_;
- Mode mode_;
- HighlightRule::MessageType messageType_;
- std::string nick_;
- };
-
-}
diff --git a/Swift/Controllers/Highlighting/HighlightAction.cpp b/Swift/Controllers/Highlighting/HighlightAction.cpp
new file mode 100644
index 0000000..e9f14df
--- /dev/null
+++ b/Swift/Controllers/Highlighting/HighlightAction.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2015-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/Controllers/Highlighting/HighlightAction.h>
+
+namespace Swift {
+
+
+
+bool operator ==(const HighlightAction& a, const HighlightAction& b) {
+ if (a.getFrontColor() != b.getFrontColor()) {
+ return false;
+ }
+ if (a.getBackColor() != b.getBackColor()) {
+ return false;
+ }
+ if (a.getSoundFilePath() != b.getSoundFilePath()) {
+ return false;
+ }
+ if (a.isSystemNotificationEnabled() != b.isSystemNotificationEnabled()) {
+ return false;
+ }
+ return true;
+}
+
+bool operator !=(const HighlightAction& a, const HighlightAction& b) {
+ return !(a == b);
+}
+
+void HighlightAction::setFrontColor(const boost::optional<std::string>& frontColor) {
+ frontColor_ = frontColor;
+}
+
+boost::optional<std::string> HighlightAction::getFrontColor() const {
+ return frontColor_;
+}
+
+void HighlightAction::setBackColor(const boost::optional<std::string>& backColor) {
+ backColor_ = backColor;
+}
+
+boost::optional<std::string> HighlightAction::getBackColor() const {
+ return backColor_;
+}
+
+void HighlightAction::setSoundFilePath(const boost::optional<std::string>& soundFilePath) {
+ soundFilePath_ = soundFilePath;
+}
+
+boost::optional<std::string> HighlightAction::getSoundFilePath() const {
+ return soundFilePath_;
+}
+
+void HighlightAction::setSystemNotificationEnabled(bool systemNotificationEnabled) {
+ systemNotificaitonEnabled_ = systemNotificationEnabled;
+}
+
+bool HighlightAction::isSystemNotificationEnabled() const {
+ return systemNotificaitonEnabled_;
+}
+
+bool HighlightAction::isEmpty() const {
+ return !frontColor_.is_initialized() && !backColor_.is_initialized() && !soundFilePath_.is_initialized() && !systemNotificaitonEnabled_;
+}
+
+}
diff --git a/Swift/Controllers/Highlighting/HighlightAction.h b/Swift/Controllers/Highlighting/HighlightAction.h
new file mode 100644
index 0000000..da92901
--- /dev/null
+++ b/Swift/Controllers/Highlighting/HighlightAction.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2014-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <boost/archive/text_iarchive.hpp>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/optional.hpp>
+#include <boost/serialization/optional.hpp>
+
+namespace Swift {
+ class HighlightAction {
+ public:
+ void setFrontColor(const boost::optional<std::string>& frontColor);
+ boost::optional<std::string> getFrontColor() const;
+
+ void setBackColor(const boost::optional<std::string>& backColor);
+ boost::optional<std::string> getBackColor() const;
+
+ void setSoundFilePath(const boost::optional<std::string>& soundFilePath);
+ boost::optional<std::string> getSoundFilePath() const;
+
+ void setSystemNotificationEnabled(bool systemNotificationEnabled);
+ bool isSystemNotificationEnabled() const;
+
+ // @return returns true if the HighlightAction would result in no
+ // noticable action to the user.
+ bool isEmpty() const;
+
+ private:
+ friend class boost::serialization::access;
+ template<class Archive> void serialize(Archive & ar, const unsigned int version);
+
+ private:
+ // Highlight color.
+ boost::optional<std::string> frontColor_;
+ boost::optional<std::string> backColor_;
+
+ // Audio notification.
+ boost::optional<std::string> soundFilePath_;
+
+ // macOS Notification Center or similar.
+ bool systemNotificaitonEnabled_ = false;
+ };
+
+ bool operator ==(const HighlightAction& a, const HighlightAction& b);
+ bool operator !=(const HighlightAction& a, const HighlightAction& b);
+
+ template<class Archive>
+ void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/) {
+ ar & frontColor_;
+ ar & backColor_;
+ ar & soundFilePath_;
+ ar & systemNotificaitonEnabled_;
+ }
+
+}
diff --git a/Swift/Controllers/Highlighting/HighlightConfiguration.cpp b/Swift/Controllers/Highlighting/HighlightConfiguration.cpp
new file mode 100644
index 0000000..e82adb8
--- /dev/null
+++ b/Swift/Controllers/Highlighting/HighlightConfiguration.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2016-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
+
+namespace Swift {
+
+bool operator ==(const HighlightConfiguration& a, const HighlightConfiguration& b) {
+ if (a.keywordHighlights != b.keywordHighlights) {
+ return false;
+ }
+ if (a.contactHighlights != b.contactHighlights) {
+ return false;
+ }
+ if (a.ownMentionAction != b.ownMentionAction) {
+ return false;
+ }
+ if (a.playSoundOnIncomingDirectMessages != b.playSoundOnIncomingDirectMessages) {
+ return false;
+ }
+ if (a.showNotificationOnIncomingDirectMessages != b.showNotificationOnIncomingDirectMessages) {
+ return false;
+ }
+ if (a.playSoundOnIncomingGroupchatMessages != b.playSoundOnIncomingGroupchatMessages) {
+ return false;
+ }
+ if (a.showNotificationOnIncomingGroupchatMessages != b.showNotificationOnIncomingGroupchatMessages) {
+ return false;
+ }
+ return true;
+}
+
+}
diff --git a/Swift/Controllers/Highlighting/HighlightConfiguration.h b/Swift/Controllers/Highlighting/HighlightConfiguration.h
new file mode 100644
index 0000000..d262dba
--- /dev/null
+++ b/Swift/Controllers/Highlighting/HighlightConfiguration.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <boost/archive/text_iarchive.hpp>
+#include <boost/archive/text_oarchive.hpp>
+
+#include <Swift/Controllers/Highlighting/HighlightAction.h>
+
+namespace Swift {
+
+class HighlightConfiguration {
+public:
+ class ContactHighlight {
+ public:
+ friend class boost::serialization::access;
+ template<class Archive> void serialize(Archive & ar, const unsigned int version);
+
+ public:
+ std::string name;
+ HighlightAction action;
+ };
+
+ class KeywordHightlight {
+ public:
+ friend class boost::serialization::access;
+ template<class Archive> void serialize(Archive & ar, const unsigned int version);
+
+ public:
+ std::string keyword;
+ bool matchCaseSensitive = false;
+ HighlightAction action;
+ };
+
+ friend class boost::serialization::access;
+ template<class Archive> void serialize(Archive & ar, const unsigned int version);
+
+public:
+ std::vector<KeywordHightlight> keywordHighlights;
+ std::vector<ContactHighlight> contactHighlights;
+ HighlightAction ownMentionAction;
+ bool playSoundOnIncomingDirectMessages = false;
+ bool showNotificationOnIncomingDirectMessages = false;
+ bool playSoundOnIncomingGroupchatMessages = false;
+ bool showNotificationOnIncomingGroupchatMessages = false;
+};
+
+bool operator ==(HighlightConfiguration const& a, HighlightConfiguration const& b);
+
+template<class Archive>
+void HighlightConfiguration::ContactHighlight::serialize(Archive& ar, const unsigned int /*version*/) {
+ ar & name;
+ ar & action;
+}
+
+template<class Archive>
+void HighlightConfiguration::KeywordHightlight::serialize(Archive& ar, const unsigned int /*version*/) {
+ ar & keyword;
+ ar & matchCaseSensitive;
+ ar & action;
+}
+
+template<class Archive>
+void HighlightConfiguration::serialize(Archive& ar, const unsigned int /*version*/) {
+ ar & keywordHighlights;
+ ar & contactHighlights;
+ ar & ownMentionAction;
+ ar & playSoundOnIncomingDirectMessages;
+ ar & showNotificationOnIncomingDirectMessages;
+ ar & playSoundOnIncomingGroupchatMessages;
+ ar & showNotificationOnIncomingGroupchatMessages;
+}
+
+}
diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/Highlighting/HighlightEditorController.cpp
index 1f5f928..50da3dc 100644
--- a/Swift/Controllers/HighlightEditorController.cpp
+++ b/Swift/Controllers/Highlighting/HighlightEditorController.cpp
@@ -1,43 +1,43 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
- * Copyright (c) 2014-2016 Isode Limited.
+ * Copyright (c) 2014-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
-#include <Swift/Controllers/HighlightEditorController.h>
+#include <Swift/Controllers/Highlighting/HighlightEditorController.h>
#include <boost/bind.hpp>
#include <Swift/Controllers/ContactSuggester.h>
#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h>
#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h>
namespace Swift {
HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager)
: highlightEditorWindowFactory_(highlightEditorWindowFactory), highlightEditorWindow_(nullptr), highlightManager_(highlightManager), contactSuggester_(nullptr)
{
uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1));
}
HighlightEditorController::~HighlightEditorController()
{
delete highlightEditorWindow_;
highlightEditorWindow_ = nullptr;
}
void HighlightEditorController::handleUIEvent(std::shared_ptr<UIEvent> rawEvent)
{
std::shared_ptr<RequestHighlightEditorUIEvent> event = std::dynamic_pointer_cast<RequestHighlightEditorUIEvent>(rawEvent);
if (event) {
if (!highlightEditorWindow_) {
highlightEditorWindow_ = highlightEditorWindowFactory_->createHighlightEditorWindow();
highlightEditorWindow_->setHighlightManager(highlightManager_);
diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/Highlighting/HighlightEditorController.h
index a699751..d7608a5 100644
--- a/Swift/Controllers/HighlightEditorController.h
+++ b/Swift/Controllers/Highlighting/HighlightEditorController.h
@@ -1,38 +1,38 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
- * Copyright (c) 2014-2016 Isode Limited.
+ * Copyright (c) 2014-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
#include <memory>
#include <string>
#include <Swift/Controllers/UIEvents/UIEvent.h>
namespace Swift {
class UIEventStream;
class HighlightEditorWindowFactory;
class HighlightEditorWindow;
class HighlightManager;
class ContactSuggester;
class HighlightEditorController {
public:
HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager);
~HighlightEditorController();
HighlightManager* getHighlightManager() const { return highlightManager_; }
void setContactSuggester(ContactSuggester *suggester) { contactSuggester_ = suggester; }
private:
diff --git a/Swift/Controllers/Highlighting/HighlightManager.cpp b/Swift/Controllers/Highlighting/HighlightManager.cpp
new file mode 100644
index 0000000..89261af
--- /dev/null
+++ b/Swift/Controllers/Highlighting/HighlightManager.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2014-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
+
+#include <cassert>
+#include <sstream>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/archive/text_iarchive.hpp>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/bind.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+#include <boost/regex.hpp>
+#include <boost/serialization/vector.hpp>
+
+#include <Swiften/Base/Log.h>
+
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
+#include <Swift/Controllers/Highlighting/Highlighter.h>
+#include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+
+namespace Swift {
+
+HighlightManager::HighlightManager(SettingsProvider* settings)
+ : settings_(settings)
+ , storingSettings_(false) {
+ highlightConfiguration_ = std::make_shared<HighlightConfiguration>();
+ loadSettings();
+ handleSettingChangedConnection_ = settings_->onSettingChanged.connect(boost::bind(&HighlightManager::handleSettingChanged, this, _1));
+}
+
+void HighlightManager::handleSettingChanged(const std::string& settingPath) {
+ if (!storingSettings_ && SettingConstants::HIGHLIGHT_RULES.getKey() == settingPath) {
+ loadSettings();
+ }
+}
+
+HighlightConfiguration HighlightManager::getDefaultConfig() {
+ HighlightConfiguration defaultConfiguration;
+ defaultConfiguration.playSoundOnIncomingDirectMessages = true;
+ defaultConfiguration.showNotificationOnIncomingDirectMessages = true;
+ defaultConfiguration.ownMentionAction.setFrontColor(std::string("black"));
+ defaultConfiguration.ownMentionAction.setBackColor(std::string("yellow"));
+ defaultConfiguration.ownMentionAction.setSoundFilePath(std::string("/sounds/message-received.wav"));
+ defaultConfiguration.ownMentionAction.setSystemNotificationEnabled(true);
+ return defaultConfiguration;
+}
+
+void HighlightManager::storeSettings() {
+ storingSettings_ = true; // don't reload settings while saving
+ settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES_V2, highlightConfigurationToString(*highlightConfiguration_));
+ storingSettings_ = false;
+}
+
+void HighlightManager::loadSettings() {
+ std::string configString = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES_V2);
+ *highlightConfiguration_ = highlightConfigurationFromString(configString);
+}
+
+Highlighter* HighlightManager::createHighlighter(NickResolver* nickResolver) {
+ return new Highlighter(this, nickResolver);
+}
+
+void HighlightManager::resetToDefaultConfiguration() {
+ *highlightConfiguration_ = getDefaultConfig();
+}
+
+HighlightConfiguration HighlightManager::highlightConfigurationFromString(const std::string& dataString) {
+ std::stringstream stream;
+ stream << dataString;
+
+ HighlightConfiguration configuration;
+ try {
+ boost::archive::text_iarchive archive(stream);
+ archive >> configuration;
+ }
+ catch (boost::archive::archive_exception&) {
+ configuration = getDefaultConfig();
+ SWIFT_LOG(warning) << "Failed to load highlight configuration. Will use default configuration instead." << std::endl;
+ }
+ return configuration;
+}
+
+std::string HighlightManager::highlightConfigurationToString(const HighlightConfiguration& configuration) {
+ std::stringstream stream;
+ boost::archive::text_oarchive archive(stream);
+ archive & configuration;
+ return stream.str();
+}
+
+}
diff --git a/Swift/Controllers/Highlighting/HighlightManager.h b/Swift/Controllers/Highlighting/HighlightManager.h
new file mode 100644
index 0000000..13f59fa
--- /dev/null
+++ b/Swift/Controllers/Highlighting/HighlightManager.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2014-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <boost/signals2.hpp>
+
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
+
+namespace Swift {
+
+ class NickResolver;
+ class SettingsProvider;
+ class Highlighter;
+
+ class HighlightManager {
+ public:
+ HighlightManager(SettingsProvider* settings);
+
+ Highlighter* createHighlighter(NickResolver* nickResolver);
+
+ std::shared_ptr<HighlightConfiguration> getConfiguration() const {
+ return highlightConfiguration_;
+ }
+
+ void setConfiguration(const HighlightConfiguration& config) {
+ *highlightConfiguration_ = config;
+ storeSettings();
+ }
+
+ void resetToDefaultConfiguration();
+
+ void storeSettings();
+ void loadSettings();
+
+ boost::signals2::signal<void (const HighlightAction&)> onHighlight;
+
+ private:
+ void handleSettingChanged(const std::string& settingPath);
+
+ static HighlightConfiguration getDefaultConfig();
+
+ static HighlightConfiguration highlightConfigurationFromString(const std::string& dataString);
+ static std::string highlightConfigurationToString(const HighlightConfiguration& configuration);
+
+ private:
+ SettingsProvider* settings_;
+ bool storingSettings_;
+ std::shared_ptr<HighlightConfiguration> highlightConfiguration_;
+ boost::signals2::scoped_connection handleSettingChangedConnection_;
+ };
+}
diff --git a/Swift/Controllers/Highlighting/Highlighter.cpp b/Swift/Controllers/Highlighting/Highlighter.cpp
new file mode 100644
index 0000000..b05de2d
--- /dev/null
+++ b/Swift/Controllers/Highlighting/Highlighter.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2014-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/Controllers/Highlighting/Highlighter.h>
+
+#include <set>
+#include <string>
+
+#include <Swiften/Base/String.h>
+#include <Swiften/Base/format.h>
+#include <Swiften/Client/NickResolver.h>
+
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
+#include <Swift/Controllers/Intl.h>
+#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
+
+namespace Swift {
+
+Highlighter::Highlighter(HighlightManager* manager, NickResolver* nickResolver)
+ : manager_(manager), nickResolver_(nickResolver) {
+}
+
+void Highlighter::handleSystemNotifications(const ChatWindow::ChatMessage& message, std::shared_ptr<MessageEvent> event) {
+ if (std::shared_ptr<MessageEvent> messageEvent = std::dynamic_pointer_cast<MessageEvent>(event)) {
+ JID jid = messageEvent->getStanza()->getFrom();
+ std::string nickname = nickResolver_->jidToNick(jid);
+
+ std::string messageText = messageEvent->getStanza()->getBody().get_value_or("");
+ if (boost::starts_with(messageText, "/me ")) {
+ messageText = "*" + String::getSplittedAtFirst(messageText, ' ').second + "*";
+ }
+
+ if (message.getHighlightActionDirectMessage().isSystemNotificationEnabled()) {
+ // title: Romeo says
+ // message: message
+ std::string title = str(format(QT_TRANSLATE_NOOP("", "%1% says")) % nickname);
+ event->addNotification(title, messageText);
+ }
+ if (message.getHighlightActionGroupMessage().isSystemNotificationEnabled()) {
+ // title: Romeo in $roomJID says
+ // message: message
+ std::string roomName = jid.getNode();
+ std::string title = str(format(QT_TRANSLATE_NOOP("", "%1% in %2% says")) % nickname % roomName);
+ event->addNotification(title, messageText);
+ }
+ if (message.getHighlightActionOwnMention().isSystemNotificationEnabled()) {
+ // title: Romeo mentioned you in $roomJID
+ // message: message
+ std::string roomName = jid.getNode();
+ std::string title = str(format(QT_TRANSLATE_NOOP("", "%1% mentioned you in %2%")) % nickname % roomName);
+ event->addNotification(title, messageText);
+ }
+ if (message.getHighlightActionSender().isSystemNotificationEnabled()) {
+ // title: Romeo says
+ // message: message
+ auto title = str(format(QT_TRANSLATE_NOOP("", "%1% says")) % nickname);
+ event->addNotification(title, messageText);
+ }
+ for (auto&& part : message.getParts()) {
+ auto highlightPart = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part);
+ if (highlightPart && highlightPart->action.isSystemNotificationEnabled()) {
+ // title: Romeo mentioned '$keyword'
+ // message: message
+ auto title = str(format(QT_TRANSLATE_NOOP("", "%1% mentioned '%2%'")) % nickname % highlightPart->text);
+ event->addNotification(title, messageText);
+ }
+ }
+ }
+}
+
+void Highlighter::handleSoundNotifications(const ChatWindow::ChatMessage& chatMessage) {
+ std::set<std::string> playedSoundPaths;
+ std::vector<HighlightAction> actionsToPlay;
+
+ // collect unique sounds to play
+ auto checkSoundActionAndQueueUnique = [&](const HighlightAction& action) {
+ if (action.getSoundFilePath()) {
+ auto soundFilePath = action.getSoundFilePath().get_value_or("");
+ if (playedSoundPaths.find(soundFilePath) == playedSoundPaths.end()) {
+ playedSoundPaths.insert(soundFilePath);
+ actionsToPlay.push_back(action);
+ }
+ }
+ };
+
+ for (auto&& part : chatMessage.getParts()) {
+ auto highlightMessage = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part);
+ if (highlightMessage) {
+ checkSoundActionAndQueueUnique(highlightMessage->action);
+ }
+ }
+
+ checkSoundActionAndQueueUnique(chatMessage.getHighlightActionSender());
+ checkSoundActionAndQueueUnique(chatMessage.getHighlightActionOwnMention());
+ checkSoundActionAndQueueUnique(chatMessage.getHighlightActionDirectMessage());
+ checkSoundActionAndQueueUnique(chatMessage.getHighlightActionGroupMessage());
+
+ // play sounds
+ for (const auto& action : actionsToPlay) {
+ manager_->onHighlight(action);
+ }
+}
+
+}
diff --git a/Swift/Controllers/Highlighting/Highlighter.h b/Swift/Controllers/Highlighting/Highlighter.h
new file mode 100644
index 0000000..51308a1
--- /dev/null
+++ b/Swift/Controllers/Highlighting/Highlighter.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2016-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
+
+namespace Swift {
+
+ class HighlightManager;
+ class NickResolver;
+
+ class Highlighter {
+ public:
+ Highlighter(HighlightManager* manager, NickResolver* nickResolver);
+
+ void handleSystemNotifications(const ChatWindow::ChatMessage& message, std::shared_ptr<MessageEvent> event);
+ void handleSoundNotifications(const ChatWindow::ChatMessage& chatMessage);
+
+ private:
+ HighlightManager* manager_;
+ NickResolver* nickResolver_;
+ };
+
+}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 0d9f1b8..e64b23d 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -1,91 +1,91 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/MainController.h>
#include <cstdlib>
#include <memory>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <Swiften/Base/Algorithm.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/String.h>
#include <Swiften/Base/format.h>
#include <Swiften/Client/Client.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/ClientXMLTracer.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Client/Storages.h>
#include <Swiften/Crypto/CryptoProvider.h>
#include <Swiften/Disco/CapsInfoGenerator.h>
#include <Swiften/Disco/ClientDiscoManager.h>
#include <Swiften/Disco/GetDiscoInfoRequest.h>
#include <Swiften/Elements/ChatState.h>
#include <Swiften/Elements/DiscoInfo.h>
#include <Swiften/Elements/Presence.h>
#include <Swiften/Elements/VCardUpdate.h>
#include <Swiften/FileTransfer/FileTransferManager.h>
#include <Swiften/Network/NetworkFactories.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Presence/PresenceSender.h>
#include <Swiften/Queries/Requests/EnableCarbonsRequest.h>
#include <Swiften/StringCodecs/Base64.h>
#include <Swiften/StringCodecs/Hexify.h>
#include <Swiften/VCards/GetVCardRequest.h>
#include <Swiften/VCards/VCardManager.h>
#ifdef SWIFTEN_PLATFORM_WIN32
#include <Swiften/SASL/WindowsAuthentication.h>
#endif
#include <Swift/Controllers/AdHocManager.h>
#include <Swift/Controllers/BlockListController.h>
#include <Swift/Controllers/BuildVersion.h>
#include <Swift/Controllers/Chat/ChatsManager.h>
#include <Swift/Controllers/Chat/MUCController.h>
#include <Swift/Controllers/Chat/UserSearchController.h>
#include <Swift/Controllers/ContactEditController.h>
#include <Swift/Controllers/ContactSuggester.h>
#include <Swift/Controllers/ContactsFromXMPPRoster.h>
#include <Swift/Controllers/EventNotifier.h>
#include <Swift/Controllers/EventWindowController.h>
#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
#include <Swift/Controllers/FileTransferListController.h>
-#include <Swift/Controllers/HighlightEditorController.h>
-#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/Highlighting/HighlightEditorController.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
#include <Swift/Controllers/HistoryController.h>
#include <Swift/Controllers/HistoryViewController.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/PresenceNotifier.h>
#include <Swift/Controllers/ProfileController.h>
#include <Swift/Controllers/Roster/RosterController.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/ShowProfileController.h>
#include <Swift/Controllers/SoundEventController.h>
#include <Swift/Controllers/SoundPlayer.h>
#include <Swift/Controllers/StatusTracker.h>
#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
#include <Swift/Controllers/Storages/CertificateStorageTrustChecker.h>
#include <Swift/Controllers/Storages/StoragesFactory.h>
#include <Swift/Controllers/SystemTray.h>
#include <Swift/Controllers/SystemTrayController.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/LoginWindow.h>
#include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/MainWindow.h>
#include <Swift/Controllers/UIInterfaces/UIFactory.h>
#include <Swift/Controllers/WhiteboardManager.h>
#include <Swift/Controllers/XMLConsoleController.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/XMPPURIController.h>
#include <SwifTools/Dock/Dock.h>
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 105b44b..0c3127c 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -1,111 +1,109 @@
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", [
+ "AdHocController.cpp",
+ "AdHocManager.cpp",
+ "BlockListController.cpp",
"Chat/ChatController.cpp",
"Chat/ChatControllerBase.cpp",
+ "Chat/ChatMessageParser.cpp",
"Chat/ChatsManager.cpp",
"Chat/MUCController.cpp",
"Chat/MUCSearchController.cpp",
"Chat/UserSearchController.cpp",
- "Chat/ChatMessageParser.cpp",
- "ContactSuggester.cpp",
- "MainController.cpp",
- "ProfileController.cpp",
- "ShowProfileController.cpp",
+ "ChatMessageSummarizer.cpp",
+ "Contact.cpp",
"ContactEditController.cpp",
+ "ContactProvider.cpp",
+ "ContactSuggester.cpp",
+ "ContactsFromXMPPRoster.cpp",
+ "EventNotifier.cpp",
+ "EventWindowController.cpp",
"FileTransfer/FileTransferController.cpp",
"FileTransfer/FileTransferOverview.cpp",
"FileTransfer/FileTransferProgressInfo.cpp",
- "Roster/RosterController.cpp",
- "Roster/RosterGroupExpandinessPersister.cpp",
+ "FileTransferListController.cpp",
+ "Highlighting/HighlightAction.cpp",
+ "Highlighting/HighlightEditorController.cpp",
+ "Highlighting/HighlightManager.cpp",
+ "Highlighting/Highlighter.cpp",
+ "HistoryController.cpp",
+ "HistoryViewController.cpp",
+ "MainController.cpp",
+ "PresenceNotifier.cpp",
+ "PreviousStatusStore.cpp",
+ "ProfileController.cpp",
+ "ProfileSettingsProvider.cpp",
"Roster/ContactRosterItem.cpp",
"Roster/GroupRosterItem.cpp",
- "Roster/RosterItem.cpp",
"Roster/Roster.cpp",
+ "Roster/RosterController.cpp",
+ "Roster/RosterGroupExpandinessPersister.cpp",
+ "Roster/RosterItem.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",
+ "SettingConstants.cpp",
"Settings/SettingsProviderHierachy.cpp",
"Settings/XMLSettingsProvider.cpp",
- "Storages/CertificateStorageFactory.cpp",
- "Storages/CertificateStorage.cpp",
+ "ShowProfileController.cpp",
+ "SoundEventController.cpp",
+ "StatusCache.cpp",
+ "StatusTracker.cpp",
+ "StatusUtil.cpp",
+ "Storages/AvatarFileStorage.cpp",
+ "Storages/CapsFileStorage.cpp",
"Storages/CertificateFileStorage.cpp",
"Storages/CertificateMemoryStorage.cpp",
- "Storages/AvatarFileStorage.cpp",
+ "Storages/CertificateStorage.cpp",
+ "Storages/CertificateStorageFactory.cpp",
"Storages/FileStorages.cpp",
"Storages/RosterFileStorage.cpp",
- "Storages/CapsFileStorage.cpp",
"Storages/VCardFileStorage.cpp",
- "StatusUtil.cpp",
+ "SystemTrayController.cpp",
"Translator.cpp",
- "XMPPURIController.cpp",
- "ChatMessageSummarizer.cpp",
- "SettingConstants.cpp",
+ "UIEvents/UIEvent.cpp",
+ "UIInterfaces/ChatListWindow.cpp",
+ "UIInterfaces/HighlightEditorWindow.cpp",
+ "UIInterfaces/XMLConsoleWidget.cpp",
"WhiteboardManager.cpp",
- "StatusCache.cpp",
- "HighlightAction.cpp",
- "HighlightEditorController.cpp",
- "HighlightManager.cpp",
- "HighlightRule.cpp",
- "Highlighter.cpp",
- "ContactsFromXMPPRoster.cpp",
- "ContactProvider.cpp",
- "Contact.cpp"
+ "XMLConsoleController.cpp",
+ "XMPPEvents/EventController.cpp",
+ "XMPPURIController.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/ChatMessageParserTest.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("Roster/UnitTest/LeastCommonSubsequenceTest.cpp"),
+ File("Roster/UnitTest/RosterControllerTest.cpp"),
+ File("Roster/UnitTest/RosterTest.cpp"),
+ File("Roster/UnitTest/TableRosterTest.cpp"),
File("Settings/UnitTest/SettingsProviderHierachyTest.cpp"),
- File("UnitTest/HighlightRuleTest.cpp"),
- File("UnitTest/ContactSuggesterTest.cpp")
+ File("UnitTest/ChatMessageSummarizerTest.cpp"),
+ File("UnitTest/ContactSuggesterTest.cpp"),
+ File("UnitTest/MockChatWindow.cpp"),
+ File("UnitTest/PresenceNotifierTest.cpp"),
+ File("UnitTest/PreviousStatusStoreTest.cpp"),
])
diff --git a/Swift/Controllers/SettingConstants.cpp b/Swift/Controllers/SettingConstants.cpp
index dedf56b..f0064ba 100644
--- a/Swift/Controllers/SettingConstants.cpp
+++ b/Swift/Controllers/SettingConstants.cpp
@@ -1,27 +1,28 @@
/*
- * Copyright (c) 2012-2016 Isode Limited.
+ * Copyright (c) 2012-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/SettingConstants.h>
namespace Swift {
const SettingsProvider::Setting<bool> SettingConstants::IDLE_GOES_OFFLINE = SettingsProvider::Setting<bool>("idleGoesOffline", false);
const SettingsProvider::Setting<int> SettingConstants::IDLE_TIMEOUT = SettingsProvider::Setting<int>("idleTimeout", 600);
const SettingsProvider::Setting<bool> SettingConstants::SHOW_NOTIFICATIONS = SettingsProvider::Setting<bool>("showNotifications", true);
const SettingsProvider::Setting<bool> SettingConstants::REQUEST_DELIVERYRECEIPTS = SettingsProvider::Setting<bool>("requestDeliveryReceipts", false);
const SettingsProvider::Setting<bool> SettingConstants::FORGET_PASSWORDS = SettingsProvider::Setting<bool>("forgetPasswords", false);
const SettingsProvider::Setting<bool> SettingConstants::REMEMBER_RECENT_CHATS = SettingsProvider::Setting<bool>("rememberRecentChats", true);
const SettingsProvider::Setting<std::string> SettingConstants::LAST_LOGIN_JID = SettingsProvider::Setting<std::string>("lastLoginJID", "");
const SettingsProvider::Setting<bool> SettingConstants::LOGIN_AUTOMATICALLY = SettingsProvider::Setting<bool>("loginAutomatically", false);
const SettingsProvider::Setting<bool> SettingConstants::SHOW_OFFLINE("showOffline", false);
const SettingsProvider::Setting<std::string> SettingConstants::EXPANDED_ROSTER_GROUPS("GroupExpandiness", "");
const SettingsProvider::Setting<bool> SettingConstants::PLAY_SOUNDS("playSounds", true);
const SettingsProvider::Setting<std::string> SettingConstants::HIGHLIGHT_RULES("highlightRules", "@");
+const SettingsProvider::Setting<std::string> SettingConstants::HIGHLIGHT_RULES_V2("highlightRulesV2", "@");
const SettingsProvider::Setting<std::string> SettingConstants::INVITE_AUTO_ACCEPT_MODE("inviteAutoAcceptMode", "presence");
const SettingsProvider::Setting<bool> SettingConstants::DISCONNECT_ON_CARD_REMOVAL("disconnectOnCardRemoval", true);
const SettingsProvider::Setting<bool> SettingConstants::SINGLE_SIGN_ON("singleSignOn", false);
}
diff --git a/Swift/Controllers/SettingConstants.h b/Swift/Controllers/SettingConstants.h
index 3f15c44..fec2d27 100644
--- a/Swift/Controllers/SettingConstants.h
+++ b/Swift/Controllers/SettingConstants.h
@@ -1,90 +1,97 @@
/*
- * Copyright (c) 2012-2016 Isode Limited.
+ * Copyright (c) 2012-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
#include <Swift/Controllers/Settings/SettingsProvider.h>
namespace Swift {
/**
* This class contains the major setting keys for Swift.
*/
class SettingConstants {
public:
/**
* The #IDLE_GOES_OFFLINE setting specifies whether to close the XMPP connection when
* the user went idle.
*
* True for automatic close of the XMPP connection and false for only changing the presence on idle.
*/
static const SettingsProvider::Setting<bool> IDLE_GOES_OFFLINE;
/**
* The #IDLE_TIMEOUT setting specifieds the seconds the user has to be inactive at the
* desktop so the user is regarded as idle.
*/
static const SettingsProvider::Setting<int> IDLE_TIMEOUT;
static const SettingsProvider::Setting<bool> SHOW_NOTIFICATIONS;
/**
* The #REQUEST_DELIVERYRECEIPTS settings specifies whether to request delivery receipts
* for messages to contacts that support message receipts.
*/
static const SettingsProvider::Setting<bool> REQUEST_DELIVERYRECEIPTS;
static const SettingsProvider::Setting<bool> FORGET_PASSWORDS;
static const SettingsProvider::Setting<bool> REMEMBER_RECENT_CHATS;
static const SettingsProvider::Setting<std::string> LAST_LOGIN_JID;
static const SettingsProvider::Setting<bool> LOGIN_AUTOMATICALLY;
/**
* The #SHOW_OFFLINE setting specifies whether or not to show offline contacts in the
* roster.
*
* If set true Swift will show offline contacts; else not.
*/
static const SettingsProvider::Setting<bool> SHOW_OFFLINE;
/**
* The #EXPANDED_ROSTER_GROUPS setting specifies the list of groups that are expanded
* in the roster UI.
*
* Its value is a string with group names seperated by newlines.
*/
static const SettingsProvider::Setting<std::string> EXPANDED_ROSTER_GROUPS;
static const SettingsProvider::Setting<bool> PLAY_SOUNDS;
/**
* The #HIGHLIGHT_RULES setting specifies the highlight rules and the associated actions.
*
* Its value is a Boost serialized representation.
*/
static const SettingsProvider::Setting<std::string> HIGHLIGHT_RULES;
/**
+ * The #HIGHLIGHT_RULES_V2 setting specifies the second version of highlight configuration
+ * rules, incompatible to old highlight rules.
+ *
+ * Its value is a Boost serialized representation.
+ */
+ static const SettingsProvider::Setting<std::string> HIGHLIGHT_RULES_V2;
+ /**
* The #INVITE_AUTO_ACCEPT_MODE setting specifies how to handle invites to chat rooms.
*
* Supported values are:
* - "no" : It is up to the user whether to accept the invitation and enter a room or not.
* - "presence" : The invitation is automatically accepted if it is from a contact that is
* already allowed to see the user's presence status.
* - "domain" : The invitation is automatically accepted if it is from a contact that is
* already allowed to see the user's presence status or from a contact of user's domain.
*/
static const SettingsProvider::Setting<std::string> INVITE_AUTO_ACCEPT_MODE;
/**
* The #DISCONNECT_ON_CARD_REMOVAL setting
* specifies whether or not to sign out the user when
* the smartcard is removed.
*
* If set true Swift will sign out the user when the
* smart card is removed; else not.
*/
static const SettingsProvider::Setting<bool> DISCONNECT_ON_CARD_REMOVAL;
/**
* The #SINGLE_SIGN_ON setting
* specifies whether to log in using Single Sign On.
* This is currently supported on Windows.
*
* If set true Swift will use GSSAPI authentication to
* log in the user; else not.
*/
static const SettingsProvider::Setting<bool> SINGLE_SIGN_ON;
};
}
diff --git a/Swift/Controllers/SoundEventController.cpp b/Swift/Controllers/SoundEventController.cpp
index 5c7568f..2bafcca 100644
--- a/Swift/Controllers/SoundEventController.cpp
+++ b/Swift/Controllers/SoundEventController.cpp
@@ -1,56 +1,56 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swift/Controllers/SoundEventController.h>
#include <boost/bind.hpp>
-#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/SoundPlayer.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h>
namespace Swift {
SoundEventController::SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager) {
settings_ = settings;
eventController_ = eventController;
soundPlayer_ = soundPlayer;
eventController_->onEventQueueEventAdded.connect(boost::bind(&SoundEventController::handleEventQueueEventAdded, this, _1));
highlightManager_ = highlightManager;
highlightManager_->onHighlight.connect(boost::bind(&SoundEventController::handleHighlight, this, _1));
settings_->onSettingChanged.connect(boost::bind(&SoundEventController::handleSettingChanged, this, _1));
playSounds_ = settings->getSetting(SettingConstants::PLAY_SOUNDS);
}
void SoundEventController::handleEventQueueEventAdded(std::shared_ptr<StanzaEvent> event) {
if (playSounds_ && std::dynamic_pointer_cast<IncomingFileTransferEvent>(event)) {
soundPlayer_->playSound(SoundPlayer::MessageReceived, "");
}
}
void SoundEventController::handleHighlight(const HighlightAction& action) {
- if (playSounds_ && action.playSound()) {
- soundPlayer_->playSound(SoundPlayer::MessageReceived, action.getSoundFile());
+ if (playSounds_ && action.getSoundFilePath()) {
+ soundPlayer_->playSound(SoundPlayer::MessageReceived, action.getSoundFilePath().get_value_or(""));
}
}
void SoundEventController::setPlaySounds(bool playSounds) {
playSounds_ = playSounds;
settings_->storeSetting(SettingConstants::PLAY_SOUNDS, playSounds);
}
void SoundEventController::handleSettingChanged(const std::string& settingPath) {
if (SettingConstants::PLAY_SOUNDS.getKey() == settingPath) {
playSounds_ = settings_->getSetting(SettingConstants::PLAY_SOUNDS);
}
}
}
diff --git a/Swift/Controllers/SoundEventController.h b/Swift/Controllers/SoundEventController.h
index e5b43b4..d612b18 100644
--- a/Swift/Controllers/SoundEventController.h
+++ b/Swift/Controllers/SoundEventController.h
@@ -1,34 +1,37 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
#include <memory>
-#include <Swift/Controllers/HighlightAction.h>
+#include <Swift/Controllers/Highlighting/HighlightAction.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/XMPPEvents/StanzaEvent.h>
namespace Swift {
class EventController;
class SoundPlayer;
class HighlightManager;
class SoundEventController {
public:
SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager);
void setPlaySounds(bool playSounds);
bool getSoundEnabled() {return playSounds_;}
+
private:
void handleSettingChanged(const std::string& settingPath);
void handleEventQueueEventAdded(std::shared_ptr<StanzaEvent> event);
void handleHighlight(const HighlightAction& action);
+
+ private:
EventController* eventController_;
SoundPlayer* soundPlayer_;
bool playSounds_;
SettingsProvider* settings_;
HighlightManager* highlightManager_;
};
}
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index 8ee083d..7aaa90e 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -1,113 +1,140 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/optional.hpp>
#include <boost/signals2.hpp>
#include <Swiften/Base/Tristate.h>
#include <Swiften/Elements/ChatState.h>
#include <Swiften/Elements/Form.h>
#include <Swiften/Elements/MUCOccupant.h>
#include <Swiften/Elements/SecurityLabelsCatalog.h>
#include <Swiften/MUC/MUCBookmark.h>
-#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
namespace Swift {
class AvatarManager;
class TreeWidget;
class Roster;
class TabComplete;
class RosterItem;
class ContactRosterItem;
class FileTransferController;
class UserSearchWindow;
class ChatWindow {
public:
class ChatMessagePart {
public:
virtual ~ChatMessagePart() {}
};
class ChatMessage {
public:
ChatMessage() {}
ChatMessage(const std::string& text) {
append(std::make_shared<ChatTextMessagePart>(text));
}
void append(const std::shared_ptr<ChatMessagePart>& part) {
parts_.push_back(part);
}
const std::vector<std::shared_ptr<ChatMessagePart> >& getParts() const {
return parts_;
}
void setParts(const std::vector<std::shared_ptr<ChatMessagePart> >& parts) {
parts_ = parts;
}
- void setFullMessageHighlightAction(const HighlightAction& action) {
- fullMessageHighlightAction_ = action;
+ void setHighlightActionSender(const HighlightAction& action) {
+ highlightActionSender_ = action;
}
- const HighlightAction& getFullMessageHighlightAction() const {
- return fullMessageHighlightAction_;
+ const HighlightAction& getHighlightActionSender() const {
+ return highlightActionSender_;
+ }
+
+ void setHighlightActionOwnMention(const HighlightAction& action) {
+ highlightActionOwnMention_ = action;
+ }
+
+ const HighlightAction& getHighlightActionOwnMention() const {
+ return highlightActionOwnMention_;
+ }
+
+ void setHighlightActionGroupMessage(const HighlightAction& action) {
+ highlightActionGroupMessage_ = action;
+ }
+
+ const HighlightAction& getHighlightActionGroupMessage() const {
+ return highlightActionGroupMessage_;
+ }
+
+ void setHighlightActonDirectMessage(const HighlightAction& action) {
+ highlightActionDirectMessage_ = action;
+ }
+
+ const HighlightAction& getHighlightActionDirectMessage() const {
+ return highlightActionDirectMessage_;
}
bool isMeCommand() const {
return isMeCommand_;
}
void setIsMeCommand(bool isMeCommand) {
isMeCommand_ = isMeCommand;
}
private:
std::vector<std::shared_ptr<ChatMessagePart> > parts_;
- HighlightAction fullMessageHighlightAction_;
+ HighlightAction highlightActionSender_;
+ HighlightAction highlightActionOwnMention_;
+ HighlightAction highlightActionGroupMessage_;
+ HighlightAction highlightActionDirectMessage_;
bool isMeCommand_ = false;
};
class ChatTextMessagePart : public ChatMessagePart {
public:
ChatTextMessagePart(const std::string& text) : text(text) {}
std::string text;
};
class ChatURIMessagePart : public ChatMessagePart {
public:
ChatURIMessagePart(const std::string& target) : target(target) {}
std::string target;
};
class ChatEmoticonMessagePart : public ChatMessagePart {
public:
std::string imagePath;
std::string alternativeText;
};
class ChatHighlightingMessagePart : public ChatMessagePart {
public:
HighlightAction action;
std::string text;
};
enum AckState {Pending, Received, Failed};
enum ReceiptState {ReceiptRequested, ReceiptReceived, ReceiptFailed};
diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp
deleted file mode 100644
index 8d49d5d..0000000
--- a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (c) 2012 Maciej Niedzielski
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2014-2016 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#include <string>
-#include <vector>
-
-#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;
- 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::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("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("keyword2", "from", "nick", HighlightRule::ChatMessage), true);
- }
-
- 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("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);
- }
-
- 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("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("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("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("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("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/Controllers/XMPPEvents/MessageEvent.h b/Swift/Controllers/XMPPEvents/MessageEvent.h
index 7af2be6..12f4c48 100644
--- a/Swift/Controllers/XMPPEvents/MessageEvent.h
+++ b/Swift/Controllers/XMPPEvents/MessageEvent.h
@@ -1,47 +1,67 @@
/*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2017 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#pragma once
#include <cassert>
#include <memory>
#include <Swiften/Elements/Message.h>
#include <Swift/Controllers/XMPPEvents/StanzaEvent.h>
namespace Swift {
class MessageEvent : public StanzaEvent {
public:
+ class SystemNotification {
+ public:
+ SystemNotification(const std::string& title, const std::string& message) : title(title), message(message) {
+ }
+
+ public:
+ std::string title;
+ std::string message;
+ };
+
+ public:
typedef std::shared_ptr<MessageEvent> ref;
MessageEvent(std::shared_ptr<Message> stanza) : stanza_(stanza), targetsMe_(true) {}
std::shared_ptr<Message> getStanza() {return stanza_;}
bool isReadable() {
return getStanza()->isError() || !getStanza()->getBody().get_value_or("").empty();
}
+ void addNotification(const std::string& title, const std::string& message) {
+ systemNotifications_.push_back(SystemNotification(title, message));
+ }
+
+ const std::vector<SystemNotification>& getNotifications() const {
+ return systemNotifications_;
+ }
+
void read() {
assert (isReadable());
conclude();
}
void setTargetsMe(bool targetsMe) {
targetsMe_ = targetsMe;
}
bool targetsMe() const {
return targetsMe_;
}
private:
std::shared_ptr<Message> stanza_;
+ std::vector<SystemNotification> systemNotifications_;
bool targetsMe_;
};
}