/***************************************************************************
- * Copyright (C) 2005-2016 by the Quassel Project *
+ * Copyright (C) 2005-2018 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
***************************************************************************/
#include "highlightrulemanager.h"
-#include "util.h"
-#include <QtCore>
#include <QDebug>
-#include <QStringList>
+
+#include "expressionmatch.h"
+#include "util.h"
INIT_SYNCABLE_OBJECT(HighlightRuleManager)
+
HighlightRuleManager &HighlightRuleManager::operator=(const HighlightRuleManager &other)
{
if (this == &other)
}
-int HighlightRuleManager::indexOf(const QString &name) const
+int HighlightRuleManager::indexOf(int id) const
{
for (int i = 0; i < _highlightRuleList.count(); i++) {
- if (_highlightRuleList[i].name == name)
+ if (_highlightRuleList[i].id() == id)
return i;
}
return -1;
}
+int HighlightRuleManager::nextId()
+{
+ int max = 0;
+ for (int i = 0; i < _highlightRuleList.count(); i++) {
+ int id = _highlightRuleList[i].id();
+ if (id > max) {
+ max = id;
+ }
+ }
+ return max + 1;
+}
+
+
QVariantMap HighlightRuleManager::initHighlightRuleList() const
{
+ QVariantList id;
QVariantMap highlightRuleListMap;
QStringList name;
QVariantList isRegEx;
QVariantList isCaseSensitive;
QVariantList isActive;
+ QVariantList isInverse;
+ QStringList sender;
QStringList channel;
for (int i = 0; i < _highlightRuleList.count(); i++) {
- name << _highlightRuleList[i].name;
- isRegEx << _highlightRuleList[i].isRegEx;
- isCaseSensitive << _highlightRuleList[i].isCaseSensitive;
- isActive << _highlightRuleList[i].isEnabled;
- channel << _highlightRuleList[i].chanName;
+ id << _highlightRuleList[i].id();
+ name << _highlightRuleList[i].contents();
+ isRegEx << _highlightRuleList[i].isRegEx();
+ isCaseSensitive << _highlightRuleList[i].isCaseSensitive();
+ isActive << _highlightRuleList[i].isEnabled();
+ isInverse << _highlightRuleList[i].isInverse();
+ sender << _highlightRuleList[i].sender();
+ channel << _highlightRuleList[i].chanName();
}
+ highlightRuleListMap["id"] = id;
highlightRuleListMap["name"] = name;
highlightRuleListMap["isRegEx"] = isRegEx;
highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
highlightRuleListMap["isEnabled"] = isActive;
+ highlightRuleListMap["isInverse"] = isInverse;
+ highlightRuleListMap["sender"] = sender;
highlightRuleListMap["channel"] = channel;
- highlightRuleListMap["highlightNick"] = _highlightNick;
- highlightRuleListMap["nicksCaseSensitive"] = _nicksCaseSensitive;
return highlightRuleListMap;
}
void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap &highlightRuleList)
{
+ QVariantList id = highlightRuleList["id"].toList();
QStringList name = highlightRuleList["name"].toStringList();
QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
QVariantList isActive = highlightRuleList["isEnabled"].toList();
+ QVariantList isInverse = highlightRuleList["isInverse"].toList();
+ QStringList sender = highlightRuleList["sender"].toStringList();
QStringList channel = highlightRuleList["channel"].toStringList();
- int count = name.count();
- if (count != isRegEx.count() || count != isCaseSensitive.count() ||
- count != isActive.count() || count != channel.count()) {
+ int count = id.count();
+ if (count != name.count() || count != isRegEx.count() || count != isCaseSensitive.count() ||
+ count != isActive.count() || count != isInverse.count() || count != sender.count() ||
+ count != channel.count()) {
qWarning() << "Corrupted HighlightRuleList settings! (Count mismatch)";
return;
}
_highlightRuleList.clear();
for (int i = 0; i < name.count(); i++) {
- _highlightRuleList << HighlightRule(name[i], isRegEx[i].toBool(), isCaseSensitive[i].toBool(),
- isActive[i].toBool(), channel[i]);
+ _highlightRuleList << HighlightRule(id[i].toInt(), name[i], isRegEx[i].toBool(), isCaseSensitive[i].toBool(),
+ isActive[i].toBool(), isInverse[i].toBool(), sender[i], channel[i]);
}
- _highlightNick = HighlightNickType(highlightRuleList["highlightNick"].toInt());
- _nicksCaseSensitive = highlightRuleList["nicksCaseSensitive"].toBool();
}
-void HighlightRuleManager::addHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive, bool isActive,
+
+void HighlightRuleManager::addHighlightRule(int id, const QString &name, bool isRegEx, bool isCaseSensitive,
+ bool isActive, bool isInverse, const QString &sender,
const QString &channel)
{
- if (contains(name)) {
+ if (contains(id)) {
return;
}
- HighlightRule newItem = HighlightRule(name, isRegEx, isCaseSensitive, isActive, channel);
+ HighlightRule newItem = HighlightRule(id, name, isRegEx, isCaseSensitive, isActive, isInverse, sender, channel);
_highlightRuleList << newItem;
- SYNC(ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(channel))
+ SYNC(ARG(id), ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(isInverse), ARG(sender),
+ ARG(channel))
}
-bool HighlightRuleManager::_match(const QString &msgContents, const QString &msgSender, Message::Type msgType, Message::Flags msgFlags, const QString &bufferName, const QString ¤tNick, const QStringList identityNicks)
+bool HighlightRuleManager::match(const NetworkId &netId,
+ const QString &msgContents,
+ const QString &msgSender,
+ Message::Type msgType,
+ Message::Flags msgFlags,
+ const QString &bufferName,
+ const QString ¤tNick,
+ const QStringList &identityNicks)
{
if (!((msgType & (Message::Plain | Message::Notice | Message::Action)) && !(msgFlags & Message::Self))) {
return false;
}
- if (!currentNick.isEmpty()) {
- QStringList nickList;
- if (_highlightNick == CurrentNick) {
- nickList << currentNick;
- }
- else if (_highlightNick == AllNicks) {
- nickList = identityNicks;
- if (!nickList.contains(currentNick))
- nickList.prepend(currentNick);
+ bool matches = false;
+
+ for (int i = 0; i < _highlightRuleList.count(); i++) {
+ auto &rule = _highlightRuleList.at(i);
+ if (!rule.isEnabled())
+ continue;
+
+ // Skip if channel name doesn't match and channel rule is not empty
+ //
+ // Match succeeds if...
+ // Channel name matches a defined rule
+ // Defined rule is empty
+ // And take the inverse of the above
+ if (!rule.chanNameMatcher().match(bufferName, true)) {
+ // A channel name rule is specified and does NOT match the current buffer name, skip
+ // this rule
+ continue;
}
- foreach(QString nickname, nickList) {
- QRegExp nickRegExp("(^|\\W)" + QRegExp::escape(nickname) + "(\\W|$)", _nicksCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
- if (nickRegExp.indexIn(stripFormatCodes(msgContents)) >= 0) {
- return true;
- }
- }
- for (int i = 0; i < _highlightRuleList.count(); i++) {
- const HighlightRule &rule = _highlightRuleList.at(i);
- if (!rule.isEnabled)
- continue;
-
- if (rule.chanName.size() > 0 && rule.chanName.compare(".*") != 0) {
- if (rule.chanName.startsWith("!")) {
- QRegExp rx(rule.chanName.mid(1), Qt::CaseInsensitive);
- if (rx.exactMatch(bufferName))
- continue;
- }
- else {
- QRegExp rx(rule.chanName, Qt::CaseInsensitive);
- if (!rx.exactMatch(bufferName))
- continue;
- }
- }
+ // Check message according to specified rule, allowing empty rules to match
+ bool contentsMatch = rule.contentsMatcher().match(stripFormatCodes(msgContents), true);
- QRegExp rx;
- if (rule.isRegEx) {
- rx = QRegExp(rule.name, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+ // Check sender according to specified rule, allowing empty rules to match
+ bool senderMatch = rule.senderMatcher().match(msgSender, true);
+
+ if (contentsMatch && senderMatch) {
+ // If an inverse rule matches, then we know that we never want to return a highlight.
+ if (rule.isInverse()) {
+ return false;
}
else {
- rx = QRegExp("(^|\\W)" + QRegExp::escape(rule.name) + "(\\W|$)", rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
- }
- bool match = (rx.indexIn(stripFormatCodes(msgContents)) >= 0);
- if (match) {
- return true;
+ matches = true;
}
}
}
+ if (matches)
+ return true;
+
+ // Check nicknames
+ if (_highlightNick != HighlightNickType::NoNick && !currentNick.isEmpty()) {
+ // Nickname matching allowed and current nickname is known
+ // Run the nickname matcher on the unformatted string
+ if (_nickMatcher.match(stripFormatCodes(msgContents), netId, currentNick, identityNicks)) {
+ return true;
+ }
+ }
+
return false;
}
-void HighlightRuleManager::removeHighlightRule(const QString &highlightRule)
+
+void HighlightRuleManager::removeHighlightRule(int highlightRule)
{
removeAt(indexOf(highlightRule));
SYNC(ARG(highlightRule))
}
-void HighlightRuleManager::toggleHighlightRule(const QString &highlightRule)
+void HighlightRuleManager::toggleHighlightRule(int highlightRule)
{
int idx = indexOf(highlightRule);
if (idx == -1)
return;
- _highlightRuleList[idx].isEnabled = !_highlightRuleList[idx].isEnabled;
+ _highlightRuleList[idx].setIsEnabled(!_highlightRuleList[idx].isEnabled());
SYNC(ARG(highlightRule))
}
+
bool HighlightRuleManager::match(const Message &msg, const QString ¤tNick, const QStringList &identityNicks)
{
- return _match(msg.contents(), msg.sender(), msg.type(), msg.flags(), msg.bufferInfo().bufferName(), currentNick, identityNicks);
+ return match(msg.bufferInfo().networkId(), msg.contents(), msg.sender(), msg.type(), msg.flags(),
+ msg.bufferInfo().bufferName(), currentNick, identityNicks);
+}
+
+
+/**************************************************************************
+ * HighlightRule
+ *************************************************************************/
+bool HighlightRuleManager::HighlightRule::operator!=(const HighlightRule &other) const
+{
+ return (_id != other._id ||
+ _contents != other._contents ||
+ _isRegEx != other._isRegEx ||
+ _isCaseSensitive != other._isCaseSensitive ||
+ _isEnabled != other._isEnabled ||
+ _isInverse != other._isInverse ||
+ _sender != other._sender ||
+ _chanName != other._chanName);
+ // Don't compare ExpressionMatch objects as they are created as needed from the above
+}
+
+
+void HighlightRuleManager::HighlightRule::determineExpressions() const
+{
+ // Don't update if not needed
+ if (!_cacheInvalid) {
+ return;
+ }
+
+ // Set up matching rules
+ // Message is either phrase or regex
+ ExpressionMatch::MatchMode contentsMode =
+ _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
+ ExpressionMatch::MatchMode::MatchPhrase;
+ // Sender and channel are either multiple wildcard entries or regex
+ ExpressionMatch::MatchMode scopeMode =
+ _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
+ ExpressionMatch::MatchMode::MatchMultiWildcard;
+
+ _contentsMatch = ExpressionMatch(_contents, contentsMode, _isCaseSensitive);
+ _senderMatch = ExpressionMatch(_sender, scopeMode, _isCaseSensitive);
+ _chanNameMatch = ExpressionMatch(_chanName, scopeMode, _isCaseSensitive);
+
+ _cacheInvalid = false;
}