Implement inverse highlight rules
authorJanne Koschinski <janne@kuschku.de>
Tue, 5 Sep 2017 15:18:57 +0000 (17:18 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 20 Dec 2017 19:39:56 +0000 (20:39 +0100)
- Implements configuration, sync, and matching behavior for inverse
  rules
- If a message matches a positive rule (or nick), and an inverse rule,
  the inverse rule takes precedence, and no highlight is generated
- Implements an "inverse rule" checkbox in the core-side highlight
  rule configuration dialog.

src/common/highlightrulemanager.cpp
src/common/highlightrulemanager.h
src/core/corehighlightrulemanager.h
src/qtui/settingspages/corehighlightsettingspage.cpp
src/qtui/settingspages/corehighlightsettingspage.h
src/qtui/settingspages/corehighlightsettingspage.ui

index 98548bd..7b24ed5 100644 (file)
@@ -56,6 +56,7 @@ QVariantMap HighlightRuleManager::initHighlightRuleList() const
     QVariantList isRegEx;
     QVariantList isCaseSensitive;
     QVariantList isActive;
     QVariantList isRegEx;
     QVariantList isCaseSensitive;
     QVariantList isActive;
+    QVariantList isInverse;
     QStringList channel;
 
     for (int i = 0; i < _highlightRuleList.count(); i++) {
     QStringList channel;
 
     for (int i = 0; i < _highlightRuleList.count(); i++) {
@@ -63,6 +64,7 @@ QVariantMap HighlightRuleManager::initHighlightRuleList() const
         isRegEx << _highlightRuleList[i].isRegEx;
         isCaseSensitive << _highlightRuleList[i].isCaseSensitive;
         isActive << _highlightRuleList[i].isEnabled;
         isRegEx << _highlightRuleList[i].isRegEx;
         isCaseSensitive << _highlightRuleList[i].isCaseSensitive;
         isActive << _highlightRuleList[i].isEnabled;
+        isInverse << _highlightRuleList[i].isInverse;
         channel << _highlightRuleList[i].chanName;
     }
 
         channel << _highlightRuleList[i].chanName;
     }
 
@@ -70,6 +72,7 @@ QVariantMap HighlightRuleManager::initHighlightRuleList() const
     highlightRuleListMap["isRegEx"] = isRegEx;
     highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
     highlightRuleListMap["isEnabled"] = isActive;
     highlightRuleListMap["isRegEx"] = isRegEx;
     highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
     highlightRuleListMap["isEnabled"] = isActive;
+    highlightRuleListMap["isInverse"] = isInverse;
     highlightRuleListMap["channel"] = channel;
     highlightRuleListMap["highlightNick"] = _highlightNick;
     highlightRuleListMap["nicksCaseSensitive"] = _nicksCaseSensitive;
     highlightRuleListMap["channel"] = channel;
     highlightRuleListMap["highlightNick"] = _highlightNick;
     highlightRuleListMap["nicksCaseSensitive"] = _nicksCaseSensitive;
@@ -83,6 +86,7 @@ void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap &highlight
     QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
     QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
     QVariantList isActive = highlightRuleList["isEnabled"].toList();
     QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
     QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
     QVariantList isActive = highlightRuleList["isEnabled"].toList();
+    QVariantList isInverse = highlightRuleList["isInverse"].toList();
     QStringList channel = highlightRuleList["channel"].toStringList();
 
     int count = name.count();
     QStringList channel = highlightRuleList["channel"].toStringList();
 
     int count = name.count();
@@ -95,23 +99,23 @@ void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap &highlight
     _highlightRuleList.clear();
     for (int i = 0; i < name.count(); i++) {
         _highlightRuleList << HighlightRule(name[i], isRegEx[i].toBool(), isCaseSensitive[i].toBool(),
     _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]);
+                                            isActive[i].toBool(), isInverse[i].toBool(), channel[i]);
     }
     _highlightNick = HighlightNickType(highlightRuleList["highlightNick"].toInt());
     _nicksCaseSensitive = highlightRuleList["nicksCaseSensitive"].toBool();
 }
 
 void HighlightRuleManager::addHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive, bool isActive,
     }
     _highlightNick = HighlightNickType(highlightRuleList["highlightNick"].toInt());
     _nicksCaseSensitive = highlightRuleList["nicksCaseSensitive"].toBool();
 }
 
 void HighlightRuleManager::addHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive, bool isActive,
-                                            const QString &channel)
+                                            bool isInverse, const QString &channel)
 {
     if (contains(name)) {
         return;
     }
 
 {
     if (contains(name)) {
         return;
     }
 
-    HighlightRule newItem = HighlightRule(name, isRegEx, isCaseSensitive, isActive, channel);
+    HighlightRule newItem = HighlightRule(name, isRegEx, isCaseSensitive, isActive, isInverse, channel);
     _highlightRuleList << newItem;
 
     _highlightRuleList << newItem;
 
-    SYNC(ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(channel))
+    SYNC(ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(isInverse), ARG(channel))
 }
 
 
 }
 
 
@@ -121,6 +125,47 @@ bool HighlightRuleManager::_match(const QString &msgContents, const QString &msg
        return false;
     }
 
        return false;
     }
 
+    bool matches = false;
+
+    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;
+            }
+        }
+
+        QRegExp rx;
+        if (rule.isRegEx) {
+            rx = QRegExp(rule.name, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+        }
+        else {
+            rx = QRegExp("(^|\\W)" + QRegExp::escape(rule.name) + "(\\W|$)", rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+        }
+        bool match = (rx.indexIn(stripFormatCodes(msgContents)) >= 0);
+        if (match) {
+            // If an inverse rule matches, then we know that we never want to return a highlight.
+            if (rule.isInverse) {
+                return false;
+            } else {
+                matches = true;
+            }
+        }
+    }
+
+    if (matches)
+        return true;
+
     if (!currentNick.isEmpty()) {
         QStringList nickList;
         if (_highlightNick == CurrentNick) {
     if (!currentNick.isEmpty()) {
         QStringList nickList;
         if (_highlightNick == CurrentNick) {
@@ -131,40 +176,10 @@ bool HighlightRuleManager::_match(const QString &msgContents, const QString &msg
             if (!nickList.contains(currentNick))
                 nickList.prepend(currentNick);
         }
             if (!nickList.contains(currentNick))
                 nickList.prepend(currentNick);
         }
-            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;
-                }
-            }
-
-            QRegExp rx;
-            if (rule.isRegEx) {
-                rx = QRegExp(rule.name, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
-            }
-            else {
-                rx = QRegExp("(^|\\W)" + QRegExp::escape(rule.name) + "(\\W|$)", rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
-            }
-            bool match = (rx.indexIn(stripFormatCodes(msgContents)) >= 0);
-            if (match) {
+        for(const QString &nickname : nickList) {
+            QRegExp nickRegExp("(^|\\W)" + QRegExp::escape(nickname) + "(\\W|$)", _nicksCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+            if (nickRegExp.indexIn(stripFormatCodes(msgContents)) >= 0) {
                 return true;
             }
         }
                 return true;
             }
         }
index 75834d1..b606a0a 100644 (file)
@@ -46,11 +46,13 @@ public:
         bool isRegEx = false;
         bool isCaseSensitive = false;
         bool isEnabled = true;
         bool isRegEx = false;
         bool isCaseSensitive = false;
         bool isEnabled = true;
+        bool isInverse = false;
         QString chanName;
         HighlightRule() {}
         HighlightRule(const QString &name_, bool isRegEx_, bool isCaseSensitive_,
         QString chanName;
         HighlightRule() {}
         HighlightRule(const QString &name_, bool isRegEx_, bool isCaseSensitive_,
-                       bool isEnabled_, const QString &chanName_)
-            : name(name_), isRegEx(isRegEx_), isCaseSensitive(isCaseSensitive_), isEnabled(isEnabled_), chanName(chanName_) {
+                       bool isEnabled_, bool isInverse_, const QString &chanName_)
+            : name(name_), isRegEx(isRegEx_), isCaseSensitive(isCaseSensitive_), isEnabled(isEnabled_),
+              isInverse(isInverse_), chanName(chanName_) {
         }
         bool operator!=(const HighlightRule &other)
         {
         }
         bool operator!=(const HighlightRule &other)
         {
@@ -58,6 +60,7 @@ public:
                     isRegEx != other.isRegEx ||
                     isCaseSensitive != other.isCaseSensitive ||
                     isEnabled != other.isEnabled ||
                     isRegEx != other.isRegEx ||
                     isCaseSensitive != other.isCaseSensitive ||
                     isEnabled != other.isEnabled ||
+                    isInverse != other.isInverse ||
                     chanName != other.chanName);
         }
     };
                     chanName != other.chanName);
         }
     };
@@ -111,14 +114,14 @@ public slots:
       * @param chanName The channel in which the rule should apply
       */
     virtual inline void requestAddHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive, bool isEnabled,
       * @param chanName The channel in which the rule should apply
       */
     virtual inline void requestAddHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive, bool isEnabled,
-                                                const QString &chanName)
+                                                bool isInverse, const QString &chanName)
     {
     {
-        REQUEST(ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isEnabled), ARG(chanName))
+        REQUEST(ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isEnabled), ARG(isInverse), ARG(chanName))
     }
 
 
     virtual void addHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive,
     }
 
 
     virtual void addHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive,
-                                  bool isEnabled, const QString &chanName);
+                                  bool isEnabled, bool isInverse, const QString &chanName);
 
     virtual inline void requestSetHighlightNick(HighlightNickType highlightNick)
     {
 
     virtual inline void requestSetHighlightNick(HighlightNickType highlightNick)
     {
@@ -138,7 +141,7 @@ protected:
     bool _match(const QString &msgContents, const QString &msgSender, Message::Type msgType, Message::Flags msgFlags, const QString &bufferName, const QString &currentNick, const QStringList identityNicks);
 
 signals:
     bool _match(const QString &msgContents, const QString &msgSender, Message::Type msgType, Message::Flags msgFlags, const QString &bufferName, const QString &currentNick, const QStringList identityNicks);
 
 signals:
-    void ruleAdded(QString name, bool isRegEx, bool isCaseSensitive, bool isEnabled, QString chanName);
+    void ruleAdded(QString name, bool isRegEx, bool isCaseSensitive, bool isEnabled, bool isInverse, QString chanName);
 
 private:
     HighlightRuleList _highlightRuleList;
 
 private:
     HighlightRuleList _highlightRuleList;
index c8fb450..9cb1b63 100644 (file)
@@ -41,9 +41,9 @@ public slots:
     virtual inline void requestToggleHighlightRule(const QString &highlightRule) { toggleHighlightRule(highlightRule); }
     virtual inline void requestRemoveHighlightRule(const QString &highlightRule) { removeHighlightRule(highlightRule); }
     virtual inline void requestAddHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive,
     virtual inline void requestToggleHighlightRule(const QString &highlightRule) { toggleHighlightRule(highlightRule); }
     virtual inline void requestRemoveHighlightRule(const QString &highlightRule) { removeHighlightRule(highlightRule); }
     virtual inline void requestAddHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive,
-                                                 bool isEnabled, const QString &chanName)
+                                                 bool isEnabled, bool isInverse, const QString &chanName)
     {
     {
-        addHighlightRule(name, isRegEx, isCaseSensitive, isEnabled, chanName);
+        addHighlightRule(name, isRegEx, isCaseSensitive, isEnabled, isInverse, chanName);
     }
 
 
     }
 
 
index bad2400..467991f 100644 (file)
@@ -53,6 +53,7 @@ CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget *parent)
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
+    ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::InverseColumn, QHeaderView::ResizeToContents);
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
 #endif
 
     ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
 #endif
 
@@ -106,7 +107,7 @@ void CoreHighlightSettingsPage::defaults()
 }
 
 
 }
 
 
-void CoreHighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, bool enable, QString chanName, bool self)
+void CoreHighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, bool enable, bool inverse, QString chanName, bool self)
 {
     ui.highlightTable->setRowCount(ui.highlightTable->rowCount()+1);
 
 {
     ui.highlightTable->setRowCount(ui.highlightTable->rowCount()+1);
 
@@ -133,6 +134,13 @@ void CoreHighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, boo
         enableItem->setCheckState(Qt::Unchecked);
     enableItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
 
         enableItem->setCheckState(Qt::Unchecked);
     enableItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
 
+    auto *inverseItem = new QTableWidgetItem("");
+    if (inverse)
+        inverseItem->setCheckState(Qt::Checked);
+    else
+        inverseItem->setCheckState(Qt::Unchecked);
+    inverseItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+
     auto *chanNameItem = new QTableWidgetItem(chanName);
 
     int lastRow = ui.highlightTable->rowCount()-1;
     auto *chanNameItem = new QTableWidgetItem(chanName);
 
     int lastRow = ui.highlightTable->rowCount()-1;
@@ -140,12 +148,13 @@ void CoreHighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, boo
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
+    ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::InverseColumn, inverseItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
 
     if (!self)
         ui.highlightTable->setCurrentItem(nameItem);
 
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
 
     if (!self)
         ui.highlightTable->setCurrentItem(nameItem);
 
-    highlightList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, chanName);
+    highlightList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, inverse, chanName);
 }
 
 
 }
 
 
@@ -215,6 +224,9 @@ void CoreHighlightSettingsPage::tableChanged(QTableWidgetItem *item)
     case CoreHighlightSettingsPage::EnableColumn:
         highlightRule.isEnabled = (item->checkState() == Qt::Checked);
         break;
     case CoreHighlightSettingsPage::EnableColumn:
         highlightRule.isEnabled = (item->checkState() == Qt::Checked);
         break;
+    case CoreHighlightSettingsPage::InverseColumn:
+        highlightRule.isInverse = (item->checkState() == Qt::Checked);
+        break;
     case CoreHighlightSettingsPage::ChanColumn:
         if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
             item->setText("");
     case CoreHighlightSettingsPage::ChanColumn:
         if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
             item->setText("");
@@ -232,7 +244,7 @@ void CoreHighlightSettingsPage::load()
 
     auto ruleManager = Client::highlightRuleManager();
     for (HighlightRuleManager::HighlightRule rule : ruleManager->highlightRuleList()) {
 
     auto ruleManager = Client::highlightRuleManager();
     for (HighlightRuleManager::HighlightRule rule : ruleManager->highlightRuleList()) {
-        addNewRow(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, rule.chanName);
+        addNewRow(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, rule.isInverse, rule.chanName);
     }
 
     switch (ruleManager->highlightNick())
     }
 
     switch (ruleManager->highlightNick())
@@ -270,7 +282,7 @@ void CoreHighlightSettingsPage::save()
     clonedManager.clear();
 
     for (const HighlightRuleManager::HighlightRule &rule : highlightList) {
     clonedManager.clear();
 
     for (const HighlightRuleManager::HighlightRule &rule : highlightList) {
-        clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isRegEx, rule.chanName);
+        clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, rule.isInverse, rule.chanName);
     }
 
     HighlightRuleManager::HighlightNickType highlightNickType = HighlightRuleManager::NoNick;
     }
 
     HighlightRuleManager::HighlightNickType highlightNickType = HighlightRuleManager::NoNick;
index a411662..915ea78 100644 (file)
@@ -47,7 +47,7 @@ public slots:
 
 private slots:
     void widgetHasChanged();
 
 private slots:
     void widgetHasChanged();
-    void addNewRow(QString name = tr("highlight rule"), bool regex = false, bool cs = false, bool enable = true, QString chanName = "", bool self = false);
+    void addNewRow(QString name = tr("highlight rule"), bool regex = false, bool cs = false, bool enable = true, bool inverse = false, QString chanName = "", bool self = false);
     void removeSelectedRows();
     void selectRow(QTableWidgetItem *item);
     void tableChanged(QTableWidgetItem *item);
     void removeSelectedRows();
     void selectRow(QTableWidgetItem *item);
     void tableChanged(QTableWidgetItem *item);
@@ -60,8 +60,9 @@ private:
         RegExColumn = 1,
         CsColumn = 2,
         EnableColumn = 3,
         RegExColumn = 1,
         CsColumn = 2,
         EnableColumn = 3,
-        ChanColumn = 4,
-        ColumnCount = 5
+        InverseColumn = 4,
+        ChanColumn = 5,
+        ColumnCount = 6
     };
 
     void emptyTable();
     };
 
     void emptyTable();
index 42b6041..6e18ecb 100644 (file)
           <string>Enable</string>
          </property>
         </column>
           <string>Enable</string>
          </property>
         </column>
+        <column>
+         <property name="text">
+          <string>Inverse</string>
+         </property>
+        </column>
         <column>
          <property name="text">
           <string>Channel</string>
         <column>
          <property name="text">
           <string>Channel</string>