From f2bcc401477dcb5ca52b5d9d5e85f4bf7bae9285 Mon Sep 17 00:00:00 2001 From: Richard Maudsley Date: Mon, 13 Jan 2014 15:26:24 +0000 Subject: Reworked highlight rules dialog. Added support for highlighting individual words in messages. Change-Id: I378fa69077c29008db4ef7c2265e5212924bc2ce 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, 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 = sentStanza->getPayload(); if (replace) { eraseIf(unackedStanzas_, PairSecondEquals, 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(), 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, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setAvailableServerFeatures(boost::shared_ptr 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, 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 m std::map::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 info); void handleIncomingMessage(boost::shared_ptr message); std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr 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& /*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, 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_; 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& emoticons) : emoticons_(emoticons) { - + ChatMessageParser::ChatMessageParser(const std::map& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) + : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { } typedef std::pair 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 part, parsedMessage.getParts()) { + boost::shared_ptr textPart; + if ((textPart = boost::dynamic_pointer_cast(part))) { + try { + boost::match_results 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(std::string(start, matchStart))); + } + boost::shared_ptr highlightPart = boost::make_shared(); + 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(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& emoticons); - ChatWindow::ChatMessage parseMessageBody(const std::string& body); + ChatMessageParser(const std::map& 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 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 = boost::make_shared(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 = boost::make_shared(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 emoticons_; ClientBlockListManager* clientBlockListManager_; - ChatMessageParser* chatMessageParser_; JID localMUCServiceJID_; boost::shared_ptr 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, 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& 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& 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, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager); virtual ~MUCController(); boost::signal onUserLeft; boost::signal 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 part = boost::dynamic_pointer_cast(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 part = boost::dynamic_pointer_cast(result.getParts()[index]); CPPUNIT_ASSERT_EQUAL(text, part->target); } + static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + HighlightRule rule; + std::vector 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 list = boost::make_shared(); + list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); + return list; + } + + static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) + { + boost::shared_ptr list = boost::make_shared(); + 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()); + 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()); 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()); ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); assertEmoticon(result, 0, smile1_, smile1Path_); } void testHiddenEmoticon() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared()); ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); assertText(result, 0, "b:)a"); } void testEndlineEmoticon() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared()); 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()); 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()); ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); assertText(result, 0, "(Like this "); assertEmoticon(result, 1, smile1_, smile1Path_); assertText(result, 2, ")"); } - private: std::map 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(mucJID_); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - chatMessageParser_ = new ChatMessageParser(std::map()); + chatMessageParser_ = boost::make_shared(std::map(), highlightManager_->getRules(), true); vcardStorage_ = new VCardMemoryStorage(crypto_.get()); vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); controller_ = new MUCController (self_, muc_, boost::optional(), 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_; boost::shared_ptr 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 +#include +#include + 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 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 + 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 #include -#include -#include #include #include +#include +#include +#include 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 rawEvent) { boost::shared_ptr event = boost::dynamic_pointer_cast(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 @@ -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 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 #include #include #include #include +#include +#include +#include #include #include @@ -40,6 +49,7 @@ HighlightManager::HighlightManager(SettingsProvider* settings) : settings_(settings) , storingSettings_(false) { + rules_ = boost::make_shared(); 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 HighlightManager::rulesFromString(const std::string& rulesString) -{ - std::vector rules; - std::string s(rulesString); - typedef boost::split_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(*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 HighlightManager::getDefaultRules() @@ -97,38 +79,48 @@ std::vector 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(index) < rules_.size()); - return rules_[static_cast(index)]; + assert(index >= 0 && static_cast(index) < rules_->getSize()); + return rules_->getRule(static_cast(index)); } void HighlightManager::setRule(int index, const HighlightRule& rule) { - assert(index >= 0 && static_cast(index) < rules_.size()); - rules_[static_cast(index)] = rule; - storeSettings(); + assert(index >= 0 && static_cast(index) < rules_->getSize()); + rules_->list_[static_cast(index)] = rule; } void HighlightManager::insertRule(int index, const HighlightRule& rule) { - assert(index >= 0 && boost::numeric_cast::size_type>(index) <= rules_.size()); - rules_.insert(rules_.begin() + index, rule); - storeSettings(); + assert(index >= 0 && boost::numeric_cast::size_type>(index) <= rules_->getSize()); + rules_->list_.insert(rules_->list_.begin() + index, rule); } void HighlightManager::removeRule(int index) { - assert(index >= 0 && boost::numeric_cast::size_type>(index) < rules_.size()); - rules_.erase(rules_.begin() + index); - storeSettings(); + assert(index >= 0 && boost::numeric_cast::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 @@ -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 list_; + }; + HighlightManager(SettingsProvider* settings); Highlighter* createHighlighter(); - const std::vector& getRules() const { return rules_; } + boost::shared_ptr 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 onHighlight; @@ -35,15 +58,14 @@ namespace Swift { void handleSettingChanged(const std::string& settingPath); std::string rulesToString() const; - static std::vector rulesFromString(const std::string&); static std::vector getDefaultRules(); SettingsProvider* settings_; bool storingSettings_; - void storeSettings(); - void loadSettings(); - std::vector rules_; + boost::shared_ptr rules_; }; + typedef boost::shared_ptr 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 #include #include @@ -56,57 +62,6 @@ bool HighlightRule::boolFromString(const std::string& s) return s == "1"; } -std::string HighlightRule::toString() const -{ - std::vector 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 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 #include #include +#include +#include #include @@ -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& getSenders() const { return senders_; } void setSenders(const std::vector&); + const std::vector& getSenderRegex() const { return senderRegex_; } const std::vector& getKeywords() const { return keywords_; } void setKeywords(const std::vector&); + const std::vector& 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 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 + 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 #include #include @@ -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 + +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 +#include +#include + +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& suggestions) = 0; + +public: + boost::signal 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 #include #include -#include +#include #include 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 #include @@ -34,7 +40,7 @@ class HighlightRuleTest : public CppUnit::TestFixture { keywords.push_back("keyword1"); keywords.push_back("KEYWORD2"); - std::vectorsenders; + std::vector 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 + +#include + +#include +#include +#include +#include +#include + +#include +#include + +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 senders = rule.getSenders(); + std::vector 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& 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 && selectedRowcount()); + 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 senders; + senders.push_back(Q2PSTRING(senderName)); + rule.setSenders(senders); + } + } + + if (ui_.keywordRadio->isChecked()) { + QString keywordString = ui_.keyword->text(); + if (!keywordString.isEmpty()) { + std::vector 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 senders = rule.getSenders(); + if (!senders.empty()) { + ui_.senderRadio->setChecked(true); + jid_->setText(P2QSTRING(senders[0])); + } + + ui_.keywordRadio->setEnabled(true); + std::vector 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 +#include +#include + +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& 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 @@ + + + QtHighlightEditor + + + + 0 + 0 + 439 + 836 + + + + + 0 + 0 + + + + + 439 + 836 + + + + Form + + + + + + Incoming messages are checked against the following rules. First rule that matches will be executed. + + + true + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + New Rule + + + + + + + Remove Rule + + + + + + + Move Up + + + + + + + Move Down + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Apply Rule To + + + + + + Chats + + + true + + + + + + + Rooms + + + + + + + Qt::Horizontal + + + + 246 + 20 + + + + + + + + + + + + 0 + 0 + + + + Rule Conditions + + + + + + Apply to all messages + + + true + + + + + + + Only messages mentioning me + + + + + + + Messages from this sender: + + + + + + + QLayout::SetMinimumSize + + + + + + + + + + Messages containing this keyword: + + + + + + + + + + Match keyword within longer words + + + + + + + Keyword is case sensitive + + + + + + + + + + + 0 + 0 + + + + Highlight Action + + + + + + + + No Highlight + + + true + + + + + + + Default Color + + + + + + + Custom Color + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + &Text + + + Qt::ToolButtonTextBesideIcon + + + + + + + false + + + &Background + + + Qt::ToolButtonTextBesideIcon + + + + + + + + + + + + + 0 + 0 + + + + Sound Action + + + + + + + + No Sound + + + true + + + + + + + Default Sound + + + + + + + Custom Sound + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + false + + + + + + + false + + + ... + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + Swift::QtColorToolButton + QToolButton +
QtColorToolButton.h
+
+
+ + +
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 -#include -#include - -#include -#include - -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 -#include - -#include - -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 @@ - - - QtHighlightRuleWidget - - - - 0 - 0 - 361 - 524 - - - - Form - - - - - - Rule conditions - - - - QFormLayout::ExpandingFieldsGrow - - - - - Choose when this rule should be applied. -If you want to provide more than one sender or keyword, input them in separate lines. - - - true - - - - - - - Qt::Horizontal - - - - - - - &Apply to: - - - applyTo - - - - - - - - - - &Senders: - - - senders - - - - - - - - - - &Keywords: - - - keywords - - - - - - - - - - Treat &nick as a keyword (in MUC) - - - - - - - Match whole &words - - - - - - - Match &case - - - - - - - - - - Actions - - - - - - &Highlight text - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 28 - 20 - - - - - - - - false - - - Custom c&olors: - - - - - - - false - - - &Foreground - - - Qt::ToolButtonTextBesideIcon - - - - - - - false - - - &Background - - - Qt::ToolButtonTextBesideIcon - - - - - - - - - &Play sound - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 28 - 20 - - - - - - - - false - - - Custom soun&d: - - - - - - - false - - - true - - - - - - - false - - - ... - - - - - - - - - - - - Qt::Vertical - - - - 20 - 101 - - - - - - - - - Swift::QtColorToolButton - QToolButton -
QtColorToolButton.h
-
-
- - -
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 #include #include -#include +#include #include #include #include @@ -164,8 +164,8 @@ WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr whiteboardSession); - virtual HighlightEditorWidget* createHighlightEditorWidget(); + virtual HighlightEditorWindow* createHighlightEditorWindow(); virtual BlockListEditorWidget* createBlockListEditorWidget(); virtual AdHocCommandWindow* createAdHocCommandWindow(boost::shared_ptr 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("").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 part, message.getParts()) { @@ -562,7 +578,8 @@ QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& messa continue; } if ((highlightPart = boost::dynamic_pointer_cast(part))) { - //FIXME: Maybe do something here. Anything, really. + QString spanStart = getHighlightSpanStart(highlightPart->foregroundColor, highlightPart->backgroundColor); + result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + ""; 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("").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") -- cgit v0.10.2-6-g49f6