summaryrefslogtreecommitdiffstats
path: root/Swift
diff options
context:
space:
mode:
Diffstat (limited to 'Swift')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp9
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp42
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h24
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.cpp167
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.h30
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp8
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp13
-rw-r--r--Swift/Controllers/Chat/MUCController.h4
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp487
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp84
-rw-r--r--Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp10
-rw-r--r--Swift/Controllers/EventNotifier.cpp9
-rw-r--r--Swift/Controllers/HighlightAction.cpp62
-rw-r--r--Swift/Controllers/HighlightAction.h82
-rw-r--r--Swift/Controllers/HighlightManager.cpp148
-rw-r--r--Swift/Controllers/HighlightManager.h81
-rw-r--r--Swift/Controllers/HighlightRule.cpp200
-rw-r--r--Swift/Controllers/HighlightRule.h103
-rw-r--r--Swift/Controllers/Highlighter.cpp51
-rw-r--r--Swift/Controllers/Highlighter.h45
-rw-r--r--Swift/Controllers/Highlighting/HighlightAction.cpp75
-rw-r--r--Swift/Controllers/Highlighting/HighlightAction.h68
-rw-r--r--Swift/Controllers/Highlighting/HighlightConfiguration.cpp36
-rw-r--r--Swift/Controllers/Highlighting/HighlightConfiguration.h81
-rw-r--r--Swift/Controllers/Highlighting/HighlightEditorController.cpp (renamed from Swift/Controllers/HighlightEditorController.cpp)4
-rw-r--r--Swift/Controllers/Highlighting/HighlightEditorController.h (renamed from Swift/Controllers/HighlightEditorController.h)2
-rw-r--r--Swift/Controllers/Highlighting/HighlightManager.cpp102
-rw-r--r--Swift/Controllers/Highlighting/HighlightManager.h64
-rw-r--r--Swift/Controllers/Highlighting/Highlighter.cpp113
-rw-r--r--Swift/Controllers/Highlighting/Highlighter.h38
-rw-r--r--Swift/Controllers/MainController.cpp6
-rw-r--r--Swift/Controllers/SConscript110
-rw-r--r--Swift/Controllers/SettingConstants.cpp3
-rw-r--r--Swift/Controllers/SettingConstants.h9
-rw-r--r--Swift/Controllers/SoundEventController.cpp8
-rw-r--r--Swift/Controllers/SoundEventController.h7
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h41
-rw-r--r--Swift/Controllers/UnitTest/HighlightRuleTest.cpp324
-rw-r--r--Swift/Controllers/XMPPEvents/MessageEvent.h22
-rw-r--r--Swift/QtUI/QtCheckBoxStyledItemDelegate.cpp51
-rw-r--r--Swift/QtUI/QtCheckBoxStyledItemDelegate.h26
-rw-r--r--Swift/QtUI/QtColorSelectionStyledItemDelegate.cpp56
-rw-r--r--Swift/QtUI/QtColorSelectionStyledItemDelegate.h26
-rw-r--r--Swift/QtUI/QtHighlightEditor.cpp568
-rw-r--r--Swift/QtUI/QtHighlightEditor.h76
-rw-r--r--Swift/QtUI/QtHighlightEditor.ui466
-rw-r--r--Swift/QtUI/QtHighlightNotificationConfigDialog.cpp246
-rw-r--r--Swift/QtUI/QtHighlightNotificationConfigDialog.h41
-rw-r--r--Swift/QtUI/QtHighlightNotificationConfigDialog.ui537
-rw-r--r--Swift/QtUI/QtSoundSelectionStyledItemDelegate.cpp111
-rw-r--r--Swift/QtUI/QtSoundSelectionStyledItemDelegate.h23
-rw-r--r--Swift/QtUI/QtUIFactory.cpp4
-rw-r--r--Swift/QtUI/QtWebKitChatView.cpp20
-rw-r--r--Swift/QtUI/SConscript9
54 files changed, 2320 insertions, 2712 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_;
};
}
diff --git a/Swift/QtUI/QtCheckBoxStyledItemDelegate.cpp b/Swift/QtUI/QtCheckBoxStyledItemDelegate.cpp
new file mode 100644
index 0000000..5f71ed4
--- /dev/null
+++ b/Swift/QtUI/QtCheckBoxStyledItemDelegate.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtCheckBoxStyledItemDelegate.h>
+
+#include <array>
+
+#include <QApplication>
+#include <QEvent>
+#include <QPainter>
+
+namespace Swift {
+
+QtCheckBoxStyledItemDelegate::QtCheckBoxStyledItemDelegate(QObject* parent) : QStyledItemDelegate(parent) {
+
+}
+
+void QtCheckBoxStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
+ QStyleOptionButton cbOpt;
+ cbOpt.rect = option.rect;
+ cbOpt.state = QStyle::State_Active;
+
+ auto copyFlags = std::array<QStyle::StateFlag, 2>({{QStyle::State_Enabled/*, QStyle::State_Sunken*/}});
+ for (auto flag : copyFlags) {
+ if (option.state && flag) {
+ cbOpt.state |= flag;
+ }
+ }
+
+ bool isChecked = index.data(DATA_ROLE).toBool();
+ if (isChecked) {
+ cbOpt.state |= QStyle::State_On;
+ }
+ else {
+ cbOpt.state |= QStyle::State_Off;
+ }
+ painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base());
+ QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
+}
+
+bool QtCheckBoxStyledItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) {
+ if (event->type() == QEvent::MouseButtonRelease) {
+ model->setData(index, !index.data(DATA_ROLE).toBool(), DATA_ROLE);
+ }
+ return true;
+}
+
+};
diff --git a/Swift/QtUI/QtCheckBoxStyledItemDelegate.h b/Swift/QtUI/QtCheckBoxStyledItemDelegate.h
new file mode 100644
index 0000000..1d8db62
--- /dev/null
+++ b/Swift/QtUI/QtCheckBoxStyledItemDelegate.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <QStyledItemDelegate>
+
+namespace Swift {
+
+class QtCheckBoxStyledItemDelegate : public QStyledItemDelegate {
+ public:
+ QtCheckBoxStyledItemDelegate(QObject* parent = nullptr);
+
+ virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const;
+
+ public:
+ static const int DATA_ROLE = Qt::UserRole + 1;
+
+ protected:
+ virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index);
+};
+
+}
diff --git a/Swift/QtUI/QtColorSelectionStyledItemDelegate.cpp b/Swift/QtUI/QtColorSelectionStyledItemDelegate.cpp
new file mode 100644
index 0000000..c3fef5a
--- /dev/null
+++ b/Swift/QtUI/QtColorSelectionStyledItemDelegate.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtColorSelectionStyledItemDelegate.h>
+
+#include <QApplication>
+#include <QColorDialog>
+#include <QEvent>
+#include <QPainter>
+#include <QStyleOptionComboBox>
+
+namespace Swift {
+
+QtColorSelectionStyledItemDelegate::QtColorSelectionStyledItemDelegate(QObject* parent) : QStyledItemDelegate(parent) {
+
+}
+
+void QtColorSelectionStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
+ // draw item selected background
+ painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base());
+
+ // draw combo box
+ QStyleOptionComboBox opt;
+ opt.rect = option.rect;
+ opt.state = QStyle::State_Active | QStyle::State_Enabled;
+ QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &opt, painter);
+
+ // draw currently selected color
+ auto contentRect = QApplication::style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField);
+ auto currentColor = index.data(DATA_ROLE).value<QColor>();
+ painter->save();
+ painter->setClipRect(contentRect);
+ painter->fillRect(contentRect.adjusted(1, -4, -1, -3), currentColor);
+ painter->restore();
+}
+
+bool QtColorSelectionStyledItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) {
+ if (event->type() == QEvent::MouseButtonRelease) {
+ auto currentColor = index.data(DATA_ROLE).value<QColor>();
+ auto newColor = QColorDialog::getColor(currentColor);
+ if (newColor.isValid()) {
+ model->setData(index, newColor, DATA_ROLE);
+ }
+
+ auto correspondingDialog = qobject_cast<QDialog*>(parent());
+ if (correspondingDialog) {
+ correspondingDialog->raise();
+ }
+ }
+ return true;
+}
+
+};
diff --git a/Swift/QtUI/QtColorSelectionStyledItemDelegate.h b/Swift/QtUI/QtColorSelectionStyledItemDelegate.h
new file mode 100644
index 0000000..d6b3336
--- /dev/null
+++ b/Swift/QtUI/QtColorSelectionStyledItemDelegate.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <QStyledItemDelegate>
+
+namespace Swift {
+
+class QtColorSelectionStyledItemDelegate : public QStyledItemDelegate {
+ public:
+ QtColorSelectionStyledItemDelegate(QObject* parent = nullptr);
+
+ virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const;
+
+ public:
+ static const int DATA_ROLE = Qt::UserRole + 1;
+
+ protected:
+ virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index);
+};
+
+}
diff --git a/Swift/QtUI/QtHighlightEditor.cpp b/Swift/QtUI/QtHighlightEditor.cpp
deleted file mode 100644
index dd95941..0000000
--- a/Swift/QtUI/QtHighlightEditor.cpp
+++ /dev/null
@@ -1,568 +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/QtUI/QtHighlightEditor.h>
-
-#include <cassert>
-
-#include <boost/lexical_cast.hpp>
-
-#include <QFileDialog>
-#include <QScrollBar>
-#include <QTreeWidgetItem>
-
-#include <Swift/Controllers/HighlightManager.cpp>
-
-#include <Swift/QtUI/QtSettingsProvider.h>
-#include <Swift/QtUI/QtSwiftUtil.h>
-#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h>
-
-namespace Swift {
-
-QtHighlightEditor::QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent)
- : QWidget(parent), settings_(settings), previousRow_(-1)
-{
- ui_.setupUi(this);
-
- connect(ui_.listWidget, SIGNAL(currentRowChanged(int)), SLOT(onCurrentRowChanged(int)));
-
- connect(ui_.newButton, SIGNAL(clicked()), SLOT(onNewButtonClicked()));
- connect(ui_.deleteButton, SIGNAL(clicked()), SLOT(onDeleteButtonClicked()));
-
- connect(ui_.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(onApplyButtonClick()));
- connect(ui_.buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(onCancelButtonClick()));
- connect(ui_.buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(onOkButtonClick()));
- connect(ui_.buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()), SLOT(onResetToDefaultRulesClicked()));
-
- connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect()));
- connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(colorCustomSelect()));
-
- connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect()));
- connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect()));
- connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(soundCustomSelect()));
-
- /* replace the static line-edit control with the roster autocompleter */
- ui_.dummySenderName->setVisible(false);
- jid_ = new QtSuggestingJIDInput(this, settings);
- ui_.senderName->addWidget(jid_);
- jid_->onUserSelected.connect(boost::bind(&QtHighlightEditor::handleOnUserSelected, this, _1));
-
- /* handle autocomplete */
- connect(jid_, SIGNAL(textEdited(QString)), SLOT(handleContactSuggestionRequested(QString)));
-
- /* we need to be notified if any of the state changes so that we can update our textual rule description */
- connect(ui_.chatRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.roomRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.nickIsKeyword, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.allMsgRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.senderRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(jid_, SIGNAL(textChanged(const QString&)), SLOT(widgetClick()));
- connect(ui_.keywordRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.keyword, SIGNAL(textChanged(const QString&)), SLOT(widgetClick()));
- connect(ui_.matchPartialWords, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.matchCase, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
-
- /* allow selection of a custom sound file */
- connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(selectSoundFile()));
-
- /* allowing reordering items */
- connect(ui_.moveUpButton, SIGNAL(clicked()), this, SLOT(onUpButtonClicked()));
- connect(ui_.moveDownButton, SIGNAL(clicked()), this, SLOT(onDownButtonClicked()));
-
- setWindowTitle(tr("Highlight Rules"));
-}
-
-QtHighlightEditor::~QtHighlightEditor()
-{
-}
-
-QString QtHighlightEditor::formatShortDescription(const HighlightRule &rule)
-{
- const QString chatOrRoom = (rule.getMatchChat() ? tr("chat") : tr("room"));
-
- std::vector<std::string> senders = rule.getSenders();
- std::vector<std::string> keywords = rule.getKeywords();
-
- if (senders.empty() && keywords.empty() && !rule.getNickIsKeyword()) {
- return tr("All %1 messages.").arg(chatOrRoom);
- }
-
- if (rule.getNickIsKeyword()) {
- return tr("All %1 messages that mention my name.").arg(chatOrRoom);
- }
-
- if (!senders.empty()) {
- return tr("All %1 messages from %2.").arg(chatOrRoom, P2QSTRING(senders[0]));
- }
-
- if (!keywords.empty()) {
- return tr("All %1 messages mentioning the keyword '%2'.").arg(chatOrRoom, P2QSTRING(keywords[0]));
- }
-
- return tr("Unknown Rule");
-}
-
-void QtHighlightEditor::show()
-{
- highlightManager_->loadSettings();
-
- populateList();
-
- if (ui_.listWidget->count()) {
- selectRow(0);
- }
-
- updateResetToDefaultRulesVisibility();
-
- /* prepare default states */
- widgetClick();
-
- QWidget::show();
- QWidget::activateWindow();
-}
-
-void QtHighlightEditor::setHighlightManager(HighlightManager* highlightManager)
-{
- highlightManager_ = highlightManager;
-}
-
-void QtHighlightEditor::setContactSuggestions(const std::vector<Contact::ref>& suggestions)
-{
- jid_->setSuggestions(suggestions);
-}
-
-void QtHighlightEditor::colorOtherSelect()
-{
- ui_.foregroundColor->setEnabled(false);
- ui_.backgroundColor->setEnabled(false);
-}
-
-void QtHighlightEditor::colorCustomSelect()
-{
- ui_.foregroundColor->setEnabled(true);
- ui_.backgroundColor->setEnabled(true);
-}
-
-void QtHighlightEditor::soundOtherSelect()
-{
- ui_.soundFile->setEnabled(false);
- ui_.soundFileButton->setEnabled(false);
-}
-
-void QtHighlightEditor::soundCustomSelect()
-{
- ui_.soundFile->setEnabled(true);
- ui_.soundFileButton->setEnabled(true);
-}
-
-void QtHighlightEditor::onNewButtonClicked()
-{
- int row = getSelectedRow() + 1;
- populateList();
- HighlightRule newRule;
- newRule.setMatchMUC(true);
- highlightManager_->insertRule(row, newRule);
- QListWidgetItem *item = new QListWidgetItem();
- item->setText(formatShortDescription(newRule));
- QFont font;
- font.setItalic(true);
- item->setFont(font);
- ui_.listWidget->insertItem(row, item);
- selectRow(row);
-}
-
-void QtHighlightEditor::onDeleteButtonClicked()
-{
- int selectedRow = getSelectedRow();
- assert(selectedRow>=0 && selectedRow<ui_.listWidget->count());
- delete ui_.listWidget->takeItem(selectedRow);
- highlightManager_->removeRule(selectedRow);
-
- if (!ui_.listWidget->count()) {
- disableDialog();
- ui_.deleteButton->setEnabled(false);
- } else {
- if (selectedRow == ui_.listWidget->count()) {
- selectRow(ui_.listWidget->count() - 1);
- } else {
- selectRow(selectedRow);
- }
- }
-}
-
-void QtHighlightEditor::moveRowFromTo(int fromRow, int toRow) {
- int verticalScrollAreaPosition = ui_.scrollArea->verticalScrollBar()->value();
- highlightManager_->swapRules(fromRow, toRow);
- populateList();
- selectRow(toRow);
- ui_.scrollArea->verticalScrollBar()->setValue(verticalScrollAreaPosition);
-}
-
-void QtHighlightEditor::onUpButtonClicked() {
- const size_t moveFrom = ui_.listWidget->currentRow();
- const size_t moveTo = moveFrom - 1;
- moveRowFromTo(moveFrom, moveTo);
-}
-
-void QtHighlightEditor::onDownButtonClicked() {
- const size_t moveFrom = ui_.listWidget->currentRow();
- const size_t moveTo = moveFrom + 1;
- moveRowFromTo(moveFrom, moveTo);
-}
-
-void QtHighlightEditor::onCurrentRowChanged(int currentRow)
-{
- ui_.deleteButton->setEnabled(currentRow != -1);
- ui_.moveUpButton->setEnabled(currentRow != -1 && currentRow != 0);
- ui_.moveDownButton->setEnabled(currentRow != -1 && currentRow != (ui_.listWidget->count()-1));
-
- if (previousRow_ != -1) {
- if (ui_.listWidget->count() > previousRow_) {
- QFont font;
- font.setItalic(false);
- ui_.listWidget->item(previousRow_)->setFont(font);
- highlightManager_->setRule(previousRow_, ruleFromDialog());
- }
- }
-
- if (currentRow != -1) {
- HighlightRule rule = highlightManager_->getRule(currentRow);
- ruleToDialog(rule);
- if (ui_.listWidget->currentItem()) {
- QFont font;
- font.setItalic(true);
- ui_.listWidget->currentItem()->setFont(font);
- ui_.listWidget->currentItem()->setText(formatShortDescription(rule));
- }
- }
-
- /* grey the dialog if we have nothing selected */
- if (currentRow == -1) {
- disableDialog();
- }
-
- previousRow_ = currentRow;
-
- updateResetToDefaultRulesVisibility();
-}
-
-void QtHighlightEditor::onApplyButtonClick()
-{
- selectRow(getSelectedRow()); /* force save */
- highlightManager_->storeSettings();
-}
-
-void QtHighlightEditor::onCancelButtonClick()
-{
- close();
-}
-
-void QtHighlightEditor::onOkButtonClick()
-{
- onApplyButtonClick();
- close();
-}
-
-void QtHighlightEditor::setChildWidgetStates()
-{
- /* disable appropriate radio button child widgets */
-
- if (ui_.chatRadio->isChecked()) {
- if (ui_.nickIsKeyword->isChecked()) {
- /* switch to another choice before we disable this button */
- ui_.allMsgRadio->setChecked(true);
- }
- ui_.nickIsKeyword->setEnabled(false);
- } else if (ui_.roomRadio->isChecked()) {
- ui_.nickIsKeyword->setEnabled(true);
- } else { /* chats and rooms */
- ui_.nickIsKeyword->setEnabled(true);
- }
-
- if (ui_.senderRadio->isChecked()) {
- jid_->setEnabled(true);
- } else {
- jid_->setEnabled(false);
- }
-
- if (ui_.keywordRadio->isChecked()) {
- ui_.keyword->setEnabled(true);
- ui_.matchPartialWords->setEnabled(true);
- ui_.matchCase->setEnabled(true);
- } else {
- ui_.keyword->setEnabled(false);
- ui_.matchPartialWords->setEnabled(false);
- ui_.matchCase->setEnabled(false);
- }
-
- if (ui_.chatRadio->isChecked()) {
- ui_.allMsgRadio->setText(tr("Apply to all chat messages"));
- } else {
- ui_.allMsgRadio->setText(tr("Apply to all room messages"));
- }
-}
-
-void QtHighlightEditor::widgetClick()
-{
- setChildWidgetStates();
-
- HighlightRule rule = ruleFromDialog();
-
- if (ui_.listWidget->currentItem()) {
- ui_.listWidget->currentItem()->setText(formatShortDescription(rule));
- }
-}
-
-void QtHighlightEditor::disableDialog()
-{
- ui_.chatRadio->setEnabled(false);
- ui_.roomRadio->setEnabled(false);
- ui_.allMsgRadio->setEnabled(false);
- ui_.nickIsKeyword->setEnabled(false);
- ui_.senderRadio->setEnabled(false);
- ui_.dummySenderName->setEnabled(false);
- ui_.keywordRadio->setEnabled(false);
- ui_.keyword->setEnabled(false);
- ui_.matchPartialWords->setEnabled(false);
- ui_.matchCase->setEnabled(false);
- ui_.noColorRadio->setEnabled(false);
- ui_.customColorRadio->setEnabled(false);
- ui_.foregroundColor->setEnabled(false);
- ui_.backgroundColor->setEnabled(false);
- ui_.noSoundRadio->setEnabled(false);
- ui_.defaultSoundRadio->setEnabled(false);
- ui_.customSoundRadio->setEnabled(false);
- ui_.soundFile->setEnabled(false);
- ui_.soundFileButton->setEnabled(false);
-}
-
-void QtHighlightEditor::handleContactSuggestionRequested(const QString& text)
-{
- std::string stdText = Q2PSTRING(text);
- onContactSuggestionsRequested(stdText);
-}
-
-void QtHighlightEditor::selectSoundFile()
-{
- QString path = QFileDialog::getOpenFileName(this, tr("Select sound file..."), QString(), tr("Sounds (*.wav)"));
- ui_.soundFile->setText(path);
-}
-
-void QtHighlightEditor::onResetToDefaultRulesClicked() {
- highlightManager_->resetToDefaultRulesList();
- populateList();
- updateResetToDefaultRulesVisibility();
-}
-
-void QtHighlightEditor::handleOnUserSelected(const Contact::ref& contact) {
- /* this might seem like it should be standard behaviour for the suggesting input box, but is not desirable in all cases */
- if (contact->jid.isValid()) {
- jid_->setText(P2QSTRING(contact->jid.toString()));
- } else {
- jid_->setText(P2QSTRING(contact->name));
- }
-}
-
-void QtHighlightEditor::populateList()
-{
- previousRow_ = -1;
- ui_.listWidget->clear();
- HighlightRulesListPtr rules = highlightManager_->getRules();
- for (size_t i = 0; i < rules->getSize(); ++i) {
- const HighlightRule& rule = rules->getRule(i);
- QListWidgetItem *item = new QListWidgetItem();
- item->setText(formatShortDescription(rule));
- ui_.listWidget->addItem(item);
- }
-}
-
-void QtHighlightEditor::selectRow(int row)
-{
- for (int i = 0; i < ui_.listWidget->count(); ++i) {
- if (i == row) {
- ui_.listWidget->item(i)->setSelected(true);
- onCurrentRowChanged(i);
- } else {
- ui_.listWidget->item(i)->setSelected(false);
- }
- }
- ui_.listWidget->setCurrentRow(row);
-}
-
-int QtHighlightEditor::getSelectedRow() const
-{
- for (int i = 0; i < ui_.listWidget->count(); ++i) {
- if (ui_.listWidget->item(i)->isSelected()) {
- return i;
- }
- }
- return -1;
-}
-
-HighlightRule QtHighlightEditor::ruleFromDialog()
-{
- HighlightRule rule;
- HighlightAction& action = rule.getAction();
-
- if (ui_.chatRadio->isChecked()) {
- rule.setMatchChat(true);
- rule.setMatchMUC(false);
- } else {
- rule.setMatchChat(false);
- rule.setMatchMUC(true);
- }
-
- if (ui_.allMsgRadio->isChecked()) {
- action.setHighlightWholeMessage(true);
- }
-
- if (ui_.senderRadio->isChecked()) {
- QString senderName = jid_->text();
- if (!senderName.isEmpty()) {
- std::vector<std::string> senders;
- senders.push_back(Q2PSTRING(senderName));
- rule.setSenders(senders);
- action.setHighlightWholeMessage(true);
- }
- }
-
- if (ui_.keywordRadio->isChecked()) {
- QString keywordString = ui_.keyword->text();
- if (!keywordString.isEmpty()) {
- std::vector<std::string> keywords;
- keywords.push_back(Q2PSTRING(keywordString));
- rule.setKeywords(keywords);
- }
- }
-
- if (ui_.nickIsKeyword->isChecked()) {
- rule.setNickIsKeyword(true);
- rule.setMatchWholeWords(true);
- rule.setMatchCase(true);
- } else {
- rule.setMatchWholeWords(!ui_.matchPartialWords->isChecked());
- rule.setMatchCase(ui_.matchCase->isChecked());
- }
-
- if (ui_.noColorRadio->isChecked()) {
- action.setTextColor("");
- action.setTextBackground("");
- } else {
- action.setTextColor(Q2PSTRING(ui_.foregroundColor->getColor().name()));
- action.setTextBackground(Q2PSTRING(ui_.backgroundColor->getColor().name()));
- }
-
- if (ui_.noSoundRadio->isChecked()) {
- action.setPlaySound(false);
- } else if (ui_.defaultSoundRadio->isChecked()) {
- action.setPlaySound(true);
- action.setSoundFile("");
- } else {
- action.setPlaySound(true);
- action.setSoundFile(Q2PSTRING(ui_.soundFile->text()));
- }
-
- return rule;
-}
-
-void QtHighlightEditor::ruleToDialog(const HighlightRule& rule)
-{
- ui_.chatRadio->setEnabled(true);
- ui_.roomRadio->setEnabled(true);
-
- if (rule.getMatchMUC()) {
- ui_.chatRadio->setChecked(false);
- ui_.roomRadio->setChecked(true);
- } else {
- ui_.chatRadio->setChecked(true);
- ui_.roomRadio->setChecked(false);
- }
-
- ui_.allMsgRadio->setEnabled(true);
- ui_.allMsgRadio->setChecked(true); /* this is the default radio button */
- jid_->setText("");
- ui_.keyword->setText("");
- ui_.matchPartialWords->setChecked(false);
- ui_.matchCase->setChecked(false);
-
- ui_.nickIsKeyword->setEnabled(true);
- if (rule.getNickIsKeyword()) {
- ui_.nickIsKeyword->setChecked(true);
- }
-
- ui_.senderRadio->setEnabled(true);
- std::vector<std::string> senders = rule.getSenders();
- if (!senders.empty()) {
- ui_.senderRadio->setChecked(true);
- jid_->setText(P2QSTRING(senders[0]));
- }
-
- ui_.keywordRadio->setEnabled(true);
- std::vector<std::string> keywords = rule.getKeywords();
- if (!keywords.empty()) {
- ui_.keywordRadio->setChecked(true);
- ui_.keyword->setText(P2QSTRING(keywords[0]));
- ui_.matchPartialWords->setChecked(!rule.getMatchWholeWords());
- ui_.matchCase->setChecked(rule.getMatchCase());
- }
-
- const HighlightAction& action = rule.getAction();
-
- ui_.noColorRadio->setEnabled(true);
- ui_.customColorRadio->setEnabled(true);
- if (action.getTextColor().empty() && action.getTextBackground().empty()) {
- ui_.noColorRadio->setChecked(true);
- ui_.foregroundColor->setEnabled(false);
- ui_.backgroundColor->setEnabled(false);
- } else {
- ui_.foregroundColor->setEnabled(true);
- ui_.backgroundColor->setEnabled(true);
- QColor foregroundColor(P2QSTRING(action.getTextColor()));
- ui_.foregroundColor->setColor(foregroundColor);
- QColor backgroundColor(P2QSTRING(action.getTextBackground()));
- ui_.backgroundColor->setColor(backgroundColor);
- ui_.customColorRadio->setChecked(true);
- }
-
- ui_.noSoundRadio->setEnabled(true);
- ui_.defaultSoundRadio->setEnabled(true);
- ui_.customSoundRadio->setEnabled(true);
- ui_.soundFile->setText("");
- ui_.soundFile->setEnabled(false);
- ui_.soundFileButton->setEnabled(false);
- if (action.playSound()) {
- if (action.getSoundFile().empty()) {
- ui_.defaultSoundRadio->setChecked(true);
- } else {
- ui_.customSoundRadio->setChecked(true);
- ui_.soundFile->setText(P2QSTRING(action.getSoundFile()));
- ui_.soundFile->setEnabled(true);
- ui_.soundFileButton->setEnabled(true);
- }
- } else {
- ui_.noSoundRadio->setChecked(true);
- }
-
- /* set radio button child option states */
- setChildWidgetStates();
-}
-
-void QtHighlightEditor::updateResetToDefaultRulesVisibility() {
- ui_.buttonBox->button(QDialogButtonBox::RestoreDefaults)->setVisible(!highlightManager_->isDefaultRulesList());
-}
-
-}
diff --git a/Swift/QtUI/QtHighlightEditor.h b/Swift/QtUI/QtHighlightEditor.h
deleted file mode 100644
index c4a12e2..0000000
--- a/Swift/QtUI/QtHighlightEditor.h
+++ /dev/null
@@ -1,76 +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 <Swift/Controllers/HighlightRule.h>
-#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h>
-
-#include <Swift/QtUI/ui_QtHighlightEditor.h>
-
-namespace Swift {
-
- class QtSettingsProvider;
- class QtSuggestingJIDInput;
- class QtWebKitChatView;
-
- class QtHighlightEditor : public QWidget, public HighlightEditorWindow {
- Q_OBJECT
-
- public:
- QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent = nullptr);
- virtual ~QtHighlightEditor();
-
- virtual void show();
- virtual void setHighlightManager(HighlightManager* highlightManager);
- virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions);
-
- private slots:
- void colorOtherSelect();
- void colorCustomSelect();
- void soundOtherSelect();
- void soundCustomSelect();
- void onNewButtonClicked();
- void onDeleteButtonClicked();
- void onUpButtonClicked();
- void onDownButtonClicked();
- void onCurrentRowChanged(int currentRow);
- void onApplyButtonClick();
- void onCancelButtonClick();
- void onOkButtonClick();
- void setChildWidgetStates();
- void widgetClick();
- void disableDialog();
- void handleContactSuggestionRequested(const QString& text);
- void selectSoundFile();
- void onResetToDefaultRulesClicked();
-
- private:
- QString formatShortDescription(const HighlightRule &rule);
- void handleOnUserSelected(const Contact::ref& contact);
- void populateList();
- void selectRow(int row);
- int getSelectedRow() const;
- HighlightRule ruleFromDialog();
- void ruleToDialog(const HighlightRule& rule);
- void updateResetToDefaultRulesVisibility();
- void moveRowFromTo(int fromRow, int toRow);
-
- private:
- Ui::QtHighlightEditor ui_;
- QtSettingsProvider* settings_;
- HighlightManager* highlightManager_ = nullptr;
- QtSuggestingJIDInput* jid_;
- int previousRow_;
- };
-
-}
diff --git a/Swift/QtUI/QtHighlightEditor.ui b/Swift/QtUI/QtHighlightEditor.ui
deleted file mode 100644
index 6d2338d..0000000
--- a/Swift/QtUI/QtHighlightEditor.ui
+++ /dev/null
@@ -1,466 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>QtHighlightEditor</class>
- <widget class="QWidget" name="QtHighlightEditor">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>500</width>
- <height>600</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>500</width>
- <height>600</height>
- </size>
- </property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QScrollArea" name="scrollArea">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="frameShape">
- <enum>QFrame::StyledPanel</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="widgetResizable">
- <bool>true</bool>
- </property>
- <widget class="QWidget" name="scrollAreaWidgetContents">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>463</width>
- <height>792</height>
- </rect>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <item>
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Incoming messages are checked against the following rules. First rule that matches will be executed.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QListWidget" name="listWidget">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <spacer name="horizontalSpacer_8">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="newButton">
- <property name="text">
- <string>New Rule</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="deleteButton">
- <property name="text">
- <string>Remove Rule</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="moveUpButton">
- <property name="text">
- <string>Move Up</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="moveDownButton">
- <property name="text">
- <string>Move Down</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="Line" name="line_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Apply Rule To</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QRadioButton" name="roomRadio">
- <property name="text">
- <string>Rooms</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="chatRadio">
- <property name="text">
- <string>Chats</string>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>246</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_6">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Rule Conditions</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QRadioButton" name="allMsgRadio">
- <property name="text">
- <string>Apply to all messages</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="nickIsKeyword">
- <property name="text">
- <string>Only messages mentioning me</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="senderRadio">
- <property name="text">
- <string>Messages from this sender:</string>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="senderName">
- <property name="sizeConstraint">
- <enum>QLayout::SetMinimumSize</enum>
- </property>
- <item>
- <widget class="QLineEdit" name="dummySenderName"/>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QRadioButton" name="keywordRadio">
- <property name="text">
- <string>Messages containing this keyword:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="keyword"/>
- </item>
- <item>
- <widget class="QCheckBox" name="matchPartialWords">
- <property name="text">
- <string>Match keyword within longer words</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="matchCase">
- <property name="text">
- <string>Keyword is case sensitive</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Highlight Action</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <item>
- <widget class="QRadioButton" name="noColorRadio">
- <property name="text">
- <string>No Highlight</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="customColorRadio">
- <property name="text">
- <string>Custom Color</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_5">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_6">
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="Swift::QtColorToolButton" name="foregroundColor">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>&amp;Text</string>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextBesideIcon</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="Swift::QtColorToolButton" name="backgroundColor">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>&amp;Background</string>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextBesideIcon</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Sound Action</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <item>
- <widget class="QRadioButton" name="noSoundRadio">
- <property name="text">
- <string>No Sound</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="defaultSoundRadio">
- <property name="text">
- <string>Default Sound</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="customSoundRadio">
- <property name="text">
- <string>Custom Sound</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_6">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_8">
- <item>
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLineEdit" name="soundFile">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="readOnly">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="soundFileButton">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_9">
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>Swift::QtColorToolButton</class>
- <extends>QToolButton</extends>
- <header>QtColorToolButton.h</header>
- </customwidget>
- </customwidgets>
- <resources/>
- <connections/>
-</ui>
diff --git a/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp b/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp
new file mode 100644
index 0000000..c4e64ab
--- /dev/null
+++ b/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2016-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtHighlightNotificationConfigDialog.h>
+
+#include <Swiften/Base/Log.h>
+
+#include <Swift/Controllers/Highlighting/HighlightManager.h>
+
+#include <Swift/QtUI/QtCheckBoxStyledItemDelegate.h>
+#include <Swift/QtUI/QtColorSelectionStyledItemDelegate.h>
+#include <Swift/QtUI/QtSoundSelectionStyledItemDelegate.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+
+QtHighlightNotificationConfigDialog::QtHighlightNotificationConfigDialog(QtSettingsProvider* settings, QWidget* parent) : QDialog(parent), settings_(settings) {
+ ui_.setupUi(this);
+
+ // setup custom delegates for checkboxes, color selection, and sound selection
+ ui_.userHighlightTreeWidget->setItemDelegateForColumn(1, new QtColorSelectionStyledItemDelegate(this));
+ ui_.userHighlightTreeWidget->setItemDelegateForColumn(2, new QtColorSelectionStyledItemDelegate(this));
+ ui_.userHighlightTreeWidget->setItemDelegateForColumn(3, new QtSoundSelectionStyledItemDelegate(this));
+ ui_.userHighlightTreeWidget->setItemDelegateForColumn(4, new QtCheckBoxStyledItemDelegate(this));
+
+ ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(1, new QtCheckBoxStyledItemDelegate(this));
+ ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(2, new QtColorSelectionStyledItemDelegate(this));
+ ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(3, new QtColorSelectionStyledItemDelegate(this));
+ ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(4, new QtSoundSelectionStyledItemDelegate(this));
+ ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(5, new QtCheckBoxStyledItemDelegate(this));
+
+ // user highlight edit slots
+ connect(ui_.addUserHighlightPushButton, &QPushButton::clicked, [&](bool) {
+ auto item = new QTreeWidgetItem();
+ item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
+ item->setData(0, Qt::EditRole, "");
+ item->setData(1, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#000000"));
+ item->setData(2, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#ffff00"));
+ item->setData(3, Qt::EditRole, "");
+ item->setData(4, QtCheckBoxStyledItemDelegate::DATA_ROLE, QVariant(true));
+ ui_.userHighlightTreeWidget->addTopLevelItem(item);
+ });
+ connect(ui_.removeUserHighlightPushButton, &QPushButton::clicked, [&](bool) {
+ auto currentItem = ui_.userHighlightTreeWidget->currentItem();
+ if (currentItem) {
+ ui_.userHighlightTreeWidget->takeTopLevelItem(ui_.userHighlightTreeWidget->indexOfTopLevelItem(currentItem));
+ }
+ });
+ connect(ui_.userHighlightTreeWidget, &QTreeWidget::currentItemChanged, [&](QTreeWidgetItem* current, QTreeWidgetItem* ) {
+ ui_.removeUserHighlightPushButton->setEnabled(current != 0);
+ });
+
+ // keyword highlight edit slots
+ connect(ui_.addKeywordHighlightPushButton, &QPushButton::clicked, [&](bool) {
+ auto item = new QTreeWidgetItem();
+ item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
+ item->setData(0, Qt::EditRole, "");
+ item->setData(1, QtCheckBoxStyledItemDelegate::DATA_ROLE, QVariant(false));
+ item->setData(2, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#000000"));
+ item->setData(3, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#ffff00"));
+ item->setData(4, Qt::EditRole, "");
+ item->setData(5, QtCheckBoxStyledItemDelegate::DATA_ROLE, QVariant(true));
+ ui_.keywordHighlightTreeWidget->addTopLevelItem(item);
+ });
+ connect(ui_.removeKeywordHighlightPushButton, &QPushButton::clicked, [&](bool) {
+ auto currentItem = ui_.keywordHighlightTreeWidget->currentItem();
+ if (currentItem) {
+ ui_.keywordHighlightTreeWidget->takeTopLevelItem(ui_.keywordHighlightTreeWidget->indexOfTopLevelItem(currentItem));
+ }
+ });
+ connect(ui_.keywordHighlightTreeWidget, &QTreeWidget::currentItemChanged, [&](QTreeWidgetItem* current, QTreeWidgetItem* ) {
+ ui_.removeKeywordHighlightPushButton->setEnabled(current != 0);
+ });
+
+ // setup slots for main dialog buttons
+ connect(ui_.buttonBox, &QDialogButtonBox::clicked, [&](QAbstractButton* clickedButton) {
+ if (clickedButton == ui_.buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
+ if (highlightManager_) {
+ highlightManager_->resetToDefaultConfiguration();
+ setHighlightConfigurationToDialog(*highlightManager_->getConfiguration());
+ }
+ }
+ });
+ connect(this, &QDialog::accepted, [&]() {
+ if (highlightManager_) {
+ highlightManager_->setConfiguration(getHighlightConfigurationFromDialog());
+ }
+ });
+}
+
+QtHighlightNotificationConfigDialog::~QtHighlightNotificationConfigDialog() {
+}
+
+void QtHighlightNotificationConfigDialog::show() {
+ if (highlightManager_) {
+ setHighlightConfigurationToDialog(*(highlightManager_->getConfiguration()));
+ }
+ QWidget::show();
+ QWidget::activateWindow();
+}
+
+void QtHighlightNotificationConfigDialog::setHighlightManager(HighlightManager* highlightManager) {
+ highlightManager_ = highlightManager;
+}
+
+void QtHighlightNotificationConfigDialog::setContactSuggestions(const std::vector<Contact::ref>& /*suggestions*/) {
+
+}
+
+HighlightConfiguration QtHighlightNotificationConfigDialog::getHighlightConfigurationFromDialog() const {
+ auto qtColorToOptionalString = [&](const QColor& color) {
+ boost::optional<std::string> colorString;
+ if (color.isValid()) {
+ colorString = Q2PSTRING(color.name(QColor::HexRgb));
+ }
+ return colorString;
+ };
+
+ auto getHighlightActionFromWidgetItem = [&](const QTreeWidgetItem* item, int startingColumn) {
+ HighlightAction action;
+
+ action.setFrontColor(qtColorToOptionalString(item->data(startingColumn, QtColorSelectionStyledItemDelegate::DATA_ROLE).value<QColor>()));
+ action.setBackColor(qtColorToOptionalString(item->data(startingColumn + 1, QtColorSelectionStyledItemDelegate::DATA_ROLE).value<QColor>()));
+
+ std::string soundFilePath = Q2PSTRING(item->data(startingColumn + 2, Qt::EditRole).toString());
+ if (soundFilePath == "defaultSound") {
+ action.setSoundFilePath(boost::optional<std::string>(""));
+ }
+ else if (soundFilePath.empty()) {
+ action.setSoundFilePath(boost::optional<std::string>());
+ }
+ else {
+ action.setSoundFilePath(boost::optional<std::string>(soundFilePath));
+ }
+
+ action.setSystemNotificationEnabled(item->data(startingColumn + 3, QtCheckBoxStyledItemDelegate::DATA_ROLE).toBool());
+ return action;
+ };
+
+ HighlightConfiguration uiConfiguration;
+
+ // contact highlights
+ for (int i = 0; i < ui_.userHighlightTreeWidget->topLevelItemCount(); i++) {
+ auto item = ui_.userHighlightTreeWidget->topLevelItem(i);
+ HighlightConfiguration::ContactHighlight contactHighlight;
+ contactHighlight.name = Q2PSTRING(item->data(0, Qt::EditRole).toString());
+ contactHighlight.action = getHighlightActionFromWidgetItem(item, 1);
+ uiConfiguration.contactHighlights.push_back(contactHighlight);
+ }
+
+ // keyword highlights
+ for (int i = 0; i < ui_.keywordHighlightTreeWidget->topLevelItemCount(); i++) {
+ auto item = ui_.keywordHighlightTreeWidget->topLevelItem(i);
+ HighlightConfiguration::KeywordHightlight keywordHighlight;
+ keywordHighlight.keyword = Q2PSTRING(item->data(0, Qt::EditRole).toString());
+ keywordHighlight.matchCaseSensitive = item->data(1, QtCheckBoxStyledItemDelegate::DATA_ROLE).toBool();
+ keywordHighlight.action = getHighlightActionFromWidgetItem(item, 2);
+ uiConfiguration.keywordHighlights.push_back(keywordHighlight);
+ }
+
+ // general configuration
+ uiConfiguration.playSoundOnIncomingDirectMessages = ui_.playSoundOnDirectMessagesCheckBox->isChecked();
+ uiConfiguration.showNotificationOnIncomingDirectMessages = ui_.notificationOnDirectMessagesCheckBox->isChecked();
+ uiConfiguration.playSoundOnIncomingGroupchatMessages = ui_.playSoundOnGroupMessagesCheckBox->isChecked();
+ uiConfiguration.showNotificationOnIncomingGroupchatMessages = ui_.notificationOnGroupMessagesCheckBox->isChecked();
+
+ uiConfiguration.ownMentionAction.setFrontColor(qtColorToOptionalString(ui_.mentionTextColorColorButton->getColor()));
+ uiConfiguration.ownMentionAction.setBackColor(qtColorToOptionalString(ui_.mentionBackgroundColorButton->getColor()));
+ uiConfiguration.ownMentionAction.setSoundFilePath(ui_.playSoundOnMentionCheckBox->isChecked() ? boost::optional<std::string>("") : boost::optional<std::string>());
+ uiConfiguration.ownMentionAction.setSystemNotificationEnabled(ui_.notificationOnMentionCheckBox->isChecked());
+ return uiConfiguration;
+}
+
+void QtHighlightNotificationConfigDialog::setHighlightConfigurationToDialog(const HighlightConfiguration& config) {
+ auto optionalStringToQtColor = [](const boost::optional<std::string>& colorString) {
+ QColor qtColor;
+ if (colorString) {
+ qtColor = QColor(P2QSTRING(colorString.get_value_or(std::string(""))));
+ }
+ return qtColor;
+ };
+
+ auto optionalSoundPathStringToQString = [](const boost::optional<std::string>& soundPath) {
+ QString ret;
+ if (soundPath) {
+ if (soundPath.get_value_or("").empty()) {
+ ret = "defaultSound";
+ }
+ else {
+ ret = P2QSTRING(soundPath.get_value_or(""));
+ }
+ }
+ return ret;
+ };
+
+ auto setHighlightActionOnTreeWidgetItem = [&](QTreeWidgetItem* item, int startingColumn, const HighlightAction& action) {
+ item->setData(startingColumn, QtColorSelectionStyledItemDelegate::DATA_ROLE, optionalStringToQtColor(action.getFrontColor()));
+ item->setData(startingColumn + 1, QtColorSelectionStyledItemDelegate::DATA_ROLE, optionalStringToQtColor(action.getBackColor()));
+ item->setData(startingColumn + 2, Qt::DisplayRole, P2QSTRING(action.getSoundFilePath().get_value_or(std::string(""))));
+ item->setData(startingColumn + 2, Qt::EditRole, optionalSoundPathStringToQString(action.getSoundFilePath()));
+ item->setData(startingColumn + 3, QtCheckBoxStyledItemDelegate::DATA_ROLE, action.isSystemNotificationEnabled());
+ };
+
+ // contact highlights
+ ui_.userHighlightTreeWidget->clear();
+ for (const auto& contactHighlight : config.contactHighlights) {
+ auto item = new QTreeWidgetItem();
+ item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
+ item->setData(0, Qt::DisplayRole, P2QSTRING(contactHighlight.name));
+ item->setData(0, Qt::EditRole, P2QSTRING(contactHighlight.name));
+
+ setHighlightActionOnTreeWidgetItem(item, 1, contactHighlight.action);
+
+ ui_.userHighlightTreeWidget->addTopLevelItem(item);
+ }
+
+ // keyword highlights
+ ui_.keywordHighlightTreeWidget->clear();
+ for (const auto& keywordHighlight : config.keywordHighlights) {
+ auto item = new QTreeWidgetItem();
+ item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+ item->setData(0, Qt::DisplayRole, P2QSTRING(keywordHighlight.keyword));
+ item->setData(0, Qt::EditRole, P2QSTRING(keywordHighlight.keyword));
+ item->setData(1, QtCheckBoxStyledItemDelegate::DATA_ROLE, keywordHighlight.matchCaseSensitive);
+
+ setHighlightActionOnTreeWidgetItem(item, 2, keywordHighlight.action);
+
+ ui_.keywordHighlightTreeWidget->addTopLevelItem(item);
+ }
+
+ // general configuration
+ ui_.playSoundOnDirectMessagesCheckBox->setChecked(config.playSoundOnIncomingDirectMessages);
+ ui_.notificationOnDirectMessagesCheckBox->setChecked(config.showNotificationOnIncomingDirectMessages);
+ ui_.playSoundOnGroupMessagesCheckBox->setChecked(config.playSoundOnIncomingGroupchatMessages);
+ ui_.notificationOnGroupMessagesCheckBox->setChecked(config.showNotificationOnIncomingGroupchatMessages);
+
+ ui_.mentionTextColorColorButton->setColor(optionalStringToQtColor(config.ownMentionAction.getFrontColor()));
+ ui_.mentionBackgroundColorButton->setColor(optionalStringToQtColor(config.ownMentionAction.getBackColor()));
+ ui_.playSoundOnMentionCheckBox->setChecked(config.ownMentionAction.getSoundFilePath().is_initialized());
+ ui_.notificationOnMentionCheckBox->setChecked(config.ownMentionAction.isSystemNotificationEnabled());
+}
+
+}
diff --git a/Swift/QtUI/QtHighlightNotificationConfigDialog.h b/Swift/QtUI/QtHighlightNotificationConfigDialog.h
new file mode 100644
index 0000000..03044eb
--- /dev/null
+++ b/Swift/QtUI/QtHighlightNotificationConfigDialog.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2016-2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/Highlighting/HighlightConfiguration.h>
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h>
+
+#include <Swift/QtUI/ui_QtHighlightNotificationConfigDialog.h>
+
+namespace Swift {
+
+ class QtSettingsProvider;
+ class QtSuggestingJIDInput;
+
+ class QtHighlightNotificationConfigDialog : public QDialog, public HighlightEditorWindow {
+ Q_OBJECT
+
+ public:
+ QtHighlightNotificationConfigDialog(QtSettingsProvider* settings, QWidget* parent = nullptr);
+ virtual ~QtHighlightNotificationConfigDialog();
+
+ virtual void show();
+ virtual void setHighlightManager(HighlightManager* highlightManager);
+ virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions);
+
+ private:
+ HighlightConfiguration getHighlightConfigurationFromDialog() const;
+ void setHighlightConfigurationToDialog(const HighlightConfiguration& config);
+
+ private:
+ Ui::QtHighlightNotificationConfigDialog ui_;
+ QtSettingsProvider* settings_;
+ HighlightManager* highlightManager_ = nullptr;
+ QtSuggestingJIDInput* jid_ = nullptr;
+ };
+
+}
diff --git a/Swift/QtUI/QtHighlightNotificationConfigDialog.ui b/Swift/QtUI/QtHighlightNotificationConfigDialog.ui
new file mode 100644
index 0000000..7074ad8
--- /dev/null
+++ b/Swift/QtUI/QtHighlightNotificationConfigDialog.ui
@@ -0,0 +1,537 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtHighlightNotificationConfigDialog</class>
+ <widget class="QDialog" name="QtHighlightNotificationConfigDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>629</width>
+ <height>515</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Highlight and Notification Configuration</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="modal">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Highlight messages from these people</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="userHighlightTreeWidget">
+ <property name="tabKeyNavigation">
+ <bool>true</bool>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::MoveAction</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <property name="animated">
+ <bool>true</bool>
+ </property>
+ <property name="headerHidden">
+ <bool>false</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerCascadingSectionResizes">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="headerDefaultSectionSize">
+ <number>80</number>
+ </attribute>
+ <attribute name="headerHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="headerMinimumSectionSize">
+ <number>15</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>User</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Text color</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Background color</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Play sound</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Create notification</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>12</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="addUserHighlightPushButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="sizeIncrement">
+ <size>
+ <width>1</width>
+ <height>1</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>13</pointsize>
+ <weight>50</weight>
+ <italic>false</italic>
+ <bold>false</bold>
+ <underline>false</underline>
+ <strikeout>false</strikeout>
+ <kerning>true</kerning>
+ </font>
+ </property>
+ <property name="text">
+ <string>+</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeUserHighlightPushButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>-</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Highlight messages containing these keywords</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="keywordHighlightTreeWidget">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="tabKeyNavigation">
+ <bool>true</bool>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::MoveAction</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <property name="animated">
+ <bool>true</bool>
+ </property>
+ <column>
+ <property name="text">
+ <string>Keyword</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Match case sensitive</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Text color</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Background color</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Play sound</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Create notification</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>12</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="addKeywordHighlightPushButton">
+ <property name="maximumSize">
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>+</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeKeywordHighlightPushButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>-</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>General notification settings</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <item row="3" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="Swift::QtColorToolButton" name="mentionBackgroundColorButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Highlight background color on own mention</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="notificationOnGroupMessagesCheckBox">
+ <property name="text">
+ <string>Create notification on incoming group messages</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="notificationOnMentionCheckBox">
+ <property name="text">
+ <string>Create notification when my name is mentioned</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="playSoundOnDirectMessagesCheckBox">
+ <property name="text">
+ <string>Play sound on incoming direct messages</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="playSoundOnGroupMessagesCheckBox">
+ <property name="text">
+ <string>Play sound on incoming group messages</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="notificationOnDirectMessagesCheckBox">
+ <property name="text">
+ <string>Create notification on incoming direct messages</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="playSoundOnMentionCheckBox">
+ <property name="text">
+ <string>Play sound when my name is mentioned</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="Swift::QtColorToolButton" name="mentionTextColorColorButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Highlight text color on own mention</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
+ </property>
+ <property name="centerButtons">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>Swift::QtColorToolButton</class>
+ <extends>QToolButton</extends>
+ <header>QtColorToolButton.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>userHighlightTreeWidget</tabstop>
+ <tabstop>addUserHighlightPushButton</tabstop>
+ <tabstop>removeUserHighlightPushButton</tabstop>
+ <tabstop>keywordHighlightTreeWidget</tabstop>
+ <tabstop>addKeywordHighlightPushButton</tabstop>
+ <tabstop>removeKeywordHighlightPushButton</tabstop>
+ <tabstop>playSoundOnDirectMessagesCheckBox</tabstop>
+ <tabstop>notificationOnDirectMessagesCheckBox</tabstop>
+ <tabstop>playSoundOnGroupMessagesCheckBox</tabstop>
+ <tabstop>notificationOnGroupMessagesCheckBox</tabstop>
+ <tabstop>playSoundOnMentionCheckBox</tabstop>
+ <tabstop>notificationOnMentionCheckBox</tabstop>
+ <tabstop>mentionTextColorColorButton</tabstop>
+ <tabstop>mentionBackgroundColorButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QtHighlightNotificationConfigDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>QtHighlightNotificationConfigDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/Swift/QtUI/QtSoundSelectionStyledItemDelegate.cpp b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.cpp
new file mode 100644
index 0000000..3811004
--- /dev/null
+++ b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtSoundSelectionStyledItemDelegate.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QEvent>
+#include <QFileDialog>
+#include <QMenu>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QStyle>
+#include <QStyleOptionComboBox>
+
+#include <Swiften/Base/Log.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+
+QtSoundSelectionStyledItemDelegate::QtSoundSelectionStyledItemDelegate(QObject* parent) : QStyledItemDelegate(parent) {
+
+}
+
+void QtSoundSelectionStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
+ // draw item selected background
+ painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base());
+
+ auto editRoleString = index.data(Qt::EditRole).toString();
+
+ // draw combo box
+ QStyleOptionComboBox opt;
+ opt.rect = option.rect;
+ opt.rect.setHeight(opt.rect.height() + 2);
+ opt.state = QStyle::State_Active | QStyle::State_Enabled;
+ if (editRoleString.isEmpty()) {
+ opt.currentText = tr("No sound");
+ }
+ else if (editRoleString == "defaultSound") {
+ opt.currentText = tr("Default sound");
+ }
+ else {
+ opt.currentText = editRoleString;
+ }
+
+ painter->save();
+ QFont smallFont;
+ smallFont.setPointSize(smallFont.pointSize() - 3);
+ painter->setFont(smallFont);
+
+ QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &opt, painter);
+ QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &opt, painter);
+ painter->restore();
+}
+
+bool QtSoundSelectionStyledItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) {
+ if (event->type() == QEvent::MouseButtonRelease) {
+ auto mouseEvent = dynamic_cast<QMouseEvent*>(event);
+ assert(mouseEvent);
+ auto editRoleString = index.data(Qt::EditRole).toString();
+
+ auto popUpMenu = new QMenu();
+
+ auto noSound = popUpMenu->addAction(tr("No sound"));
+ auto defaultSound = popUpMenu->addAction(tr("Default sound"));
+ QAction* customSoundFile = nullptr;
+ QAction* selectedAction = nullptr;
+ if (editRoleString.isEmpty()) {
+ selectedAction = noSound;
+ }
+ else if (editRoleString == "defaultSound") {
+ selectedAction = defaultSound;
+ }
+ else {
+ customSoundFile = popUpMenu->addAction(editRoleString);
+ selectedAction = customSoundFile;
+ }
+ if (selectedAction) {
+ selectedAction->setCheckable(true);
+ selectedAction->setChecked(true);
+ }
+ auto chooseSoundFile = popUpMenu->addAction(tr("Choose sound fileā€¦"));
+
+ selectedAction = popUpMenu->exec(mouseEvent->globalPos(), selectedAction);
+
+ if (selectedAction == defaultSound) {
+ model->setData(index, "defaultSound", Qt::EditRole);
+ }
+ else if (customSoundFile && (selectedAction == customSoundFile)) {
+ model->setData(index, customSoundFile->text(), Qt::EditRole);
+ }
+ else if (selectedAction == noSound) {
+ model->setData(index, "", Qt::EditRole);
+ }
+ else if (selectedAction == chooseSoundFile) {
+ auto newPath = QFileDialog::getOpenFileName(0, tr("Choose notification sound file"), "", tr("WAV Files (*.wav)"));
+ if (!newPath.isEmpty()) {
+ model->setData(index, newPath, Qt::EditRole);
+ }
+ }
+
+ delete popUpMenu;
+ }
+ return true;
+}
+
+};
diff --git a/Swift/QtUI/QtSoundSelectionStyledItemDelegate.h b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.h
new file mode 100644
index 0000000..fabf668
--- /dev/null
+++ b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <QStyledItemDelegate>
+
+namespace Swift {
+
+class QtSoundSelectionStyledItemDelegate : public QStyledItemDelegate {
+ public:
+ QtSoundSelectionStyledItemDelegate(QObject* parent = nullptr);
+
+ virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const;
+
+ protected:
+ virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index);
+};
+
+}
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 16952a0..ece29ec 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -23,7 +23,7 @@
#include <Swift/QtUI/QtChatWindowFactory.h>
#include <Swift/QtUI/QtContactEditWindow.h>
#include <Swift/QtUI/QtFileTransferListWidget.h>
-#include <Swift/QtUI/QtHighlightEditor.h>
+#include <Swift/QtUI/QtHighlightNotificationConfigDialog.h>
#include <Swift/QtUI/QtHistoryWindow.h>
#include <Swift/QtUI/QtJoinMUCWindow.h>
#include <Swift/QtUI/QtLoginWindow.h>
@@ -169,7 +169,7 @@ WhiteboardWindow* QtUIFactory::createWhiteboardWindow(std::shared_ptr<Whiteboard
}
HighlightEditorWindow* QtUIFactory::createHighlightEditorWindow() {
- return new QtHighlightEditor(qtOnlySettings);
+ return new QtHighlightNotificationConfigDialog(qtOnlySettings);
}
BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() {
diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp
index 6fe9397..9aeef24 100644
--- a/Swift/QtUI/QtWebKitChatView.cpp
+++ b/Swift/QtUI/QtWebKitChatView.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.
*/
@@ -478,7 +478,7 @@ std::string QtWebKitChatView::addMessage(
std::shared_ptr<SecurityLabel> label,
const std::string& avatarPath,
const boost::posix_time::ptime& time) {
- return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, message.getFullMessageHighlightAction(), ChatSnippet::getDirection(message));
+ return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, message.getHighlightActionSender(), ChatSnippet::getDirection(message));
}
QString QtWebKitChatView::getHighlightSpanStart(const std::string& text, const std::string& background) {
@@ -494,7 +494,7 @@ QString QtWebKitChatView::getHighlightSpanStart(const std::string& text, const s
}
QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) {
- return getHighlightSpanStart(highlight.getTextColor(), highlight.getTextBackground());
+ return getHighlightSpanStart(highlight.getFrontColor().get_value_or(""), highlight.getBackColor().get_value_or(""));
}
QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) {
@@ -523,7 +523,7 @@ QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& messa
continue;
}
if ((highlightPart = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) {
- QString spanStart = getHighlightSpanStart(highlightPart->action.getTextColor(), highlightPart->action.getTextBackground());
+ QString spanStart = getHighlightSpanStart(highlightPart->action.getFrontColor().get_value_or(""), highlightPart->action.getBackColor().get_value_or(""));
result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + "</span>";
continue;
}
@@ -554,7 +554,7 @@ std::string QtWebKitChatView::addMessage(
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
- bool highlightWholeMessage = highlight.highlightWholeMessage() && highlight.getTextBackground() != "" && highlight.getTextColor() != "";
+ bool highlightWholeMessage = highlight.getFrontColor() || highlight.getBackColor();
QString highlightSpanStart = highlightWholeMessage ? getHighlightSpanStart(highlight) : "";
QString highlightSpanEnd = highlightWholeMessage ? "</span>" : "";
htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ;
@@ -572,7 +572,7 @@ std::string QtWebKitChatView::addMessage(
}
std::string QtWebKitChatView::addAction(const ChatWindow::ChatMessage& message, const std::string &senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) {
- return addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, message.getFullMessageHighlightAction(), ChatSnippet::getDirection(message));
+ return addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, message.getHighlightActionSender(), ChatSnippet::getDirection(message));
}
static QString encodeButtonArgument(const QString& str) {
@@ -835,11 +835,11 @@ std::string QtWebKitChatView::addSystemMessage(const ChatWindow::ChatMessage& me
}
void QtWebKitChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) {
- replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", message.getFullMessageHighlightAction());
+ replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", message.getHighlightActionSender());
}
void QtWebKitChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) {
- replaceMessage(chatMessageToHTML(message), id, time, "", message.getFullMessageHighlightAction());
+ replaceMessage(chatMessageToHTML(message), id, time, "", message.getHighlightActionSender());
}
void QtWebKitChatView::replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& id, ChatWindow::TimestampBehaviour timestampBehavior) {
@@ -876,8 +876,8 @@ void QtWebKitChatView::replaceMessage(const QString& message, const std::string&
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
- QString highlightSpanStart = highlight.highlightWholeMessage() ? getHighlightSpanStart(highlight) : "";
- QString highlightSpanEnd = highlight.highlightWholeMessage() ? "</span>" : "";
+ QString highlightSpanStart = (highlight.getFrontColor() || highlight.getBackColor()) ? getHighlightSpanStart(highlight) : "";
+ QString highlightSpanEnd = (highlight.getFrontColor() || highlight.getBackColor()) ? "</span>" : "";
messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd;
replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 3512120..69e99e7 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -145,9 +145,9 @@ sources = [
"QtContactEditWindow.cpp",
"QtContactEditWidget.cpp",
"QtSingleWindow.cpp",
- "QtHighlightEditor.cpp",
"QtColorToolButton.cpp",
"QtClosableLineEdit.cpp",
+ "QtHighlightNotificationConfigDialog.cpp",
"ChatSnippet.cpp",
"MessageSnippet.cpp",
"SystemMessageSnippet.cpp",
@@ -216,7 +216,10 @@ sources = [
"QtUpdateFeedSelectionDialog.cpp",
"Trellis/QtDynamicGridLayout.cpp",
"Trellis/QtDNDTabBar.cpp",
- "Trellis/QtGridSelectionDialog.cpp"
+ "Trellis/QtGridSelectionDialog.cpp",
+ "QtCheckBoxStyledItemDelegate.cpp",
+ "QtColorSelectionStyledItemDelegate.cpp",
+ "QtSoundSelectionStyledItemDelegate.cpp"
]
if env["PLATFORM"] == "win32" :
@@ -311,7 +314,7 @@ myenv.Uic4("QtAffiliationEditor.ui")
myenv.Uic4("QtJoinMUCWindow.ui")
myenv.Uic4("QtHistoryWindow.ui")
myenv.Uic4("QtConnectionSettings.ui")
-myenv.Uic4("QtHighlightEditor.ui")
+myenv.Uic4("QtHighlightNotificationConfigDialog.ui")
myenv.Uic4("QtBlockListEditorWindow.ui")
myenv.Uic4("QtSpellCheckerWindow.ui")
myenv.Uic4("QtUpdateFeedSelectionDialog.ui")