1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "highlightrulemanager.h"
25 #include "expressionmatch.h"
28 INIT_SYNCABLE_OBJECT(HighlightRuleManager)
30 HighlightRuleManager &HighlightRuleManager::operator=(const HighlightRuleManager &other)
35 SyncableObject::operator=(other);
36 _highlightRuleList = other._highlightRuleList;
37 _nicksCaseSensitive = other._nicksCaseSensitive;
38 _highlightNick = other._highlightNick;
43 int HighlightRuleManager::indexOf(int id) const
45 for (int i = 0; i < _highlightRuleList.count(); i++) {
46 if (_highlightRuleList[i].id() == id)
53 int HighlightRuleManager::nextId()
56 for (int i = 0; i < _highlightRuleList.count(); i++) {
57 int id = _highlightRuleList[i].id();
66 QVariantMap HighlightRuleManager::initHighlightRuleList() const
69 QVariantMap highlightRuleListMap;
72 QVariantList isCaseSensitive;
73 QVariantList isActive;
74 QVariantList isInverse;
78 for (int i = 0; i < _highlightRuleList.count(); i++) {
79 id << _highlightRuleList[i].id();
80 name << _highlightRuleList[i].contents();
81 isRegEx << _highlightRuleList[i].isRegEx();
82 isCaseSensitive << _highlightRuleList[i].isCaseSensitive();
83 isActive << _highlightRuleList[i].isEnabled();
84 isInverse << _highlightRuleList[i].isInverse();
85 sender << _highlightRuleList[i].sender();
86 channel << _highlightRuleList[i].chanName();
89 highlightRuleListMap["id"] = id;
90 highlightRuleListMap["name"] = name;
91 highlightRuleListMap["isRegEx"] = isRegEx;
92 highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
93 highlightRuleListMap["isEnabled"] = isActive;
94 highlightRuleListMap["isInverse"] = isInverse;
95 highlightRuleListMap["sender"] = sender;
96 highlightRuleListMap["channel"] = channel;
97 return highlightRuleListMap;
101 void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap &highlightRuleList)
103 QVariantList id = highlightRuleList["id"].toList();
104 QStringList name = highlightRuleList["name"].toStringList();
105 QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
106 QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
107 QVariantList isActive = highlightRuleList["isEnabled"].toList();
108 QVariantList isInverse = highlightRuleList["isInverse"].toList();
109 QStringList sender = highlightRuleList["sender"].toStringList();
110 QStringList channel = highlightRuleList["channel"].toStringList();
112 int count = id.count();
113 if (count != name.count() || count != isRegEx.count() || count != isCaseSensitive.count() ||
114 count != isActive.count() || count != isInverse.count() || count != sender.count() ||
115 count != channel.count()) {
116 qWarning() << "Corrupted HighlightRuleList settings! (Count mismatch)";
120 _highlightRuleList.clear();
121 for (int i = 0; i < name.count(); i++) {
122 _highlightRuleList << HighlightRule(id[i].toInt(), name[i], isRegEx[i].toBool(), isCaseSensitive[i].toBool(),
123 isActive[i].toBool(), isInverse[i].toBool(), sender[i], channel[i]);
128 void HighlightRuleManager::addHighlightRule(int id, const QString &name, bool isRegEx, bool isCaseSensitive,
129 bool isActive, bool isInverse, const QString &sender,
130 const QString &channel)
136 HighlightRule newItem = HighlightRule(id, name, isRegEx, isCaseSensitive, isActive, isInverse, sender, channel);
137 _highlightRuleList << newItem;
139 SYNC(ARG(id), ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(isInverse), ARG(sender),
144 bool HighlightRuleManager::match(const QString &msgContents,
145 const QString &msgSender,
146 Message::Type msgType,
147 Message::Flags msgFlags,
148 const QString &bufferName,
149 const QString ¤tNick,
150 const QStringList identityNicks)
152 if (!((msgType & (Message::Plain | Message::Notice | Message::Action)) && !(msgFlags & Message::Self))) {
156 bool matches = false;
158 for (int i = 0; i < _highlightRuleList.count(); i++) {
159 auto &rule = _highlightRuleList.at(i);
160 if (!rule.isEnabled())
163 // Skip if channel name doesn't match and channel rule is not empty
165 // Match succeeds if...
166 // Channel name matches a defined rule
167 // Defined rule is empty
168 // And take the inverse of the above
169 if (!rule.chanNameMatcher().match(bufferName, true)) {
170 // A channel name rule is specified and does NOT match the current buffer name, skip
175 // Check message according to specified rule, allowing empty rules to match
176 bool contentsMatch = rule.contentsMatcher().match(stripFormatCodes(msgContents), true);
178 // Check sender according to specified rule, allowing empty rules to match
179 bool senderMatch = rule.senderMatcher().match(msgSender, true);
181 if (contentsMatch && senderMatch) {
182 // If an inverse rule matches, then we know that we never want to return a highlight.
183 if (rule.isInverse()) {
196 if (_highlightNick != HighlightNickType::NoNick && !currentNick.isEmpty()) {
197 // Update cache if needed
198 determineNickExpressions(currentNick, identityNicks);
201 if (_cachedNickMatcher.isValid()
202 && _cachedNickMatcher.match(stripFormatCodes(msgContents))) {
203 // Nick matcher is valid and match found
212 void HighlightRuleManager::removeHighlightRule(int highlightRule)
214 removeAt(indexOf(highlightRule));
215 SYNC(ARG(highlightRule))
219 void HighlightRuleManager::toggleHighlightRule(int highlightRule)
221 int idx = indexOf(highlightRule);
224 _highlightRuleList[idx].setIsEnabled(!_highlightRuleList[idx].isEnabled());
225 SYNC(ARG(highlightRule))
229 bool HighlightRuleManager::match(const Message &msg, const QString ¤tNick, const QStringList &identityNicks)
231 return match(msg.contents(), msg.sender(), msg.type(), msg.flags(), msg.bufferInfo().bufferName(), currentNick, identityNicks);
235 void HighlightRuleManager::determineNickExpressions(const QString ¤tNick,
236 const QStringList identityNicks) const
238 // Don't do anything for no nicknames
239 if (_highlightNick == HighlightNickType::NoNick) {
243 // Only update if needed (check nickname config, current nick, identity nicks for change)
244 if (!_cacheNickConfigInvalid
245 && _cachedNickCurrent == currentNick
246 && _cachedIdentityNicks == identityNicks) {
251 QStringList nickList;
252 if (_highlightNick == CurrentNick) {
253 nickList << currentNick;
255 else if (_highlightNick == AllNicks) {
256 nickList = identityNicks;
257 if (!nickList.contains(currentNick))
258 nickList.prepend(currentNick);
261 // Set up phrase matcher, joining with newlines
262 _cachedNickMatcher = ExpressionMatch(nickList.join("\n"),
263 ExpressionMatch::MatchMode::MatchMultiPhrase,
264 _nicksCaseSensitive);
266 _cacheNickConfigInvalid = false;
267 _cachedNickCurrent = currentNick;
268 _cachedIdentityNicks = identityNicks;
272 /**************************************************************************
274 *************************************************************************/
275 bool HighlightRuleManager::HighlightRule::operator!=(const HighlightRule &other) const
277 return (_id != other._id ||
278 _contents != other._contents ||
279 _isRegEx != other._isRegEx ||
280 _isCaseSensitive != other._isCaseSensitive ||
281 _isEnabled != other._isEnabled ||
282 _isInverse != other._isInverse ||
283 _sender != other._sender ||
284 _chanName != other._chanName);
285 // Don't compare ExpressionMatch objects as they are created as needed from the above
289 void HighlightRuleManager::HighlightRule::determineExpressions() const
291 // Don't update if not needed
292 if (!_cacheInvalid) {
296 // Set up matching rules
297 // Message is either phrase or regex
298 ExpressionMatch::MatchMode contentsMode =
299 _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
300 ExpressionMatch::MatchMode::MatchPhrase;
301 // Sender and channel are either multiple wildcard entries or regex
302 ExpressionMatch::MatchMode scopeMode =
303 _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
304 ExpressionMatch::MatchMode::MatchMultiWildcard;
306 _contentsMatch = ExpressionMatch(_contents, contentsMode, _isCaseSensitive);
307 _senderMatch = ExpressionMatch(_sender, scopeMode, _isCaseSensitive);
308 _chanNameMatch = ExpressionMatch(_chanName, scopeMode, _isCaseSensitive);
310 _cacheInvalid = false;