diff options
Diffstat (limited to 'Swift')
38 files changed, 1571 insertions, 675 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);  		} diff --git a/Swift/QtUI/QtHighlightEditor.cpp b/Swift/QtUI/QtHighlightEditor.cpp new file mode 100644 index 0000000..3900cf9 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.cpp @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * 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. + */ + +#include <cassert> + +#include <boost/lexical_cast.hpp> + +#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> +#include <Swift/Controllers/HighlightManager.cpp> +#include <Swift/QtUI/QtHighlightEditor.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSettingsProvider.h> + +#include <QTreeWidgetItem> +#include <QFileDialog> + +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_.noColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect())); +	connect(ui_.defaultColorRadio, 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_.defaultColorRadio, 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())); + +	/* if these are not needed, then they should be removed */ +	ui_.moveUpButton->setVisible(false); +	ui_.moveDownButton->setVisible(false); + +	setWindowTitle(tr("Highlight Rules")); +} + +QtHighlightEditor::~QtHighlightEditor() +{ +} + +std::string formatShortDescription(const HighlightRule &rule) +{ +	const std::string chatOrRoom = (rule.getMatchChat() ? "chat" : "room"); + +	std::vector<std::string> senders = rule.getSenders(); +	std::vector<std::string> keywords = rule.getKeywords(); + +	if (senders.empty() && keywords.empty() && !rule.getNickIsKeyword()) { +		return std::string("All ") + chatOrRoom + " messages."; +	} + +	if (rule.getNickIsKeyword()) { +		return std::string("All ") + chatOrRoom + " messages that mention my name."; +	} + +	if (!senders.empty()) { +		return std::string("All ") + chatOrRoom + " messages from " + senders[0] + "."; +	} + +	if (!keywords.empty()) { +		return std::string("All ") + chatOrRoom + " messages mentioning the keyword '" + keywords[0] + "'."; +	} + +	return "Unknown Rule"; +} + +void QtHighlightEditor::show() +{ +	highlightManager_->loadSettings(); + +	populateList(); + +	if (ui_.listWidget->count()) { +		selectRow(0); +	} + +	/* 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.setMatchChat(true); +	highlightManager_->insertRule(row, newRule); +	QListWidgetItem *item = new QListWidgetItem(); +	item->setText(P2QSTRING(formatShortDescription(newRule))); +	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::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_) { +			highlightManager_->setRule(previousRow_, ruleFromDialog()); +		} +	} + +	if (currentRow != -1) { +		HighlightRule rule = highlightManager_->getRule(currentRow); +		ruleToDialog(rule); +		if (ui_.listWidget->currentItem()) { +			ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule))); +		} +	} + +	/* grey the dialog if we have nothing selected */ +	if (currentRow == -1) { +		disableDialog(); +	} + +	previousRow_ = currentRow; +} + +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(P2QSTRING(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_.defaultColorRadio->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(), "Sounds (*.wav)"); +	ui_.soundFile->setText(path); +} + +void QtHighlightEditor::handleOnUserSelected(const JID& jid) { +	/* this might seem like it should be standard behaviour for the suggesting input box, but is not desirable in all cases */ +	jid_->setText(P2QSTRING(jid.toString())); +} + +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(P2QSTRING(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; + +	if (ui_.chatRadio->isChecked()) { +		rule.setMatchChat(true); +		rule.setMatchMUC(false); +	} else { +		rule.setMatchChat(false); +		rule.setMatchMUC(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); +		} +	} + +	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); +		} +	} + +	rule.setNickIsKeyword(ui_.nickIsKeyword->isChecked()); +	rule.setMatchWholeWords(!ui_.matchPartialWords->isChecked()); +	rule.setMatchCase(ui_.matchCase->isChecked()); + +	HighlightAction& action = rule.getAction(); + +	if (ui_.noColorRadio->isChecked()) { +		action.setHighlightText(false); +		action.setTextColor(""); +		action.setTextBackground(""); +	} else if (ui_.defaultColorRadio->isChecked()) { +		action.setHighlightText(true); +		action.setTextColor(""); +		action.setTextBackground(""); +	} else { +		action.setHighlightText(true); +		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_.defaultColorRadio->setEnabled(true); +	ui_.customColorRadio->setEnabled(true); +	if (action.highlightText()) { +		if (action.getTextColor().empty() && action.getTextBackground().empty()) { +			ui_.defaultColorRadio->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); +		} +	} else { +		ui_.noColorRadio->setChecked(true); +		ui_.foregroundColor->setEnabled(false); +		ui_.backgroundColor->setEnabled(false); +	} + +	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(); +} + +} diff --git a/Swift/QtUI/QtHighlightEditor.h b/Swift/QtUI/QtHighlightEditor.h new file mode 100644 index 0000000..c7db464 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * 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 + +#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 = NULL); +			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 onCurrentRowChanged(int currentRow); +			void onApplyButtonClick(); +			void onCancelButtonClick(); +			void onOkButtonClick(); +			void setChildWidgetStates(); +			void widgetClick(); +			void disableDialog(); +			void handleContactSuggestionRequested(const QString& text); +			void selectSoundFile(); + +		private: +			void handleOnUserSelected(const JID& jid); +			void populateList(); +			void updateChatPreview(); +			void selectRow(int row); +			int getSelectedRow() const; +			HighlightRule ruleFromDialog(); +			void ruleToDialog(const HighlightRule& rule); + +			Ui::QtHighlightEditor ui_; +			QtSettingsProvider* settings_; +			HighlightManager* highlightManager_; +			QtSuggestingJIDInput* jid_; +			int previousRow_; +		}; + +} diff --git a/Swift/QtUI/QtHighlightEditor.ui b/Swift/QtUI/QtHighlightEditor.ui new file mode 100644 index 0000000..09a7297 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.ui @@ -0,0 +1,446 @@ +<?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>439</width> +    <height>836</height> +   </rect> +  </property> +  <property name="sizePolicy"> +   <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> +    <horstretch>0</horstretch> +    <verstretch>0</verstretch> +   </sizepolicy> +  </property> +  <property name="minimumSize"> +   <size> +    <width>439</width> +    <height>836</height> +   </size> +  </property> +  <property name="windowTitle"> +   <string>Form</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout_2"> +   <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"/> +   </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="chatRadio"> +        <property name="text"> +         <string>Chats</string> +        </property> +        <property name="checked"> +         <bool>true</bool> +        </property> +       </widget> +      </item> +      <item> +       <widget class="QRadioButton" name="roomRadio"> +        <property name="text"> +         <string>Rooms</string> +        </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="defaultColorRadio"> +          <property name="text"> +           <string>Default Color</string> +          </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>&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>&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> +   <item> +    <widget class="Line" name="line_2"> +     <property name="orientation"> +      <enum>Qt::Horizontal</enum> +     </property> +    </widget> +   </item> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout_9"> +     <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> +     <item> +      <widget class="QDialogButtonBox" name="buttonBox"> +       <property name="standardButtons"> +        <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</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/QtHighlightRuleWidget.cpp b/Swift/QtUI/QtHighlightRuleWidget.cpp deleted file mode 100644 index 9c0df5e..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#include <QDataWidgetMapper> -#include <QStringListModel> -#include <QFileDialog> - -#include <Swift/QtUI/QtHighlightRuleWidget.h> -#include <Swift/QtUI/QtHighlightRulesItemModel.h> - -namespace Swift { - -QtHighlightRuleWidget::QtHighlightRuleWidget(QWidget* parent) -	: QWidget(parent) -{ -	ui_.setupUi(this); - -	QStringList applyToItems; -	for (int i = 0; i < QtHighlightRulesItemModel::ApplyToEOL; ++i) { -		applyToItems << QtHighlightRulesItemModel::getApplyToString(i); -	} -	QStringListModel * applyToModel = new QStringListModel(applyToItems, this); -	ui_.applyTo->setModel(applyToModel); - -	connect(ui_.highlightText, SIGNAL(toggled(bool)), SLOT(onHighlightTextToggled(bool))); -	connect(ui_.customColors, SIGNAL(toggled(bool)), SLOT(onCustomColorsToggled(bool))); -	connect(ui_.playSound, SIGNAL(toggled(bool)), SLOT(onPlaySoundToggled(bool))); -	connect(ui_.customSound, SIGNAL(toggled(bool)), SLOT(onCustomSoundToggled(bool))); -	connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(onSoundFileButtonClicked())); - -	mapper_ = new QDataWidgetMapper(this); -	hasValidIndex_ = false; -	model_ = NULL; -} - -QtHighlightRuleWidget::~QtHighlightRuleWidget() -{ -} - -/** Widget does not gain ownership over the model */ -void QtHighlightRuleWidget::setModel(QtHighlightRulesItemModel* model) -{ -	model_ = model; -	mapper_->setModel(model_); -} - -void QtHighlightRuleWidget::setActiveIndex(const QModelIndex& index) -{ -	if (index.isValid()) { -		if (!hasValidIndex_) { -			mapper_->addMapping(ui_.applyTo, QtHighlightRulesItemModel::ApplyTo, "currentIndex"); -			mapper_->addMapping(ui_.senders, QtHighlightRulesItemModel::Sender, "plainText"); -			mapper_->addMapping(ui_.keywords, QtHighlightRulesItemModel::Keyword, "plainText"); -			mapper_->addMapping(ui_.nickIsKeyword, QtHighlightRulesItemModel::NickIsKeyword); -			mapper_->addMapping(ui_.matchCase, QtHighlightRulesItemModel::MatchCase); -			mapper_->addMapping(ui_.matchWholeWords, QtHighlightRulesItemModel::MatchWholeWords); -			mapper_->addMapping(ui_.highlightText, QtHighlightRulesItemModel::HighlightText); -			mapper_->addMapping(ui_.foreground, QtHighlightRulesItemModel::TextColor, "color"); -			mapper_->addMapping(ui_.background, QtHighlightRulesItemModel::TextBackground, "color"); -			mapper_->addMapping(ui_.playSound, QtHighlightRulesItemModel::PlaySound); -			mapper_->addMapping(ui_.soundFile, QtHighlightRulesItemModel::SoundFile); -		} -		mapper_->setCurrentModelIndex(index); -		ui_.customColors->setChecked(ui_.foreground->getColor().isValid() || ui_.background->getColor().isValid()); -		ui_.customSound->setChecked(!ui_.soundFile->text().isEmpty()); -		ui_.applyTo->focusWidget(); -	} else { -		if (hasValidIndex_) { -			mapper_->clearMapping(); -		} -	} - -	hasValidIndex_ = index.isValid(); -} - -void QtHighlightRuleWidget::onCustomColorsToggled(bool enabled) -{ -	if (!enabled) { -		ui_.foreground->setColor(QColor()); -		ui_.background->setColor(QColor()); -	} -	ui_.foreground->setEnabled(enabled); -	ui_.background->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onCustomSoundToggled(bool enabled) -{ -	if (enabled) { -		if (ui_.soundFile->text().isEmpty()) { -			onSoundFileButtonClicked(); -		} -	} else { -		ui_.soundFile->clear(); -	} -	ui_.soundFile->setEnabled(enabled); -	ui_.soundFileButton->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onSoundFileButtonClicked() -{ -	QString s = QFileDialog::getOpenFileName(this, tr("Choose sound file"), QString(), tr("Sound files (*.wav)")); -	if (!s.isEmpty()) { -		ui_.soundFile->setText(s); -	} -} - -void QtHighlightRuleWidget::onHighlightTextToggled(bool enabled) -{ -	ui_.customColors->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onPlaySoundToggled(bool enabled) -{ -	ui_.customSound->setEnabled(enabled); -} - -void QtHighlightRuleWidget::save() -{ -	if (hasValidIndex_) { -		mapper_->submit(); -	} -} - -void QtHighlightRuleWidget::revert() -{ -	if (hasValidIndex_) { -		mapper_->revert(); -	} -} - -} diff --git a/Swift/QtUI/QtHighlightRuleWidget.h b/Swift/QtUI/QtHighlightRuleWidget.h deleted file mode 100644 index 8a59a14..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#pragma once - -#include <QWidget> -#include <QModelIndex> - -#include <Swift/QtUI/ui_QtHighlightRuleWidget.h> - -class QDataWidgetMapper; - -namespace Swift { - -	class QtHighlightRulesItemModel; - -	class QtHighlightRuleWidget : public QWidget -	{ -		Q_OBJECT - -		public: -			explicit QtHighlightRuleWidget(QWidget* parent = NULL); -			~QtHighlightRuleWidget(); - -			void setModel(QtHighlightRulesItemModel* model); - -		public slots: -			void setActiveIndex(const QModelIndex&); -			void save(); -			void revert(); - -		private slots: -			void onHighlightTextToggled(bool); -			void onCustomColorsToggled(bool); -			void onPlaySoundToggled(bool); -			void onCustomSoundToggled(bool); -			void onSoundFileButtonClicked(); - -		private: -			QDataWidgetMapper * mapper_; -			QtHighlightRulesItemModel * model_; -			bool hasValidIndex_; -			Ui::QtHighlightRuleWidget ui_; -	}; - -} diff --git a/Swift/QtUI/QtHighlightRuleWidget.ui b/Swift/QtUI/QtHighlightRuleWidget.ui deleted file mode 100644 index 9c465f9..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.ui +++ /dev/null @@ -1,260 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>QtHighlightRuleWidget</class> - <widget class="QWidget" name="QtHighlightRuleWidget"> -  <property name="geometry"> -   <rect> -    <x>0</x> -    <y>0</y> -    <width>361</width> -    <height>524</height> -   </rect> -  </property> -  <property name="windowTitle"> -   <string>Form</string> -  </property> -  <layout class="QVBoxLayout" name="verticalLayout_2"> -   <item> -    <widget class="QGroupBox" name="groupBox"> -     <property name="title"> -      <string>Rule conditions</string> -     </property> -     <layout class="QFormLayout" name="formLayout"> -      <property name="fieldGrowthPolicy"> -       <enum>QFormLayout::ExpandingFieldsGrow</enum> -      </property> -      <item row="0" column="0" colspan="2"> -       <widget class="QLabel" name="label"> -        <property name="text"> -         <string>Choose when this rule should be applied. -If you want to provide more than one sender or keyword, input them in separate lines.</string> -        </property> -        <property name="wordWrap"> -         <bool>true</bool> -        </property> -       </widget> -      </item> -      <item row="1" column="0" colspan="2"> -       <widget class="Line" name="line"> -        <property name="orientation"> -         <enum>Qt::Horizontal</enum> -        </property> -       </widget> -      </item> -      <item row="2" column="0"> -       <widget class="QLabel" name="label_2"> -        <property name="text"> -         <string>&Apply to:</string> -        </property> -        <property name="buddy"> -         <cstring>applyTo</cstring> -        </property> -       </widget> -      </item> -      <item row="2" column="1"> -       <widget class="QComboBox" name="applyTo"/> -      </item> -      <item row="3" column="0"> -       <widget class="QLabel" name="label_3"> -        <property name="text"> -         <string>&Senders:</string> -        </property> -        <property name="buddy"> -         <cstring>senders</cstring> -        </property> -       </widget> -      </item> -      <item row="3" column="1"> -       <widget class="QPlainTextEdit" name="senders"/> -      </item> -      <item row="4" column="0"> -       <widget class="QLabel" name="label_4"> -        <property name="text"> -         <string>&Keywords:</string> -        </property> -        <property name="buddy"> -         <cstring>keywords</cstring> -        </property> -       </widget> -      </item> -      <item row="4" column="1"> -       <widget class="QPlainTextEdit" name="keywords"/> -      </item> -      <item row="5" column="1"> -       <widget class="QCheckBox" name="nickIsKeyword"> -        <property name="text"> -         <string>Treat &nick as a keyword (in MUC)</string> -        </property> -       </widget> -      </item> -      <item row="6" column="1"> -       <widget class="QCheckBox" name="matchWholeWords"> -        <property name="text"> -         <string>Match whole &words</string> -        </property> -       </widget> -      </item> -      <item row="7" column="1"> -       <widget class="QCheckBox" name="matchCase"> -        <property name="text"> -         <string>Match &case</string> -        </property> -       </widget> -      </item> -     </layout> -    </widget> -   </item> -   <item> -    <widget class="QGroupBox" name="groupBox_2"> -     <property name="title"> -      <string>Actions</string> -     </property> -     <layout class="QVBoxLayout" name="verticalLayout"> -      <item> -       <widget class="QCheckBox" name="highlightText"> -        <property name="text"> -         <string>&Highlight text</string> -        </property> -       </widget> -      </item> -      <item> -       <layout class="QHBoxLayout" name="horizontalLayout"> -        <item> -         <spacer name="horizontalSpacer"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeType"> -           <enum>QSizePolicy::Fixed</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>28</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item> -         <widget class="QCheckBox" name="customColors"> -          <property name="enabled"> -           <bool>false</bool> -          </property> -          <property name="text"> -           <string>Custom c&olors:</string> -          </property> -         </widget> -        </item> -        <item> -         <widget class="Swift::QtColorToolButton" name="foreground"> -          <property name="enabled"> -           <bool>false</bool> -          </property> -          <property name="text"> -           <string>&Foreground</string> -          </property> -          <property name="toolButtonStyle"> -           <enum>Qt::ToolButtonTextBesideIcon</enum> -          </property> -         </widget> -        </item> -        <item> -         <widget class="Swift::QtColorToolButton" name="background"> -          <property name="enabled"> -           <bool>false</bool> -          </property> -          <property name="text"> -           <string>&Background</string> -          </property> -          <property name="toolButtonStyle"> -           <enum>Qt::ToolButtonTextBesideIcon</enum> -          </property> -         </widget> -        </item> -       </layout> -      </item> -      <item> -       <widget class="QCheckBox" name="playSound"> -        <property name="text"> -         <string>&Play sound</string> -        </property> -       </widget> -      </item> -      <item> -       <layout class="QHBoxLayout" name="horizontalLayout_2"> -        <item> -         <spacer name="horizontalSpacer_2"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeType"> -           <enum>QSizePolicy::Fixed</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>28</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item> -         <widget class="QCheckBox" name="customSound"> -          <property name="enabled"> -           <bool>false</bool> -          </property> -          <property name="text"> -           <string>Custom soun&d:</string> -          </property> -         </widget> -        </item> -        <item> -         <widget class="QLineEdit" name="soundFile"> -          <property name="enabled"> -           <bool>false</bool> -          </property> -          <property name="readOnly"> -           <bool>true</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> -   <item> -    <spacer name="verticalSpacer"> -     <property name="orientation"> -      <enum>Qt::Vertical</enum> -     </property> -     <property name="sizeHint" stdset="0"> -      <size> -       <width>20</width> -       <height>101</height> -      </size> -     </property> -    </spacer> -   </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/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 701170c..b0c1492 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -25,7 +25,7 @@  #include <Swift/QtUI/QtContactEditWindow.h>  #include <Swift/QtUI/QtAdHocCommandWindow.h>  #include <Swift/QtUI/QtFileTransferListWidget.h> -#include <Swift/QtUI/QtHighlightEditorWidget.h> +#include <Swift/QtUI/QtHighlightEditor.h>  #include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h>  #include <Swift/Controllers/Settings/SettingsProviderHierachy.h>  #include <Swift/QtUI/QtUISettingConstants.h> @@ -164,8 +164,8 @@ WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<Whiteboa  	return new QtWhiteboardWindow(whiteboardSession);  } -HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() { -	return new QtHighlightEditorWidget(); +HighlightEditorWindow* QtUIFactory::createHighlightEditorWindow() { +	return new QtHighlightEditor(qtOnlySettings);  }  BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() { diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 4c50572..9c07e76 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -48,7 +48,7 @@ namespace Swift {  			virtual ContactEditWindow* createContactEditWindow();  			virtual FileTransferListWidget* createFileTransferListWidget();  			virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession); -			virtual HighlightEditorWidget* createHighlightEditorWidget(); +			virtual HighlightEditorWindow* createHighlightEditorWindow();  			virtual BlockListEditorWidget* createBlockListEditorWidget();  			virtual AdHocCommandWindow* createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp index 23bc099..1486293 100644 --- a/Swift/QtUI/QtWebKitChatView.cpp +++ b/Swift/QtUI/QtWebKitChatView.cpp @@ -536,6 +536,22 @@ std::string QtWebKitChatView::addMessage(  	return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message));  } +QString QtWebKitChatView::getHighlightSpanStart(const std::string& text, const std::string& background) { +	QString ecsapeColor = QtUtilities::htmlEscape(P2QSTRING(text)); +	QString escapeBackground = QtUtilities::htmlEscape(P2QSTRING(background)); +	if (ecsapeColor.isEmpty()) { +		ecsapeColor = "black"; +	} +	if (escapeBackground.isEmpty()) { +		escapeBackground = "yellow"; +	} +	return QString("<span style=\"color: %1; background: %2\">").arg(ecsapeColor).arg(escapeBackground); +} + +QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { +	return getHighlightSpanStart(highlight.getTextColor(), highlight.getTextBackground()); +} +  QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) {  	QString result;  	foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) { @@ -562,7 +578,8 @@ QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& messa  			continue;  		}  		if ((highlightPart = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) { -			//FIXME: Maybe do something here. Anything, really. +			QString spanStart = getHighlightSpanStart(highlightPart->foregroundColor, highlightPart->backgroundColor); +			result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + "</span>";  			continue;  		} @@ -570,20 +587,6 @@ QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& messa  	return result;  } - -QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { -	QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor())); -	QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground())); -	if (color.isEmpty()) { -		color = "black"; -	} -	if (background.isEmpty()) { -		background = "yellow"; -	} - -	return QString("<span style=\"color: %1; background: %2\">").arg(color).arg(background); -} -  std::string QtWebKitChatView::addMessage(  		const QString& message,   		const std::string& senderName,  diff --git a/Swift/QtUI/QtWebKitChatView.h b/Swift/QtUI/QtWebKitChatView.h index fb6e4da..925ceeb 100644 --- a/Swift/QtUI/QtWebKitChatView.h +++ b/Swift/QtUI/QtWebKitChatView.h @@ -1,5 +1,5 @@  /* - * Copyright (c) 2010-2013 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.   */ @@ -148,8 +148,9 @@ namespace Swift {  					const HighlightAction& highlight);  			bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf);  			static ChatSnippet::Direction getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction); -			QString chatMessageToHTML(const ChatWindow::ChatMessage& message); +			QString getHighlightSpanStart(const std::string& text, const std::string& background);  			QString getHighlightSpanStart(const HighlightAction& highlight); +			QString chatMessageToHTML(const ChatWindow::ChatMessage& message);  			static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString());  		private: diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index dd7d0c3..26e738a 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -127,9 +127,7 @@ sources = [      "QtContactEditWindow.cpp",      "QtContactEditWidget.cpp",      "QtSingleWindow.cpp", -    "QtHighlightEditorWidget.cpp", -    "QtHighlightRulesItemModel.cpp", -    "QtHighlightRuleWidget.cpp", +    "QtHighlightEditor.cpp",      "QtColorToolButton.cpp",      "QtClosableLineEdit.cpp",      "ChatSnippet.cpp", @@ -286,8 +284,7 @@ myenv.Uic4("QtAffiliationEditor.ui")  myenv.Uic4("QtJoinMUCWindow.ui")  myenv.Uic4("QtHistoryWindow.ui")  myenv.Uic4("QtConnectionSettings.ui") -myenv.Uic4("QtHighlightRuleWidget.ui") -myenv.Uic4("QtHighlightEditorWidget.ui") +myenv.Uic4("QtHighlightEditor.ui")  myenv.Uic4("QtBlockListEditorWindow.ui")  myenv.Uic4("QtSpellCheckerWindow.ui")  myenv.Qrc("DefaultTheme.qrc") | 
 Swift
 Swift