diff options
Diffstat (limited to 'Swift/Controllers/Chat')
| -rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 9 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.cpp | 42 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.h | 24 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatMessageParser.cpp | 167 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatMessageParser.h | 30 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatsManager.cpp | 8 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/MUCController.cpp | 13 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/MUCController.h | 4 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp | 487 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 84 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp | 10 | 
11 files changed, 471 insertions, 407 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_;  | 
 Swift