diff options
Diffstat (limited to 'Swift/Controllers')
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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -28,7 +28,7 @@ #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> @@ -51,7 +51,7 @@ 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); @@ -210,9 +210,10 @@ void ChatController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> mess } 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); } } 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -26,8 +26,8 @@ #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> @@ -38,13 +38,13 @@ 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(); } @@ -204,30 +204,12 @@ 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(); @@ -314,12 +296,6 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes } 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) { @@ -327,12 +303,12 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes 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); } 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -28,24 +28,25 @@ #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: @@ -73,7 +74,7 @@ namespace Swift { 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. @@ -96,8 +97,7 @@ namespace Swift { /** 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: 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,16 +1,18 @@ /* - * 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> @@ -19,14 +21,14 @@ 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; @@ -60,8 +62,12 @@ namespace Swift { 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; @@ -89,7 +95,7 @@ namespace Swift { 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 { @@ -137,61 +143,122 @@ namespace Swift { } 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -746,7 +746,7 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &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_); @@ -835,8 +835,8 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti 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 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 @@ -34,7 +34,7 @@ #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> @@ -80,6 +80,7 @@ MUCController::MUCController ( StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, + NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* uiEventStream, @@ -97,7 +98,7 @@ MUCController::MUCController ( 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; @@ -134,8 +135,7 @@ MUCController::MUCController ( 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)); @@ -593,10 +593,11 @@ void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> mess 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); } } } @@ -1111,7 +1112,7 @@ void MUCController::checkDuplicates(std::shared_ptr<Message> newMessage) { void MUCController::setNick(const std::string& nick) { nick_ = nick; - highlighter_->setNick(nick_); + chatMessageParser_->setNick(nick); } Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) { 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -54,7 +54,7 @@ namespace Swift { 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; 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,30 +1,22 @@ /* - * 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_ = ":("; @@ -33,244 +25,54 @@ public: 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_; @@ -278,4 +80,213 @@ private: 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -131,7 +131,7 @@ public: 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)); @@ -786,24 +786,17 @@ public: } 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"); @@ -817,28 +810,22 @@ public: 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"); @@ -852,8 +839,8 @@ public: 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() { @@ -922,12 +909,13 @@ public: 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); @@ -1141,8 +1129,8 @@ private: void handleHighlightAction(const HighlightAction& action) { handledHighlightActions_++; - if (action.playSound()) { - soundsPlayed_.insert(action.getSoundFile()); + if (action.getSoundFilePath()) { + soundsPlayed_.insert(action.getSoundFilePath().get_value_or("")); } } 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -94,18 +94,20 @@ public: 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_; @@ -592,7 +594,7 @@ private: ChatWindowFactory* chatWindowFactory_; UserSearchWindowFactory* userSearchWindowFactory_; MUCController* controller_; -// NickResolver* nickResolver_; + NickResolver* nickResolver_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; StanzaChannelPresenceSender* presenceSender_; 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -42,17 +42,14 @@ void EventNotifier::handleEventAdded(std::shared_ptr<StanzaEvent> event) { } 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)) { 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 @@ -5,12 +5,12 @@ */ /* - * 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> 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 @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2014-2016 Isode Limited. + * Copyright (c) 2014-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -57,8 +57,8 @@ #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> diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 105b44b..0c3127c 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -22,90 +22,88 @@ if env["SCONS_STAGE"] == "build" : 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,5 +1,5 @@ /* - * Copyright (c) 2012-2016 Isode Limited. + * Copyright (c) 2012-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -20,6 +20,7 @@ const SettingsProvider::Setting<bool> SettingConstants::SHOW_OFFLINE("showOfflin 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,5 +1,5 @@ /* - * Copyright (c) 2012-2016 Isode Limited. + * Copyright (c) 2012-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -58,6 +58,13 @@ namespace Swift { */ 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: 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -8,7 +8,7 @@ #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> @@ -37,8 +37,8 @@ void SoundEventController::handleEventQueueEventAdded(std::shared_ptr<StanzaEven } 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("")); } } 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -8,7 +8,7 @@ #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> @@ -21,10 +21,13 @@ namespace Swift { 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_; 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -22,7 +22,7 @@ #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; @@ -62,12 +62,36 @@ namespace Swift { 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 { @@ -80,7 +104,10 @@ namespace Swift { private: std::vector<std::shared_ptr<ChatMessagePart> > parts_; - HighlightAction fullMessageHighlightAction_; + HighlightAction highlightActionSender_; + HighlightAction highlightActionOwnMention_; + HighlightAction highlightActionGroupMessage_; + HighlightAction highlightActionDirectMessage_; bool isMeCommand_ = false; }; 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,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -16,6 +16,17 @@ 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) {} @@ -26,6 +37,14 @@ namespace Swift { 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(); @@ -41,6 +60,7 @@ namespace Swift { private: std::shared_ptr<Message> stanza_; + std::vector<SystemNotification> systemNotifications_; bool targetsMe_; }; } |