src: Yearly copyright bump
[quassel.git] / src / common / highlightrulemanager.cpp
index d18d8d6..2c7a14c 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2016 by the Quassel Project                        *
+ *   Copyright (C) 2005-2019 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>
 
-INIT_SYNCABLE_OBJECT(HighlightRuleManager)
-HighlightRuleManager &HighlightRuleManager::operator=(const HighlightRuleManager &other)
+#include "expressionmatch.h"
+#include "util.h"
+
+HighlightRuleManager& HighlightRuleManager::operator=(const HighlightRuleManager& other)
 {
     if (this == &other)
         return *this;
@@ -38,153 +37,222 @@ HighlightRuleManager &HighlightRuleManager::operator=(const HighlightRuleManager
     return *this;
 }
 
-
-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)
+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,
-                                            const QString &channel)
+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 &currentNick, 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& currentNick,
+                                 const QStringList& identityNicks)
 {
     if (!((msgType & (Message::Plain | Message::Notice | Message::Action)) && !(msgFlags & Message::Self))) {
-       return false;
+        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& currentNick, const QStringList& 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;
+}