diff options
Diffstat (limited to 'Swift/Controllers')
27 files changed, 505 insertions, 206 deletions
| diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 2367761..9df7708 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -49,7 +49,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, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) +ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider)  	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {  	isInMUC_ = isInMUC;  	lastWasPresence_ = false; @@ -318,7 +318,7 @@ void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<  	boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();  	if (replace) {  		eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); -		replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time(), HighlightAction()); +		replaceMessage(body, myLastMessageUIID_, true, boost::posix_time::microsec_clock::universal_time(), HighlightAction());  	} else {  		myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction());  	} diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index f8b6d8b..8b1bb9a 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,5 +1,5 @@  /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith   * Licensed under the GNU General Public License v3.   * See Documentation/Licenses/GPLv3.txt for more information.   */ @@ -28,7 +28,7 @@ namespace Swift {  	class ChatController : public ChatControllerBase {  		public: -			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); +			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);  			virtual ~ChatController();  			virtual void setToJID(const JID& jid);  			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 23137dc..5363e0c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,5 +1,5 @@  /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith   * Licensed under the GNU General Public License v3.   * See Documentation/Licenses/GPLv3.txt for more information.   */ @@ -42,7 +42,7 @@  namespace Swift { -ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { +ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) {  	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);  	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));  	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); @@ -201,15 +201,15 @@ std::string ChatControllerBase::addMessage(const std::string& message, const std  	if (boost::starts_with(message, "/me ")) {  		return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);  	} else { -		return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); +		return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);  	}  } -void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { +void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight) {  	if (boost::starts_with(message, "/me ")) {  		chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);  	} else { -		chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message), id, time, highlight); +		chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), id, time, highlight);  	}  } @@ -280,7 +280,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m  			std::map<JID, std::string>::iterator lastMessage;  			lastMessage = lastMessagesUIID_.find(from);  			if (lastMessage != lastMessagesUIID_.end()) { -				replaceMessage(body, lastMessagesUIID_[from], timeStamp, highlight); +				replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), timeStamp, highlight);  			}  		}  		else { diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 7db94a4..cf0a4d2 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,5 +1,5 @@  /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith   * Licensed under the GNU General Public License v3.   * See Documentation/Licenses/GPLv3.txt for more information.   */ @@ -55,7 +55,7 @@ namespace Swift {  			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);  			void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);  			std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); -			void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); +			void replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight);  			virtual void setOnline(bool online);  			virtual void setEnabled(bool enabled);  			virtual void setToJID(const JID& jid) {toJID_ = jid;} @@ -70,7 +70,7 @@ namespace Swift {  			boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC;  		protected: -			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); +			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);  			/**  			 * Pass the Message appended, and the stanza used to send it. @@ -127,7 +127,7 @@ namespace Swift {  			HistoryController* historyController_;  			MUCRegistry* mucRegistry_;  			Highlighter* highlighter_; -			ChatMessageParser* chatMessageParser_; +			boost::shared_ptr<ChatMessageParser> chatMessageParser_;  			AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;  			UIEventStream* eventStream_;  	}; diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp index 698b766..09d93ac 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.cpp +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -20,13 +20,13 @@  namespace Swift { -	ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons) : emoticons_(emoticons) { - +	ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) +	: emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) {  	}  	typedef std::pair<std::string, std::string> StringPair; -	ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body) { +	ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, bool senderIsSelf) {  		ChatWindow::ChatMessage parsedMessage;  		std::string remaining = body;  		/* Parse one, URLs */ @@ -51,8 +51,21 @@ namespace Swift {  				}  			}  		} -		 +		/* do emoticon substitution */ +		parsedMessage = emoticonHighlight(parsedMessage); + +		if (!senderIsSelf) { /* do not highlight our own messsages */ +			/* do word-based color highlighting */ +			parsedMessage = splitHighlight(parsedMessage); +		} + +		return parsedMessage; +	} + +	ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) +	{ +		ChatWindow::ChatMessage parsedMessage = message;  		std::string regexString;  		/* Parse two, emoticons */ @@ -124,4 +137,58 @@ namespace Swift {  		}  		return parsedMessage;  	} + +	ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message) +	{ +		ChatWindow::ChatMessage parsedMessage = message; + +		for (size_t i = 0; i < highlightRules_->getSize(); ++i) { +			const HighlightRule& rule = highlightRules_->getRule(i); +			if (rule.getMatchMUC() && !mucMode_) { +				continue; /* this rule only applies to MUC's, and this is a CHAT */ +			} else if (rule.getMatchChat() && mucMode_) { +				continue; /* this rule only applies to CHAT's, and this is a MUC */ +			} +			foreach(const boost::regex ®ex, rule.getKeywordRegex()) { +				ChatWindow::ChatMessage newMessage; +				foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { +					boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; +					if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { +						try { +							boost::match_results<std::string::const_iterator> match; +							const std::string& text = textPart->text; +							std::string::const_iterator start = text.begin(); +							while (regex_search(start, text.end(), match, regex)) { +								std::string::const_iterator matchStart = match[0].first; +								std::string::const_iterator matchEnd = match[0].second; +								if (start != matchStart) { +									/* If we're skipping over plain text since the previous emoticon, record it as plain text */ +									newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); +								} +								boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>(); +								highlightPart->text = match.str(); +								highlightPart->foregroundColor = rule.getAction().getTextColor(); +								highlightPart->backgroundColor = rule.getAction().getTextBackground(); +								newMessage.append(highlightPart); +								start = matchEnd; +							} +							if (start != text.end()) { +								/* If there's plain text after the last emoticon, record it */ +								newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); +							} +						} +						catch (std::runtime_error) { +							/* Basically too expensive to compute the regex results and it gave up, so pass through as text */ +							newMessage.append(part); +						} +					} else { +						newMessage.append(part); +					} +				} +				parsedMessage = newMessage; +			} +		} + +		return parsedMessage; +	}  } diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h index c9b9456..cff4ffa 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.h +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -1,5 +1,5 @@  /* - * Copyright (c) 2013 Kevin Smith + * Copyright (c) 2013-2014 Kevin Smith   * Licensed under the GNU General Public License v3.   * See Documentation/Licenses/GPLv3.txt for more information.   */ @@ -14,10 +14,13 @@ namespace Swift {  	class ChatMessageParser {  		public: -			ChatMessageParser(const std::map<std::string, std::string>& emoticons); -			ChatWindow::ChatMessage parseMessageBody(const std::string& body); +			ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false); +			ChatWindow::ChatMessage parseMessageBody(const std::string& body, bool senderIsSelf = false);  		private: +			ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); +			ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage);  			std::map<std::string, std::string> emoticons_; - +			HighlightRulesListPtr highlightRules_; +			bool mucMode_;  	};  } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 1698b4a..8a077d1 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -145,6 +145,7 @@ ChatsManager::ChatsManager(  			historyController_(historyController),  			whiteboardManager_(whiteboardManager),  			highlightManager_(highlightManager), +			emoticons_(emoticons),  			clientBlockListManager_(clientBlockListManager),  			inviteUserSearchController_(inviteUserSearchController),  			vcardManager_(vcardManager) { @@ -161,7 +162,6 @@ ChatsManager::ChatsManager(  	uiEventStream_ = uiEventStream;  	mucBookmarkManager_ = NULL;  	profileSettings_ = profileSettings; -	chatMessageParser_ = new ChatMessageParser(emoticons);  	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));  	uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1)); @@ -208,7 +208,6 @@ ChatsManager::~ChatsManager() {  	}  	delete mucBookmarkManager_;  	delete mucSearchController_; -	delete chatMessageParser_;  	delete autoAcceptMUCInviteDecider_;  } @@ -697,7 +696,8 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact)  ChatController* ChatsManager::createNewChatController(const JID& contact) {  	assert(chatControllers_.find(contact) == chatControllers_.end()); -	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, autoAcceptMUCInviteDecider_); +	boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */ +	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_);  	chatControllers_[contact] = controller;  	controller->setAvailableServerFeatures(serverDiscoInfo_);  	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); @@ -781,7 +781,8 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti  		if (reuseChatwindow) {  			chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);  		} -		controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_); +		boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */ +		controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_);  		if (chatWindowFactoryAdapter) {  			/* The adapters are only passed to chat windows, which are deleted in their  			 * controllers' dtor, which are deleted in ChatManager's dtor. The adapters diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 88a0986..41435d9 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -174,8 +174,8 @@ namespace Swift {  			HistoryController* historyController_;  			WhiteboardManager* whiteboardManager_;  			HighlightManager* highlightManager_; +			std::map<std::string, std::string> emoticons_;  			ClientBlockListManager* clientBlockListManager_; -			ChatMessageParser* chatMessageParser_;  			JID localMUCServiceJID_;  			boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_;  			AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index d09bc3d..b467227 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -71,7 +71,7 @@ MUCController::MUCController (  		HistoryController* historyController,  		MUCRegistry* mucRegistry,  		HighlightManager* highlightManager, -		ChatMessageParser* chatMessageParser, +		boost::shared_ptr<ChatMessageParser> chatMessageParser,  		bool isImpromptu,  		AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,  		VCardManager* vcardManager) : diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index feffaba..b5b5837 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -50,7 +50,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* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager); +			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager);  			virtual ~MUCController();  			boost::signal<void ()> onUserLeft;  			boost::signal<void ()> onUserJoined; diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp index 0a14303..5dca63a 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -48,28 +48,136 @@ public:  		CPPUNIT_ASSERT_EQUAL(path, part->imagePath);  	} +	void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { +		boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); +		CPPUNIT_ASSERT_EQUAL(text, part->text); +	} +  	void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {  		boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]);  		CPPUNIT_ASSERT_EQUAL(text, part->target);  	} +	static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) +	{ +		HighlightRule rule; +		std::vector<std::string> keywords; +		keywords.push_back(keyword); +		rule.setKeywords(keywords); +		rule.setMatchCase(matchCase); +		rule.setMatchWholeWords(matchWholeWord); +		rule.setMatchChat(true); +		return rule; +	} + +	static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) +	{ +		boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); +		list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); +		return list; +	} + +	static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) +	{ +		boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); +		list->addRule(rule1); +		list->addRule(rule2); +		return list; +	} +  	void testFullBody() { -		ChatMessageParser testling(emoticons_); -		ChatWindow::ChatMessage result = testling.parseMessageBody(":) shiny :( :) http://wonderland.lit/blah http://denmark.lit boom boom"); +		const std::string no_special_message = "a message with no special content"; +		ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); +		ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); +		assertText(result, 0, no_special_message); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); +		result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom");  		assertEmoticon(result, 0, smile1_, smile1Path_);  		assertText(result, 1, " shiny ");  		assertEmoticon(result, 2, smile2_, smile2Path_);  		assertText(result, 3, " "); -		assertEmoticon(result, 4, smile1_, smile1Path_); +		assertHighlight(result, 4, "trigger");  		assertText(result, 5, " "); -		assertURL(result, 6, "http://wonderland.lit/blah"); +		assertEmoticon(result, 6, smile1_, smile1Path_);  		assertText(result, 7, " "); -		assertURL(result, 8, "http://denmark.lit"); -		assertText(result, 9, " boom boom"); +		assertURL(result, 8, "http://wonderland.lit/blah"); +		assertText(result, 9, " "); +		assertURL(result, 10, "http://denmark.lit"); +		assertText(result, 11, " boom boom"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); +		result = testling.parseMessageBody("testtriggermessage"); +		assertText(result, 0, "test"); +		assertHighlight(result, 1, "trigger"); +		assertText(result, 2, "message"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true)); +		result = testling.parseMessageBody("testtriggermessage"); +		assertText(result, 0, "testtriggermessage"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false)); +		result = testling.parseMessageBody("TrIgGeR"); +		assertText(result, 0, "TrIgGeR"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); +		result = testling.parseMessageBody("TrIgGeR"); +		assertHighlight(result, 0, "TrIgGeR"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); +		result = testling.parseMessageBody("partialTrIgGeRmatch"); +		assertText(result, 0, "partial"); +		assertHighlight(result, 1, "TrIgGeR"); +		assertText(result, 2, "match"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); +		result = testling.parseMessageBody("zero one two three"); +		assertText(result, 0, "zero "); +		assertHighlight(result, 1, "one"); +		assertText(result, 2, " two "); +		assertHighlight(result, 3, "three"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); +		result = testling.parseMessageBody("zero oNe two tHrEe"); +		assertText(result, 0, "zero "); +		assertHighlight(result, 1, "oNe"); +		assertText(result, 2, " two "); +		assertHighlight(result, 3, "tHrEe"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false))); +		result = testling.parseMessageBody("zero oNe two tHrEe"); +		assertText(result, 0, "zero "); +		assertHighlight(result, 1, "oNe"); +		assertText(result, 2, " two tHrEe"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false))); +		result = testling.parseMessageBody("zero oNe two tHrEe"); +		assertText(result, 0, "zero oNe two tHrEe"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); +		result = testling.parseMessageBody("zeroonetwothree"); +		assertText(result, 0, "zero"); +		assertHighlight(result, 1, "one"); +		assertText(result, 2, "two"); +		assertHighlight(result, 3, "three"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false))); +		result = testling.parseMessageBody("zeroOnEtwoThReE"); +		assertText(result, 0, "zeroOnEtwo"); +		assertHighlight(result, 1, "ThReE"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false))); +		result = testling.parseMessageBody("zeroonetwothree"); +		assertText(result, 0, "zeroonetwo"); +		assertHighlight(result, 1, "three"); + +		testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true))); +		result = testling.parseMessageBody("zeroonetwothree"); +		assertText(result, 0, "zeroonetwothree");  	}  	void testOneEmoticon() { -		ChatMessageParser testling(emoticons_); +		ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());  		ChatWindow::ChatMessage result = testling.parseMessageBody(" :) ");  		assertText(result, 0, " ");  		assertEmoticon(result, 1, smile1_, smile1Path_); @@ -78,26 +186,26 @@ public:  	void testBareEmoticon() { -		ChatMessageParser testling(emoticons_); +		ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());  		ChatWindow::ChatMessage result = testling.parseMessageBody(":)");  		assertEmoticon(result, 0, smile1_, smile1Path_);  	}  	void testHiddenEmoticon() { -		ChatMessageParser testling(emoticons_); +		ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());  		ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a");  		assertText(result, 0, "b:)a");  	}  	void testEndlineEmoticon() { -		ChatMessageParser testling(emoticons_); +		ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());  		ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)");  		assertText(result, 0, "Lazy");  		assertEmoticon(result, 1, smile1_, smile1Path_);  	}  	void testBoundedEmoticons() { -		ChatMessageParser testling(emoticons_); +		ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());  		ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:(");  		assertEmoticon(result, 0, smile1_, smile1Path_);  		assertText(result, 1, "Lazy"); @@ -105,14 +213,13 @@ public:  	}  	void testEmoticonParenthesis() { -		ChatMessageParser testling(emoticons_); +		ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());  		ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))");  		assertText(result, 0, "(Like this ");  		assertEmoticon(result, 1, smile1_, smile1Path_);  		assertText(result, 2, ")");  	} -  private:  	std::map<std::string, std::string> emoticons_;  	std::string smile1_; @@ -122,4 +229,3 @@ private:  };  CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); - diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index 7268878..bb22e43 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,5 +1,5 @@  /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith   * Licensed under the GNU General Public License v3.   * See Documentation/Licenses/GPLv3.txt for more information.   */ @@ -81,7 +81,7 @@ public:  		highlightManager_ = new HighlightManager(settings_);  		muc_ = boost::make_shared<MockMUC>(mucJID_);  		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); -		chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>()); +		chatMessageParser_ = boost::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true);  		vcardStorage_ = new VCardMemoryStorage(crypto_.get());  		vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);  		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL, vcardManager_); @@ -105,7 +105,6 @@ public:  		delete iqChannel_;  		delete mucRegistry_;  		delete avatarManager_; -		delete chatMessageParser_;  	}  	void finishJoin() { @@ -429,7 +428,7 @@ private:  	DummyEntityCapsProvider* entityCapsProvider_;  	DummySettingsProvider* settings_;  	HighlightManager* highlightManager_; -	ChatMessageParser* chatMessageParser_; +	boost::shared_ptr<ChatMessageParser> chatMessageParser_;  	boost::shared_ptr<CryptoProvider> crypto_;  	VCardManager* vcardManager_;  	VCardMemoryStorage* vcardStorage_; diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h index bfbed74..de1f201 100644 --- a/Swift/Controllers/HighlightAction.h +++ b/Swift/Controllers/HighlightAction.h @@ -4,10 +4,19 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #pragma once  #include <string> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> +  namespace Swift {  	class HighlightRule; @@ -19,21 +28,33 @@ namespace Swift {  			bool highlightText() const { return highlightText_; }  			void setHighlightText(bool highlightText); +			/** +			* Gets the foreground highlight color. If the string is empty, assume a default color. +			*/  			const std::string& getTextColor() const { return textColor_; }  			void setTextColor(const std::string& textColor) { textColor_ = textColor; } +			/** +			* Gets the background highlight color. If the string is empty, assume a default color. +			*/  			const std::string& getTextBackground() const { return textBackground_; }  			void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; }  			bool playSound() const { return playSound_; }  			void setPlaySound(bool playSound); +			/** +			* Gets the sound filename. If the string is empty, assume a default sound file. +			*/  			const std::string& getSoundFile() const { return soundFile_; }  			void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; }  			bool isEmpty() const { return !highlightText_ && !playSound_; }  		private: +			friend class boost::serialization::access; +			template<class Archive> void serialize(Archive & ar, const unsigned int version); +  			bool highlightText_;  			std::string textColor_;  			std::string textBackground_; @@ -42,4 +63,14 @@ namespace Swift {  			std::string soundFile_;  	}; +	template<class Archive> +	void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/) +	{ +		ar & highlightText_; +		ar & textColor_; +		ar & textBackground_; +		ar & playSound_; +		ar & soundFile_; +	} +  } diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/HighlightEditorController.cpp index 899e4bb..38007f0 100644 --- a/Swift/Controllers/HighlightEditorController.cpp +++ b/Swift/Controllers/HighlightEditorController.cpp @@ -4,36 +4,52 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #include <boost/bind.hpp>  #include <Swift/Controllers/HighlightEditorController.h> -#include <Swift/Controllers/UIInterfaces/HighlightEditorWidget.h> -#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h>  #include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h>  #include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> +#include <Swift/Controllers/ContactSuggester.h>  namespace Swift { -HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager) : highlightEditorWidgetFactory_(highlightEditorWidgetFactory), highlightEditorWidget_(NULL), highlightManager_(highlightManager) +HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager) +: highlightEditorWindowFactory_(highlightEditorWindowFactory), highlightEditorWindow_(NULL), highlightManager_(highlightManager), contactSuggester_(0)  {  	uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1));  }  HighlightEditorController::~HighlightEditorController()  { -	delete highlightEditorWidget_; -	highlightEditorWidget_ = NULL; +	delete highlightEditorWindow_; +	highlightEditorWindow_ = NULL;  }  void HighlightEditorController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent)  {  	boost::shared_ptr<RequestHighlightEditorUIEvent> event = boost::dynamic_pointer_cast<RequestHighlightEditorUIEvent>(rawEvent);  	if (event) { -		if (!highlightEditorWidget_) { -			highlightEditorWidget_ = highlightEditorWidgetFactory_->createHighlightEditorWidget(); -			highlightEditorWidget_->setHighlightManager(highlightManager_); +		if (!highlightEditorWindow_) { +			highlightEditorWindow_ = highlightEditorWindowFactory_->createHighlightEditorWindow(); +			highlightEditorWindow_->setHighlightManager(highlightManager_); +			highlightEditorWindow_->onContactSuggestionsRequested.connect(boost::bind(&HighlightEditorController::handleContactSuggestionsRequested, this, _1));  		} -		highlightEditorWidget_->show(); +		highlightEditorWindow_->show(); +	} +} + +void HighlightEditorController::handleContactSuggestionsRequested(const std::string& text) +{ +	if (contactSuggester_) { +		highlightEditorWindow_->setContactSuggestions(contactSuggester_->getSuggestions(text));  	}  } diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/HighlightEditorController.h index 3868251..54322e2 100644 --- a/Swift/Controllers/HighlightEditorController.h +++ b/Swift/Controllers/HighlightEditorController.h @@ -4,6 +4,12 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #pragma once  #include <boost/shared_ptr.hpp> @@ -14,25 +20,29 @@ namespace Swift {  	class UIEventStream; -	class HighlightEditorWidgetFactory; -	class HighlightEditorWidget; +	class HighlightEditorWindowFactory; +	class HighlightEditorWindow;  	class HighlightManager; +	class ContactSuggester;  	class HighlightEditorController {  		public: -			HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager); +			HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager);  			~HighlightEditorController();  			HighlightManager* getHighlightManager() const { return highlightManager_; } +			void setContactSuggester(ContactSuggester *suggester) { contactSuggester_ = suggester; }  		private:  			void handleUIEvent(boost::shared_ptr<UIEvent> event); +			void handleContactSuggestionsRequested(const std::string& text);  		private: -			HighlightEditorWidgetFactory* highlightEditorWidgetFactory_; -			HighlightEditorWidget* highlightEditorWidget_; +			HighlightEditorWindowFactory* highlightEditorWindowFactory_; +			HighlightEditorWindow* highlightEditorWindow_;  			HighlightManager* highlightManager_; +			ContactSuggester* contactSuggester_;  	};  } diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp index 7ab578e..eac562f 100644 --- a/Swift/Controllers/HighlightManager.cpp +++ b/Swift/Controllers/HighlightManager.cpp @@ -4,12 +4,21 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #include <cassert>  #include <boost/algorithm/string.hpp>  #include <boost/regex.hpp>  #include <boost/bind.hpp>  #include <boost/numeric/conversion/cast.hpp> +#include <boost/serialization/vector.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp>  #include <Swiften/Base/foreach.h>  #include <Swift/Controllers/HighlightManager.h> @@ -40,6 +49,7 @@ HighlightManager::HighlightManager(SettingsProvider* settings)  	: settings_(settings)  	, storingSettings_(false)  { +	rules_ = boost::make_shared<HighlightRulesList>();  	loadSettings();  	settings_->onSettingChanged.connect(boost::bind(&HighlightManager::handleSettingChanged, this, _1));  } @@ -51,40 +61,12 @@ void HighlightManager::handleSettingChanged(const std::string& settingPath)  	}  } -void HighlightManager::loadSettings() -{ -	std::string highlightRules = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES); -	if (highlightRules == "@") { -		rules_ = getDefaultRules(); -	} else { -		rules_ = rulesFromString(highlightRules); -	} -} -  std::string HighlightManager::rulesToString() const  { -	std::string s; -	foreach (HighlightRule r, rules_) { -		s += r.toString() + '\f'; -	} -	if (s.size()) { -		s.erase(s.end() - 1); -	} -	return s; -} - -std::vector<HighlightRule> HighlightManager::rulesFromString(const std::string& rulesString) -{ -	std::vector<HighlightRule> rules; -	std::string s(rulesString); -	typedef boost::split_iterator<std::string::iterator> split_iterator; -	for (split_iterator it = boost::make_split_iterator(s, boost::first_finder("\f")); it != split_iterator(); ++it) { -		HighlightRule r = HighlightRule::fromString(boost::copy_range<std::string>(*it)); -		if (!r.isEmpty()) { -			rules.push_back(r); -		} -	} -	return rules; +	std::stringstream stream; +	boost::archive::text_oarchive archive(stream); +	archive << rules_->list_; +	return stream.str();  }  std::vector<HighlightRule> HighlightManager::getDefaultRules() @@ -97,38 +79,48 @@ std::vector<HighlightRule> HighlightManager::getDefaultRules()  	return rules;  } -void HighlightManager::storeSettings() -{ -	storingSettings_ = true;	// don't reload settings while saving -	settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString()); -	storingSettings_ = false; -} -  HighlightRule HighlightManager::getRule(int index) const  { -	assert(index >= 0 && static_cast<size_t>(index) < rules_.size()); -	return rules_[static_cast<size_t>(index)]; +	assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); +	return rules_->getRule(static_cast<size_t>(index));  }  void HighlightManager::setRule(int index, const HighlightRule& rule)  { -	assert(index >= 0 && static_cast<size_t>(index) < rules_.size()); -	rules_[static_cast<size_t>(index)] = rule; -	storeSettings(); +	assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); +	rules_->list_[static_cast<size_t>(index)] = rule;  }  void HighlightManager::insertRule(int index, const HighlightRule& rule)  { -	assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_.size()); -	rules_.insert(rules_.begin() + index, rule); -	storeSettings(); +	assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_->getSize()); +	rules_->list_.insert(rules_->list_.begin() + index, rule);  }  void HighlightManager::removeRule(int index)  { -	assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_.size()); -	rules_.erase(rules_.begin() + index); -	storeSettings(); +	assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_->getSize()); +	rules_->list_.erase(rules_->list_.begin() + index); +} + +void HighlightManager::storeSettings() +{ +	storingSettings_ = true;	// don't reload settings while saving +	settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString()); +	storingSettings_ = false; +} + +void HighlightManager::loadSettings() +{ +	std::string rulesString = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES); +	std::stringstream stream; +	stream << rulesString; +	try { +		boost::archive::text_iarchive archive(stream); +		archive >> rules_->list_; +	} catch (boost::archive::archive_exception&) { +		rules_->list_ = getDefaultRules(); +	}  }  Highlighter* HighlightManager::createHighlighter() diff --git a/Swift/Controllers/HighlightManager.h b/Swift/Controllers/HighlightManager.h index d195d05..3da72eb 100644 --- a/Swift/Controllers/HighlightManager.h +++ b/Swift/Controllers/HighlightManager.h @@ -4,6 +4,12 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #pragma once  #include <vector> @@ -19,15 +25,32 @@ namespace Swift {  	class HighlightManager {  		public: + +			class HighlightRulesList { +			public: +				friend class HighlightManager; +				size_t getSize() const { return list_.size(); } +				const HighlightRule& getRule(const size_t index) const { return list_[index]; } +				void addRule(const HighlightRule &rule) { list_.push_back(rule); } +				void combineRules(const HighlightRulesList &rhs) { +					list_.insert(list_.end(), rhs.list_.begin(), rhs.list_.end()); +				} +			private: +				std::vector<HighlightRule> list_; +			}; +  			HighlightManager(SettingsProvider* settings);  			Highlighter* createHighlighter(); -			const std::vector<HighlightRule>& getRules() const { return rules_; } +			boost::shared_ptr<const HighlightManager::HighlightRulesList> getRules() const { return rules_; } +  			HighlightRule getRule(int index) const;  			void setRule(int index, const HighlightRule& rule);  			void insertRule(int index, const HighlightRule& rule);  			void removeRule(int index); +			void storeSettings(); +			void loadSettings();  			boost::signal<void (const HighlightAction&)> onHighlight; @@ -35,15 +58,14 @@ namespace Swift {  			void handleSettingChanged(const std::string& settingPath);  			std::string rulesToString() const; -			static std::vector<HighlightRule> rulesFromString(const std::string&);  			static std::vector<HighlightRule> getDefaultRules();  			SettingsProvider* settings_;  			bool storingSettings_; -			void storeSettings(); -			void loadSettings(); -			std::vector<HighlightRule> rules_; +			boost::shared_ptr<HighlightManager::HighlightRulesList> rules_;  	}; +	typedef boost::shared_ptr<const HighlightManager::HighlightRulesList> HighlightRulesListPtr; +  } diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp index 9ca7d86..f1a5235 100644 --- a/Swift/Controllers/HighlightRule.cpp +++ b/Swift/Controllers/HighlightRule.cpp @@ -4,6 +4,12 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #include <algorithm>  #include <boost/algorithm/string.hpp>  #include <boost/lambda/lambda.hpp> @@ -56,57 +62,6 @@ bool HighlightRule::boolFromString(const std::string& s)  	return s == "1";  } -std::string HighlightRule::toString() const -{ -	std::vector<std::string> v; -	v.push_back(boost::join(senders_, "\t")); -	v.push_back(boost::join(keywords_, "\t")); -	v.push_back(boolToString(nickIsKeyword_)); -	v.push_back(boolToString(matchChat_)); -	v.push_back(boolToString(matchMUC_)); -	v.push_back(boolToString(matchCase_)); -	v.push_back(boolToString(matchWholeWords_)); -	v.push_back(boolToString(action_.highlightText())); -	v.push_back(action_.getTextColor()); -	v.push_back(action_.getTextBackground()); -	v.push_back(boolToString(action_.playSound())); -	v.push_back(action_.getSoundFile()); -	return boost::join(v, "\n"); -} - -HighlightRule HighlightRule::fromString(const std::string& s) -{ -	std::vector<std::string> v; -	boost::split(v, s, boost::is_any_of("\n")); - -	HighlightRule r; -	size_t i = 0; -	try { -		boost::split(r.senders_, v.at(i++), boost::is_any_of("\t")); -		r.senders_.erase(std::remove_if(r.senders_.begin(), r.senders_.end(), boost::lambda::_1 == ""), r.senders_.end()); -		boost::split(r.keywords_, v.at(i++), boost::is_any_of("\t")); -		r.keywords_.erase(std::remove_if(r.keywords_.begin(), r.keywords_.end(), boost::lambda::_1 == ""), r.keywords_.end()); -		r.nickIsKeyword_ = boolFromString(v.at(i++)); -		r.matchChat_ = boolFromString(v.at(i++)); -		r.matchMUC_ = boolFromString(v.at(i++)); -		r.matchCase_ = boolFromString(v.at(i++)); -		r.matchWholeWords_ = boolFromString(v.at(i++)); -		r.action_.setHighlightText(boolFromString(v.at(i++))); -		r.action_.setTextColor(v.at(i++)); -		r.action_.setTextBackground(v.at(i++)); -		r.action_.setPlaySound(boolFromString(v.at(i++))); -		r.action_.setSoundFile(v.at(i++)); -	} -	catch (std::out_of_range) { -		// this may happen if more properties are added to HighlightRule object, etc... -		// in such case, default values (set by default constructor) will be used -	} - -	r.updateRegex(); - -	return r; -} -  bool HighlightRule::isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType messageType) const  {  	if ((messageType == HighlightRule::ChatMessage && matchChat_) || (messageType == HighlightRule::MUCMessage && matchMUC_)) { @@ -114,13 +69,6 @@ bool HighlightRule::isMatch(const std::string& body, const std::string& sender,  		bool matchesKeyword = keywords_.empty() && (nick.empty() || !nickIsKeyword_);  		bool matchesSender = senders_.empty(); -		foreach (const boost::regex & rx, keywordRegex_) { -			if (boost::regex_search(body, rx)) { -				matchesKeyword = true; -				break; -			} -		} -  		if (!matchesKeyword && nickIsKeyword_ && !nick.empty()) {  			if (boost::regex_search(body, regexFromString(nick))) {  				matchesKeyword = true; diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h index 1abfa5a..ae1a3d3 100644 --- a/Swift/Controllers/HighlightRule.h +++ b/Swift/Controllers/HighlightRule.h @@ -4,12 +4,20 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #pragma once  #include <vector>  #include <string>  #include <boost/regex.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp>  #include <Swift/Controllers/HighlightAction.h> @@ -26,14 +34,13 @@ namespace Swift {  			const HighlightAction& getAction() const { return action_; }  			HighlightAction& getAction() { return action_; } -			static HighlightRule fromString(const std::string&); -			std::string toString() const; -  			const std::vector<std::string>& getSenders() const { return senders_; }  			void setSenders(const std::vector<std::string>&); +			const std::vector<boost::regex>& getSenderRegex() const { return senderRegex_; }  			const std::vector<std::string>& getKeywords() const { return keywords_; }  			void setKeywords(const std::vector<std::string>&); +			const std::vector<boost::regex>& getKeywordRegex() const { return keywordRegex_; }  			bool getNickIsKeyword() const { return nickIsKeyword_; }  			void setNickIsKeyword(bool); @@ -53,6 +60,9 @@ namespace Swift {  			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&); @@ -74,4 +84,18 @@ namespace Swift {  			HighlightAction action_;  	}; +	template<class Archive> +	void HighlightRule::serialize(Archive& ar, const unsigned int /*version*/) +	{ +		ar & senders_; +		ar & keywords_; +		ar & nickIsKeyword_; +		ar & matchChat_; +		ar & matchMUC_; +		ar & matchCase_; +		ar & matchWholeWords_; +		ar & action_; +		updateRegex(); +	} +  } diff --git a/Swift/Controllers/Highlighter.cpp b/Swift/Controllers/Highlighter.cpp index 754641a..efeeb6b 100644 --- a/Swift/Controllers/Highlighter.cpp +++ b/Swift/Controllers/Highlighter.cpp @@ -4,6 +4,12 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #include <Swiften/Base/foreach.h>  #include <Swift/Controllers/Highlighter.h>  #include <Swift/Controllers/HighlightManager.h> @@ -24,9 +30,11 @@ void Highlighter::setMode(Mode mode)  HighlightAction Highlighter::findAction(const std::string& body, const std::string& sender) const  { -	foreach (const HighlightRule & r, manager_->getRules()) { -		if (r.isMatch(body, sender, nick_, messageType_)) { -			return r.getAction(); +	HighlightRulesListPtr rules = manager_->getRules(); +	for (size_t i = 0; i < rules->getSize(); ++i) { +		const HighlightRule& rule = rules->getRule(i); +		if (rule.isMatch(body, sender, nick_, messageType_)) { +			return rule.getAction();  		}  	} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 79b7502..a16cbe7 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -1,11 +1,5 @@  /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -/* - * Copyright (c) 2013 Remko Tronçon + * Copyright (c) 2010-2014 Kevin Smith and Remko Tronçon   * Licensed under the GNU General Public License v3.   * See Documentation/Licenses/GPLv3.txt for more information.   */ @@ -369,6 +363,7 @@ void MainController::handleConnected() {  		contactSuggesterWithoutRoster_->addContactProvider(chatsManager_);  		contactSuggesterWithRoster_->addContactProvider(chatsManager_);  		contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_); +		highlightEditorController_->setContactSuggester(contactSuggesterWithoutRoster_);  		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));  		chatsManager_->setAvatarManager(client_->getAvatarManager()); diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 4c71268..5ebbdd3 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -62,6 +62,7 @@ if env["SCONS_STAGE"] == "build" :  			"UIEvents/UIEvent.cpp",  			"UIInterfaces/XMLConsoleWidget.cpp",  			"UIInterfaces/ChatListWindow.cpp", +			"UIInterfaces/HighlightEditorWindow.cpp",  			"PreviousStatusStore.cpp",  			"ProfileSettingsProvider.cpp",  			"Settings/SettingsProviderHierachy.cpp", diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp new file mode 100644 index 0000000..f90903b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> + +namespace Swift { + +HighlightEditorWindow::HighlightEditorWindow() +{ +} + +HighlightEditorWindow::~HighlightEditorWindow() +{ +} + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h new file mode 100644 index 0000000..83ae959 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <vector> +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class HighlightManager; + +class HighlightEditorWindow { +public: +	HighlightEditorWindow(); +	virtual ~HighlightEditorWindow(); +	virtual void show() = 0; +	virtual void setHighlightManager(HighlightManager *highlightManager) = 0; +	virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) = 0; + +public: +	boost::signal<void (const std::string&)> onContactSuggestionsRequested; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h new file mode 100644 index 0000000..e0aaaef --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { +	class HighlightEditorWindow; + +	class HighlightEditorWindowFactory { +	public : +		virtual ~HighlightEditorWindowFactory() {} + +		virtual HighlightEditorWindow* createHighlightEditorWindow() = 0; +	}; +} diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 990dc98..54fa7ce 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -1,5 +1,5 @@  /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon   * Licensed under the GNU General Public License v3.   * See Documentation/Licenses/GPLv3.txt for more information.   */ @@ -21,7 +21,7 @@  #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h>  #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h>  #include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> -#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h>  #include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h>  namespace Swift { @@ -41,7 +41,7 @@ namespace Swift {  			public AdHocCommandWindowFactory,  			public FileTransferListWidgetFactory,  			public WhiteboardWindowFactory, -			public HighlightEditorWidgetFactory, +			public HighlightEditorWindowFactory,  			public BlockListEditorWidgetFactory {  		public:  			virtual ~UIFactory() {} diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp index ec81227..c988b8d 100644 --- a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp +++ b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp @@ -4,6 +4,12 @@   * See Documentation/Licenses/BSD-simplified.txt for more information.   */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +  #include <vector>  #include <string> @@ -34,7 +40,7 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			keywords.push_back("keyword1");  			keywords.push_back("KEYWORD2"); -			std::vector<std::string>senders; +			std::vector<std::string> senders;  			senders.push_back("sender1");  			senders.push_back("SENDER2"); @@ -157,20 +163,20 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); -			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::MUCMessage), false);  			CPPUNIT_ASSERT_EQUAL(keywordRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "sender contains keyword1", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), true); -			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), false); +			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), true); -			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), true); -			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), false); +			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), false); +			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), false);  		}  		void testNickKeyword() { @@ -178,7 +184,7 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false);  			CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "sender contains nick", "nick", HighlightRule::ChatMessage), false); @@ -232,7 +238,7 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);  		}  		void testWholeWords() { @@ -240,14 +246,14 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), true);  		} @@ -256,8 +262,8 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); -			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); +			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), true); @@ -273,7 +279,7 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); +			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); @@ -290,7 +296,7 @@ class HighlightRuleTest : public CppUnit::TestFixture {  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); -			CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), true); +			CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), false);  			CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::MUCMessage), true);  		} | 
 Swift
 Swift