diff options
Diffstat (limited to 'Swift/Controllers')
| -rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.cpp | 4 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatMessageParser.cpp | 9 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/ChatMessageParser.h | 4 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp | 36 | ||||
| -rw-r--r-- | Swift/Controllers/HighlightAction.cpp | 2 | ||||
| -rw-r--r-- | Swift/Controllers/HighlightAction.h | 11 | ||||
| -rw-r--r-- | Swift/Controllers/HighlightRule.cpp | 12 | ||||
| -rw-r--r-- | Swift/Controllers/HighlightRule.h | 2 | ||||
| -rw-r--r-- | Swift/Controllers/Highlighter.h | 1 |
9 files changed, 67 insertions, 14 deletions
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 24341e6..519deda 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,373 +1,373 @@ /* * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/Chat/ChatControllerBase.h> #include <sstream> #include <map> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/numeric/conversion/cast.hpp> #include <boost/algorithm/string.hpp> #include <Swiften/Base/format.h> #include <Swiften/Base/Path.h> #include <Swiften/Base/String.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Elements/Delay.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/Base/foreach.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h> #include <Swiften/Avatars/AvatarManager.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> #include <Swift/Controllers/HighlightManager.h> #include <Swift/Controllers/Highlighter.h> #include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> 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, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this)); entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); highlighter_ = highlightManager->createHighlighter(); setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); createDayChangeTimer(); } ChatControllerBase::~ChatControllerBase() { delete chatWindow_; } void ChatControllerBase::handleLogCleared() { cancelReplaces(); } ChatWindow* ChatControllerBase::detachChatWindow() { ChatWindow* chatWindow = chatWindow_; chatWindow_ = NULL; return chatWindow; } void ChatControllerBase::handleCapsChanged(const JID& jid) { if (jid.compare(toJID_, JID::WithoutResource) == 0) { handleBareJIDCapsChanged(jid); } } void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) { if (chatWindow_) { chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu); } } void ChatControllerBase::createDayChangeTimer() { if (timerFactory_) { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1)); int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds()); dateChangeTimer_ = boost::shared_ptr<Timer>(timerFactory_->createTimer(millisecondsUntilMidnight)); dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this)); dateChangeTimer_->start(); } } void ChatControllerBase::handleDayChangeTick() { dateChangeTimer_->stop(); boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection); dayTicked(); createDayChangeTimer(); } void ChatControllerBase::setEnabled(bool enabled) { chatWindow_->setInputEnabled(enabled); } void ChatControllerBase::setOnline(bool online) { setEnabled(online); } JID ChatControllerBase::getBaseJID() { return JID(toJID_.toBare()); } void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) { GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_); request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2)); request->send(); } else { chatWindow_->setSecurityLabelsEnabled(false); labelsEnabled_ = false; } } void ChatControllerBase::handleAllMessagesRead() { if (!unreadMessages_.empty()) { targetedUnreadMessages_.clear(); foreach (boost::shared_ptr<StanzaEvent> stanzaEvent, unreadMessages_) { stanzaEvent->conclude(); } unreadMessages_.clear(); chatWindow_->setUnreadMessageCount(0); onUnreadCountChanged(); } } int ChatControllerBase::getUnreadCount() { return boost::numeric_cast<int>(targetedUnreadMessages_.size()); } void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) { if (!stanzaChannel_->isAvailable() || body.empty()) { return; } boost::shared_ptr<Message> message(new Message()); message->setTo(toJID_); message->setType(Swift::Message::Chat); message->setBody(body); if (labelsEnabled_) { if (!isCorrectionMessage) { lastLabel_ = chatWindow_->getSelectedSecurityLabel(); } SecurityLabelsCatalog::Item labelItem = lastLabel_; if (labelItem.getLabel()) { message->addPayload(labelItem.getLabel()); } } preSendMessageRequest(message); boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); if (useDelayForLatency_) { message->addPayload(boost::make_shared<Delay>(now, selfJID_)); } if (isCorrectionMessage) { message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_))); } message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID()); stanzaChannel_->sendMessage(message); postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message)); onActivity(message->getBody()); #ifdef SWIFT_EXPERIMENTAL_HISTORY logMessage(body, selfJID_, toJID_, now, false); #endif } void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) { if (catalog && !error) { if (catalog->getItems().size() == 0) { chatWindow_->setSecurityLabelsEnabled(false); labelsEnabled_ = false; } else { labelsEnabled_ = true; chatWindow_->setAvailableSecurityLabels(catalog->getItems()); chatWindow_->setSecurityLabelsEnabled(true); } } else { labelsEnabled_ = false; chatWindow_->setSecurityLabelsError(); } } void ChatControllerBase::showChatWindow() { chatWindow_->show(); } void ChatControllerBase::activateChatWindow() { chatWindow_->activate(); } bool ChatControllerBase::hasOpenWindow() const { return chatWindow_ && chatWindow_->isVisible(); } std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { 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,senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); + return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, 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,senderIsSelf), id, time, highlight); + chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), id, time, highlight); } } bool ChatControllerBase::isFromContact(const JID& from) { return from.toBare() == toJID_.toBare(); } void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { preHandleIncomingMessage(messageEvent); if (messageEvent->isReadable() && !messageEvent->getConcluded()) { unreadMessages_.push_back(messageEvent); if (messageEvent->targetsMe()) { targetedUnreadMessages_.push_back(messageEvent); } } boost::shared_ptr<Message> message = messageEvent->getStanza(); std::string body = message->getBody(); HighlightAction highlight; if (message->isError()) { if (!message->getTo().getResource().empty()) { std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } } else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { handleMUCInvitation(messageEvent->getStanza()); return; } else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) { handleMediatedMUCInvitation(messageEvent->getStanza()); return; } else { if (!messageEvent->isReadable()) { return; } showChatWindow(); JID from = message->getFrom(); std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>(); for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) { if (!delayPayloads[i]->getFrom()) { continue; } boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); std::ostringstream s; s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << "."; chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); } boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); // Determine the timestamp boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time(); boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message); if (messageTimeStamp) { timeStamp = *messageTimeStamp; } onActivity(body); // Highlight if (!isIncomingMessageFromMe(message)) { highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from)); } boost::shared_ptr<Replace> replace = message->getPayload<Replace>(); if (replace) { std::string body = message->getBody(); // Should check if the user has a previous message std::map<JID, std::string>::iterator lastMessage; lastMessage = lastMessagesUIID_.find(from); if (lastMessage != lastMessagesUIID_.end()) { replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), timeStamp, highlight); } } else { lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, avatarManager_->getAvatarPath(from), timeStamp, highlight); } logMessage(body, from, selfJID_, timeStamp, true); } chatWindow_->show(); chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); postHandleIncomingMessage(messageEvent, highlight); } std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) { std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message"); if (!error->getText().empty()) { return error->getText(); } else { switch (error->getCondition()) { case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); } } assert(false); return defaultMessage; } void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) { unreadMessages_.push_back(event); chatWindow_->show(); chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu()); eventController_->handleIncomingEvent(event); } void ChatControllerBase::handleMUCInvitation(Message::ref message) { MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true)); } else { MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu()); handleGeneralMUCInvitation(inviteEvent); } } void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { MUCUserPayload::Invite invite = *message->getPayload<MUCUserPayload>()->getInvite(); JID from = message->getFrom(); std::string reason; if (!invite.reason.empty()) { reason = invite.reason; } std::string password; if (message->getPayload<MUCUserPayload>()->getPassword()) { password = *message->getPayload<MUCUserPayload>()->getPassword(); } MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false); handleGeneralMUCInvitation(inviteEvent); } } diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp index 09d93ac..5a608db 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.cpp +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -1,194 +1,195 @@ /* * Copyright (c) 2013-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <vector> #include <utility> #include <boost/smart_ptr/make_shared.hpp> #include <boost/algorithm/string.hpp> #include <Swiften/Base/Regex.h> #include <Swiften/Base/foreach.h> #include <SwifTools/Linkify.h> namespace Swift { ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { } typedef std::pair<std::string, std::string> StringPair; - ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, bool senderIsSelf) { + ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) { ChatWindow::ChatMessage parsedMessage; std::string remaining = body; /* Parse one, URLs */ while (!remaining.empty()) { bool found = false; std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining); remaining = ""; for (size_t i = 0; i < links.first.size(); i++) { const std::string& part = links.first[i]; if (found) { // Must be on the last part, then remaining = part; } else { if (i == links.second) { found = true; parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part)); } else { parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part)); } } } } /* do emoticon substitution */ parsedMessage = emoticonHighlight(parsedMessage); if (!senderIsSelf) { /* do not highlight our own messsages */ /* do word-based color highlighting */ - parsedMessage = splitHighlight(parsedMessage); + parsedMessage = splitHighlight(parsedMessage, nick); } return parsedMessage; } ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) { ChatWindow::ChatMessage parsedMessage = message; std::string regexString; /* Parse two, emoticons */ foreach (StringPair emoticon, emoticons_) { /* Construct a regexp that finds an instance of any of the emoticons inside a group * at the start or end of the line, or beside whitespace. */ regexString += regexString.empty() ? "" : "|"; std::string escaped = "(" + Regex::escape(emoticon.first) + ")"; regexString += "^" + escaped + "|"; regexString += escaped + "$|"; regexString += "\\s" + escaped + "|"; regexString += escaped + "\\s"; } if (!regexString.empty()) { regexString += ""; boost::regex emoticonRegex(regexString); ChatWindow::ChatMessage newMessage; foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { try { boost::match_results<std::string::const_iterator> match; const std::string& text = textPart->text; std::string::const_iterator start = text.begin(); while (regex_search(start, text.end(), match, emoticonRegex)) { int matchIndex = 0; for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) { if (match[matchIndex].length() > 0) { //This is the matching subgroup break; } } std::string::const_iterator matchStart = match[matchIndex].first; std::string::const_iterator matchEnd = match[matchIndex].second; if (start != matchStart) { /* If we're skipping over plain text since the previous emoticon, record it as plain text */ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); } boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>(); std::string matchString = match[matchIndex].str(); std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString); assert (emoticonIterator != emoticons_.end()); const StringPair& emoticon = *emoticonIterator; emoticonPart->imagePath = emoticon.second; emoticonPart->alternativeText = emoticon.first; newMessage.append(emoticonPart); start = matchEnd; } if (start != text.end()) { /* If there's plain text after the last emoticon, record it */ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); } } catch (std::runtime_error) { /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ newMessage.append(part); } } else { newMessage.append(part); } } parsedMessage = newMessage; } return parsedMessage; } - ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message) + ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick) { ChatWindow::ChatMessage parsedMessage = message; for (size_t i = 0; i < highlightRules_->getSize(); ++i) { const HighlightRule& rule = highlightRules_->getRule(i); if (rule.getMatchMUC() && !mucMode_) { continue; /* this rule only applies to MUC's, and this is a CHAT */ } else if (rule.getMatchChat() && mucMode_) { continue; /* this rule only applies to CHAT's, and this is a MUC */ } - foreach(const boost::regex ®ex, rule.getKeywordRegex()) { + const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick); + foreach(const boost::regex& regex, keywordRegex) { ChatWindow::ChatMessage newMessage; foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { try { boost::match_results<std::string::const_iterator> match; const std::string& text = textPart->text; std::string::const_iterator start = text.begin(); while (regex_search(start, text.end(), match, regex)) { std::string::const_iterator matchStart = match[0].first; std::string::const_iterator matchEnd = match[0].second; if (start != matchStart) { /* If we're skipping over plain text since the previous emoticon, record it as plain text */ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); } boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>(); highlightPart->text = match.str(); highlightPart->foregroundColor = rule.getAction().getTextColor(); highlightPart->backgroundColor = rule.getAction().getTextBackground(); newMessage.append(highlightPart); start = matchEnd; } if (start != text.end()) { /* If there's plain text after the last emoticon, record it */ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); } } catch (std::runtime_error) { /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ newMessage.append(part); } } else { newMessage.append(part); } } parsedMessage = newMessage; } } return parsedMessage; } } diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h index cff4ffa..2f5c171 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.h +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -1,26 +1,26 @@ /* * Copyright (c) 2013-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once #include <string> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class ChatMessageParser { public: ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false); - ChatWindow::ChatMessage parseMessageBody(const std::string& body, bool senderIsSelf = false); + ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false); private: ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); - ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage); + ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick); std::map<std::string, std::string> emoticons_; HighlightRulesListPtr highlightRules_; bool mucMode_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp index 5dca63a..2a07654 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -1,231 +1,267 @@ /* * Copyright (c) 2013-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <hippomocks.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> using namespace Swift; class ChatMessageParserTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(ChatMessageParserTest); CPPUNIT_TEST(testFullBody); CPPUNIT_TEST(testOneEmoticon); CPPUNIT_TEST(testBareEmoticon); CPPUNIT_TEST(testHiddenEmoticon); CPPUNIT_TEST(testEndlineEmoticon); CPPUNIT_TEST(testBoundedEmoticons); CPPUNIT_TEST_SUITE_END(); public: void setUp() { smile1_ = ":)"; smile1Path_ = "/blah/smile1.png"; smile2_ = ":("; smile2Path_ = "/blah/smile2.jpg"; emoticons_[smile1_] = smile1Path_; emoticons_[smile2_] = smile2Path_; } void tearDown() { emoticons_.clear(); } void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); CPPUNIT_ASSERT_EQUAL(text, part->text); } void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); CPPUNIT_ASSERT(!!part); CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); CPPUNIT_ASSERT_EQUAL(path, part->imagePath); } void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); CPPUNIT_ASSERT_EQUAL(text, part->text); } void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); CPPUNIT_ASSERT_EQUAL(text, part->target); } static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) { HighlightRule rule; std::vector<std::string> keywords; keywords.push_back(keyword); rule.setKeywords(keywords); rule.setMatchCase(matchCase); rule.setMatchWholeWords(matchWholeWord); rule.setMatchChat(true); return rule; } static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) { boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); return list; } static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) { boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); list->addRule(rule1); list->addRule(rule2); return list; } + static HighlightRulesListPtr ruleListWithNickHighlight() + { + HighlightRule rule; + rule.setMatchChat(true); + rule.setNickIsKeyword(true); + rule.setMatchCase(true); + rule.setMatchWholeWords(true); + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule); + return list; + } + void testFullBody() { const std::string no_special_message = "a message with no special content"; ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); assertText(result, 0, no_special_message); testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); assertEmoticon(result, 0, smile1_, smile1Path_); assertText(result, 1, " shiny "); assertEmoticon(result, 2, smile2_, smile2Path_); assertText(result, 3, " "); assertHighlight(result, 4, "trigger"); assertText(result, 5, " "); assertEmoticon(result, 6, smile1_, smile1Path_); assertText(result, 7, " "); assertURL(result, 8, "http://wonderland.lit/blah"); assertText(result, 9, " "); assertURL(result, 10, "http://denmark.lit"); assertText(result, 11, " boom boom"); testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); result = testling.parseMessageBody("testtriggermessage"); assertText(result, 0, "test"); assertHighlight(result, 1, "trigger"); 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"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice", "Alice"); + assertHighlight(result, 0, "Alice"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("TextAliceText", "Alice"); + assertText(result, 0, "TextAliceText"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice Text", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice"); + assertText(result, 2, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice Text", "Alice"); + assertHighlight(result, 0, "Alice"); + assertText(result, 1, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice"); } void testOneEmoticon() { ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody(" :) "); assertText(result, 0, " "); assertEmoticon(result, 1, smile1_, smile1Path_); assertText(result, 2, " "); } void testBareEmoticon() { ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); assertEmoticon(result, 0, smile1_, smile1Path_); } void testHiddenEmoticon() { ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); assertText(result, 0, "b:)a"); } void testEndlineEmoticon() { ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)"); assertText(result, 0, "Lazy"); assertEmoticon(result, 1, smile1_, smile1Path_); } void testBoundedEmoticons() { ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:("); assertEmoticon(result, 0, smile1_, smile1Path_); assertText(result, 1, "Lazy"); assertEmoticon(result, 2, smile2_, smile2Path_); } void testEmoticonParenthesis() { ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); assertText(result, 0, "(Like this "); assertEmoticon(result, 1, smile1_, smile1Path_); assertText(result, 2, ")"); } private: std::map<std::string, std::string> emoticons_; std::string smile1_; std::string smile1Path_; std::string smile2_; std::string smile2Path_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); diff --git a/Swift/Controllers/HighlightAction.cpp b/Swift/Controllers/HighlightAction.cpp index d4d199d..492d4d2 100644 --- a/Swift/Controllers/HighlightAction.cpp +++ b/Swift/Controllers/HighlightAction.cpp @@ -1,28 +1,28 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ #include <Swift/Controllers/HighlightAction.h> namespace Swift { -void HighlightAction::setHighlightText(bool highlightText) +void HighlightAction::setHighlightAllText(bool highlightText) { highlightText_ = highlightText; if (!highlightText_) { textColor_.clear(); textBackground_.clear(); } } void HighlightAction::setPlaySound(bool playSound) { playSound_ = playSound; if (!playSound_) { soundFile_.clear(); } } } diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h index de1f201..90768a7 100644 --- a/Swift/Controllers/HighlightAction.h +++ b/Swift/Controllers/HighlightAction.h @@ -1,76 +1,79 @@ /* * 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 <string> #include <boost/archive/text_oarchive.hpp> #include <boost/archive/text_iarchive.hpp> namespace Swift { class HighlightRule; class HighlightAction { public: HighlightAction() : highlightText_(false), playSound_(false) {} - bool highlightText() const { return highlightText_; } - void setHighlightText(bool highlightText); + /** + * Gets the flag that indicates the entire message should be highlighted. + */ + bool highlightAllText() const { return highlightText_; } + void setHighlightAllText(bool highlightText); /** - * Gets the foreground highlight color. If the string is empty, assume a default color. + * Gets the foreground highlight color. */ const std::string& getTextColor() const { return textColor_; } void setTextColor(const std::string& textColor) { textColor_ = textColor; } /** - * Gets the background highlight color. If the string is empty, assume a default color. + * Gets the background highlight color. */ const std::string& getTextBackground() const { return textBackground_; } void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; } bool playSound() const { return playSound_; } void setPlaySound(bool playSound); /** * Gets the sound filename. If the string is empty, assume a default sound file. */ const std::string& getSoundFile() const { return soundFile_; } void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; } bool isEmpty() const { return !highlightText_ && !playSound_; } private: friend class boost::serialization::access; template<class Archive> void serialize(Archive & ar, const unsigned int version); bool highlightText_; std::string textColor_; std::string textBackground_; bool playSound_; std::string soundFile_; }; template<class Archive> void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/) { ar & highlightText_; ar & textColor_; ar & textBackground_; ar & playSound_; ar & soundFile_; } } diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp index f1a5235..3251067 100644 --- a/Swift/Controllers/HighlightRule.cpp +++ b/Swift/Controllers/HighlightRule.cpp @@ -1,140 +1,152 @@ /* * 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 <algorithm> #include <boost/algorithm/string.hpp> #include <boost/lambda/lambda.hpp> #include <Swiften/Base/foreach.h> #include <Swiften/Base/Regex.h> #include <Swift/Controllers/HighlightRule.h> namespace Swift { HighlightRule::HighlightRule() : nickIsKeyword_(false) , matchCase_(false) , matchWholeWords_(false) , matchChat_(false) , matchMUC_(false) { } boost::regex HighlightRule::regexFromString(const std::string & s) const { std::string escaped = Regex::escape(s); std::string word = matchWholeWords_ ? "\\b" : ""; boost::regex::flag_type flags = boost::regex::normal; if (!matchCase_) { flags |= boost::regex::icase; } return boost::regex(word + escaped + word, flags); } void HighlightRule::updateRegex() const { keywordRegex_.clear(); foreach (const std::string & k, keywords_) { keywordRegex_.push_back(regexFromString(k)); } senderRegex_.clear(); foreach (const std::string & s, senders_) { senderRegex_.push_back(regexFromString(s)); } } std::string HighlightRule::boolToString(bool b) { return b ? "1" : "0"; } bool HighlightRule::boolFromString(const std::string& s) { return s == "1"; } bool HighlightRule::isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType messageType) const { if ((messageType == HighlightRule::ChatMessage && matchChat_) || (messageType == HighlightRule::MUCMessage && matchMUC_)) { bool matchesKeyword = keywords_.empty() && (nick.empty() || !nickIsKeyword_); bool matchesSender = senders_.empty(); if (!matchesKeyword && nickIsKeyword_ && !nick.empty()) { if (boost::regex_search(body, regexFromString(nick))) { matchesKeyword = true; } } foreach (const boost::regex & rx, senderRegex_) { if (boost::regex_search(sender, rx)) { matchesSender = true; break; } } if (matchesKeyword && matchesSender) { return true; } } return false; } void HighlightRule::setSenders(const std::vector<std::string>& senders) { senders_ = senders; updateRegex(); } void HighlightRule::setKeywords(const std::vector<std::string>& keywords) { keywords_ = keywords; updateRegex(); } +std::vector<boost::regex> HighlightRule::getKeywordRegex(const std::string& nick) const { + if (nickIsKeyword_) { + std::vector<boost::regex> regex; + if (!nick.empty()) { + regex.push_back(regexFromString(nick)); + } + return regex; + } else { + return keywordRegex_; + } +} + void HighlightRule::setNickIsKeyword(bool nickIsKeyword) { nickIsKeyword_ = nickIsKeyword; updateRegex(); } void HighlightRule::setMatchCase(bool matchCase) { matchCase_ = matchCase; updateRegex(); } void HighlightRule::setMatchWholeWords(bool matchWholeWords) { matchWholeWords_ = matchWholeWords; updateRegex(); } void HighlightRule::setMatchChat(bool matchChat) { matchChat_ = matchChat; updateRegex(); } void HighlightRule::setMatchMUC(bool matchMUC) { matchMUC_ = matchMUC; updateRegex(); } bool HighlightRule::isEmpty() const { return senders_.empty() && keywords_.empty() && !nickIsKeyword_ && !matchChat_ && !matchMUC_ && action_.isEmpty(); } } diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h index ae1a3d3..c0226bc 100644 --- a/Swift/Controllers/HighlightRule.h +++ b/Swift/Controllers/HighlightRule.h @@ -1,101 +1,101 @@ /* * 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 <vector> #include <string> #include <boost/regex.hpp> #include <boost/archive/text_oarchive.hpp> #include <boost/archive/text_iarchive.hpp> #include <Swift/Controllers/HighlightAction.h> namespace Swift { class HighlightRule { public: HighlightRule(); enum MessageType { ChatMessage, MUCMessage }; bool isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType) const; const HighlightAction& getAction() const { return action_; } HighlightAction& getAction() { return action_; } const std::vector<std::string>& getSenders() const { return senders_; } void setSenders(const std::vector<std::string>&); const std::vector<boost::regex>& getSenderRegex() const { return senderRegex_; } const std::vector<std::string>& getKeywords() const { return keywords_; } void setKeywords(const std::vector<std::string>&); - const std::vector<boost::regex>& getKeywordRegex() const { return keywordRegex_; } + std::vector<boost::regex> getKeywordRegex(const std::string& nick) const; bool getNickIsKeyword() const { return nickIsKeyword_; } void setNickIsKeyword(bool); bool getMatchCase() const { return matchCase_; } void setMatchCase(bool); bool getMatchWholeWords() const { return matchWholeWords_; } void setMatchWholeWords(bool); bool getMatchChat() const { return matchChat_; } void setMatchChat(bool); bool getMatchMUC() const { return matchMUC_; } void setMatchMUC(bool); bool isEmpty() const; private: friend class boost::serialization::access; template<class Archive> void serialize(Archive & ar, const unsigned int version); static std::string boolToString(bool); static bool boolFromString(const std::string&); std::vector<std::string> senders_; std::vector<std::string> keywords_; bool nickIsKeyword_; mutable std::vector<boost::regex> senderRegex_; mutable std::vector<boost::regex> keywordRegex_; void updateRegex() const; boost::regex regexFromString(const std::string&) const; bool matchCase_; bool matchWholeWords_; bool matchChat_; bool matchMUC_; HighlightAction action_; }; template<class Archive> void HighlightRule::serialize(Archive& ar, const unsigned int /*version*/) { ar & senders_; ar & keywords_; ar & nickIsKeyword_; ar & matchChat_; ar & matchMUC_; ar & matchCase_; ar & matchWholeWords_; ar & action_; updateRegex(); } } diff --git a/Swift/Controllers/Highlighter.h b/Swift/Controllers/Highlighter.h index d026f29..d5d846b 100644 --- a/Swift/Controllers/Highlighter.h +++ b/Swift/Controllers/Highlighter.h @@ -1,37 +1,38 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ #pragma once #include <string> #include <Swift/Controllers/HighlightRule.h> namespace Swift { class HighlightManager; class Highlighter { public: Highlighter(HighlightManager* manager); enum Mode { ChatMode, MUCMode }; void setMode(Mode mode); void setNick(const std::string& nick) { nick_ = nick; } + std::string getNick() const { return nick_; } HighlightAction findAction(const std::string& body, const std::string& sender) const; void handleHighlightAction(const HighlightAction& action); private: HighlightManager* manager_; Mode mode_; HighlightRule::MessageType messageType_; std::string nick_; }; } |
Swift