From: Shane Synan Date: Mon, 28 May 2018 20:33:05 +0000 (-0500) Subject: common: Apply RegEx, CS column to sender, channel X-Git-Tag: travis-deploy-test~89 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=d261638f2e30aa47a08f1c3f43372da0c0e8d13f;ds=sidebyside common: Apply RegEx, CS column to sender, channel Modify scopeMatch to optionally treat the rule as a regular expression, and to treat it as case-sensitive or not. Modify highlight rule processing to... * Use scopeMatch for "Sender", allowing easy multi-sender rules * Treat both "Sender" and "Channel" as regular expressions if "RegEx" is checked, allowing for full regular expression power. (Breaks existing rules with RegEx matching, but in a different way) This means it is no longer possible to use RegEx matching for the "Sender" and "Channel" yet not on the phrase itself. However, it should be easy enough to work around this by using the same phrase regular expression Quassel does. This should simplify common highlight rules without limiting those who know regular expressions. Win-win, hopefully? Update tooltips to reflect these changes. --- diff --git a/src/client/clientignorelistmanager.cpp b/src/client/clientignorelistmanager.cpp index 96216e1c..2f112efa 100644 --- a/src/client/clientignorelistmanager.cpp +++ b/src/client/clientignorelistmanager.cpp @@ -50,8 +50,8 @@ QMap ClientIgnoreListManager::matchingRulesForHostmask(const QStr QMap result; foreach(IgnoreListItem item, ignoreList()) { if (item.type == SenderIgnore && pureMatch(item, hostmask) - && ((network.isEmpty() && channel.isEmpty()) || item.scope == GlobalScope || (item.scope == NetworkScope && scopeMatch(item.scopeRule, network)) - || (item.scope == ChannelScope && scopeMatch(item.scopeRule, channel)))) { + && ((network.isEmpty() && channel.isEmpty()) || item.scope == GlobalScope || (item.scope == NetworkScope && scopeMatch(network, item.scopeRule)) + || (item.scope == ChannelScope && scopeMatch(channel, item.scopeRule)))) { result[item.ignoreRule] = item.isActive; // qDebug() << "matchingRulesForHostmask found: " << item.ignoreRule << "is active: " << item.isActive; } diff --git a/src/common/highlightrulemanager.cpp b/src/common/highlightrulemanager.cpp index 21b89329..3fe3691c 100644 --- a/src/common/highlightrulemanager.cpp +++ b/src/common/highlightrulemanager.cpp @@ -145,7 +145,8 @@ bool HighlightRuleManager::match(const QString &msgContents, if (!rule.isEnabled) continue; - if (!rule.chanName.isEmpty() && !scopeMatch(rule.chanName, bufferName)) { + if (!rule.chanName.isEmpty() + && !scopeMatch(bufferName, rule.chanName, rule.isRegEx, rule.isCaseSensitive)) { // A channel name rule is specified and does NOT match the current buffer name, skip // this rule continue; @@ -163,12 +164,8 @@ bool HighlightRuleManager::match(const QString &msgContents, if (rule.sender.isEmpty()) { senderMatch = true; } else { - if (rule.isRegEx) { - rx = QRegExp(rule.sender, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); - } else { - rx = QRegExp(rule.sender, Qt::CaseInsensitive, QRegExp::Wildcard); - } - senderMatch = rx.exactMatch(msgSender); + // A sender name rule is specified, match according to scope rules. + senderMatch = scopeMatch(msgSender, rule.sender, rule.isRegEx, rule.isCaseSensitive); } if (nameMatch && senderMatch) { diff --git a/src/common/ignorelistmanager.cpp b/src/common/ignorelistmanager.cpp index e2e515cd..b01273ed 100644 --- a/src/common/ignorelistmanager.cpp +++ b/src/common/ignorelistmanager.cpp @@ -136,8 +136,8 @@ IgnoreListManager::StrictnessType IgnoreListManager::_match(const QString &msgCo if (!item.isActive || item.type == CtcpIgnore) continue; if (item.scope == GlobalScope - || (item.scope == NetworkScope && scopeMatch(item.scopeRule, network)) - || (item.scope == ChannelScope && scopeMatch(item.scopeRule, bufferName))) { + || (item.scope == NetworkScope && scopeMatch(network, item.scopeRule)) + || (item.scope == ChannelScope && scopeMatch(bufferName, item.scopeRule))) { QString str; if (item.type == MessageIgnore) str = msgContents; @@ -182,7 +182,7 @@ bool IgnoreListManager::ctcpMatch(const QString sender, const QString &network, foreach(IgnoreListItem item, _ignoreList) { if (!item.isActive) continue; - if (item.scope == GlobalScope || (item.scope == NetworkScope && scopeMatch(item.scopeRule, network))) { + if (item.scope == GlobalScope || (item.scope == NetworkScope && scopeMatch(network, item.scopeRule))) { QString sender_; QStringList types = item.ignoreRule.split(QRegExp("\\s+"), QString::SkipEmptyParts); diff --git a/src/common/util.cpp b/src/common/util.cpp index da3ea10a..b42f4fbf 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -282,57 +282,84 @@ QString formatCurrentDateTimeInString(const QString &formatStr) } -bool scopeMatch(const QString &scopeRule, const QString &string) +bool scopeMatch(const QString &string, const QString &scopeRule, const bool &isRegEx, + const bool &isCaseSensitive) { + // When isRegEx is false: // A match happens when the string does NOT match ANY inverted rules and matches AT LEAST one // normal rule, unless no normal rules exist (implicit wildcard match). This gives inverted // rules higher priority regardless of ordering. // + // When isRegEx is true: + // A match happens when the normal regular expression matches. If prefixed with '!', the match + // happens UNLESS the following regular expression matches. + // TODO: After switching to Qt 5, use of this should be split into two parts, one part that // would generate compiled QRegularExpressions for match/inverted match, regenerating it on any // rule changes, and another part that would check each message against these compiled rules. - // Keep track if any matches are found - bool matches = false; - // Keep track if normal rules and inverted rules are found, allowing for implicit wildcard - bool normalRuleFound = false, invertedRuleFound = false; - - // Split each scope rule by separator, ignoring empty parts - foreach(QString rule, scopeRule.split(";", QString::SkipEmptyParts)) { - // Trim whitespace from the start/end of the rule - rule = rule.trimmed(); - // Ignore empty rules - if (rule.isEmpty()) - continue; + // Cache case sensitivity + Qt::CaseSensitivity ruleExactCase = (isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); + if (isRegEx) { + // Regular expression tests + // ------- // Check if this is an inverted rule (starts with '!') - if (rule.startsWith("!")) { - // Inverted rule found - invertedRuleFound = true; - + if (scopeRule.startsWith("!")) { // Take the reminder of the string - QRegExp ruleRx(rule.mid(1), Qt::CaseInsensitive); - ruleRx.setPatternSyntax(QRegExp::Wildcard); - if (ruleRx.exactMatch(string)) { - // Matches an inverted rule, full rule cannot match - return false; - } + QRegExp ruleRx(scopeRule.mid(1), ruleExactCase); + // Matching an inverted rule: matched (true) implies rule failure (false) + return !ruleRx.exactMatch(string); } else { - // Normal rule found - normalRuleFound = true; - - QRegExp ruleRx(rule, Qt::CaseInsensitive); - ruleRx.setPatternSyntax(QRegExp::Wildcard); - if (ruleRx.exactMatch(string)) { - // Matches a normal rule, full rule might match - matches = true; - // Continue checking in case other inverted rules negate this + QRegExp ruleRx(scopeRule, ruleExactCase); + // Matching a normal rule: matched (true) implies rule success (true) + return ruleRx.exactMatch(string); + } + } else { + // Wildcard expression tests + // ------- + // Keep track if any matches are found + bool matches = false; + // Keep track if normal rules and inverted rules are found, allowing for implicit wildcard + bool normalRuleFound = false, invertedRuleFound = false; + + // Split each scope rule by separator, ignoring empty parts + foreach(QString rule, scopeRule.split(";", QString::SkipEmptyParts)) { + // Trim whitespace from the start/end of the rule + rule = rule.trimmed(); + // Ignore empty rules + if (rule.isEmpty()) + continue; + + // Check if this is an inverted rule (starts with '!') + if (rule.startsWith("!")) { + // Inverted rule found + invertedRuleFound = true; + + // Take the reminder of the string + QRegExp ruleRx(rule.mid(1), ruleExactCase); + ruleRx.setPatternSyntax(QRegExp::Wildcard); + if (ruleRx.exactMatch(string)) { + // Matches an inverted rule, full rule cannot match + return false; + } + } else { + // Normal rule found + normalRuleFound = true; + + QRegExp ruleRx(rule, ruleExactCase); + ruleRx.setPatternSyntax(QRegExp::Wildcard); + if (ruleRx.exactMatch(string)) { + // Matches a normal rule, full rule might match + matches = true; + // Continue checking in case other inverted rules negate this + } } } + // No inverted rules matched, okay to match normally + // Return true if... + // ...we found a normal match + // ...implicit wildcard: we had inverted rules (that didn't match) and no normal rules + return matches || (invertedRuleFound && !normalRuleFound); } - // No inverted rules matched, okay to match normally - // Return true if... - // ...we found a normal match - // ...implicit wildcard: we had inverted rules (that didn't match) and no normal rules - return matches || (invertedRuleFound && !normalRuleFound); } diff --git a/src/common/util.h b/src/common/util.h index 85d79108..8b312f93 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -82,14 +82,22 @@ QString formatCurrentDateTimeInString(const QString &formatStr); /** Check if a scope rule matches a string * + * When isRegEx is false: * Checks that the string does NOT match ANY inverted rules (prefixed by '!'), then checks that * it matches AT LEAST one normal (non-inverted) rule. * * If only inverted rules are specified, it'll match so long as the string does not match any * inverted rules (implicit wildcard). * - * @param scopeRule A ';'-separated list of wildcard expressions, prefix of '!' inverts subrule - * @param string String to test, e.g. network/channel name + * When isRegEx is true: + * Checks that the string matches the entire scopeRule as a regular expression. If scopeRule starts + * with a '!', check that the string does NOT match the regular expression. + * + * @param string String to test, e.g. network/channel name + * @param scopeRule ';'-separated list of wildcard expressions, prefix of '!' inverts subrule + * @param isRegEx If true, treat entire scope rule as regular expression, not wildcards + * @param isCaseSensitive If true, treat as case-sensitive, else case-insensitive * @return True if matches, otherwise false */ -bool scopeMatch(const QString &scopeRule, const QString &string); +bool scopeMatch(const QString &string, const QString &scopeRule, + const bool &isRegEx = false, const bool &isCaseSensitive = false); diff --git a/src/qtui/qtuimessageprocessor.cpp b/src/qtui/qtuimessageprocessor.cpp index 76415cca..0e0dca58 100644 --- a/src/qtui/qtuimessageprocessor.cpp +++ b/src/qtui/qtuimessageprocessor.cpp @@ -142,7 +142,8 @@ void QtUiMessageProcessor::checkForHighlight(Message &msg) continue; if (!rule.chanName.isEmpty() - && !scopeMatch(rule.chanName, msg.bufferInfo().bufferName())) { + && !scopeMatch(msg.bufferInfo().bufferName(), rule.chanName, + rule.isRegExp, rule.caseSensitive)) { // A channel name rule is specified and does NOT match the current buffer name, skip // this rule continue; diff --git a/src/qtui/settingspages/corehighlightsettingspage.cpp b/src/qtui/settingspages/corehighlightsettingspage.cpp index 68d60176..78805cf3 100644 --- a/src/qtui/settingspages/corehighlightsettingspage.cpp +++ b/src/qtui/settingspages/corehighlightsettingspage.cpp @@ -105,29 +105,57 @@ void CoreHighlightSettingsPage::setupRuleTable(QTableWidget *table) const table->verticalHeader()->hide(); table->setShowGrid(false); + table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn)->setToolTip( + tr("Enable/disable this rule")); + table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn)->setWhatsThis( + table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn)->toolTip()); + + table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn)->setToolTip( + tr("Phrase to match")); + table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn)->setWhatsThis( + table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn)->toolTip()); + table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setToolTip( - tr("RegEx: This option determines if the highlight rule should be " - "interpreted as a regular expression or just as a keyword.")); + tr("RegEx: This option determines if the highlight rule, Sender, and " + "Channel should be interpreted as regular expressions or just as " + "keywords.")); table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setWhatsThis( table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->toolTip()); table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setToolTip( - tr("CS: This option determines if the highlight rule should be interpreted " - "case sensitive.")); + tr("CS: This option determines if the highlight rule, Sender, and " + "Channel should be interpreted case sensitive.")); table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setWhatsThis( table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->toolTip()); + table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn)->setToolTip( + tr("

Sender: Semicolon separated list of nick!ident@host names, " + "leave blank to match any nickname.

" + "

Example:
" + "Alice!*; Bob!*@example.com; Carol*!*; !Caroline!*
" + "would match on Alice, Bob with hostmask example.com, and " + "any nickname starting with Carol except for Caroline
" + "

If only inverted names are specified, it will match anything except for " + "what's specified (implicit wildcard).

" + "

Example:
" + "!Announce*!*; !Wheatley!aperture@*
" + "would match anything except for Wheatley with ident aperture or " + "any nickname starting with Announce

")); + table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn)->setWhatsThis( + table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn)->toolTip()); + table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setToolTip( - tr("

Channel: Semicolon separated list of channel names.

" + tr("

Channel: Semicolon separated list of channel names, leave blank to " + "match any name.

" "

Example:
" "#quassel*; #foobar; !#quasseldroid
" - "would match on #foobar and on any channel starting with #quassel except " - "for #quasseldroid
" + "would match on #foobar and any channel starting with #quassel " + "except for #quasseldroid
" "

If only inverted names are specified, it will match anything except for " "what's specified (implicit wildcard).

" "

Example:
" "!#quassel*; !#foobar
" - "would match anything except for #foobar or any channel starting with " + "would match anything except for #foobar or any channel starting with " "#quassel

")); table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setWhatsThis( table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->toolTip()); @@ -221,32 +249,44 @@ void CoreHighlightSettingsPage::addNewHighlightRow(bool enable, const QString &n enableItem->setCheckState(Qt::Unchecked); enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); - auto *chanNameItem = new QTableWidgetItem(chanName); - auto *senderItem = new QTableWidgetItem(sender); + auto *chanNameItem = new QTableWidgetItem(chanName); + enableItem->setToolTip(tr("Enable/disable this rule")); nameItem->setToolTip(tr("Phrase to match")); regexItem->setToolTip( - tr("RegEx: This option determines if the highlight rule should be " - "interpreted as a regular expression or just as a keyword.")); + tr("RegEx: This option determines if the highlight rule, Sender, and " + "Channel should be interpreted as regular expressions or just as " + "keywords.")); csItem->setToolTip( - tr("CS: This option determines if the highlight rule should be interpreted " - "case sensitive.")); + tr("CS: This option determines if the highlight rule, Sender, and " + "Channel should be interpreted case sensitive.")); senderItem->setToolTip( - tr("Sender: This option specifies which sender to match. Leave blank to " - "match any nickname.")); + tr("

Sender: Semicolon separated list of nick!ident@host names, " + "leave blank to match any nickname.

" + "

Example:
" + "Alice!*; Bob!*@example.com; Carol*!*; !Caroline!*
" + "would match on Alice, Bob with hostmask example.com, and " + "any nickname starting with Carol except for Caroline
" + "

If only inverted names are specified, it will match anything except for " + "what's specified (implicit wildcard).

" + "

Example:
" + "!Announce*!*; !Wheatley!aperture@*
" + "would match anything except for Wheatley with ident aperture or " + "any nickname starting with Announce

")); chanNameItem->setToolTip( - tr("

Channel: Semicolon separated list of channel names.

" + tr("

Channel: Semicolon separated list of channel names, leave blank to " + "match any name.

" "

Example:
" "#quassel*; #foobar; !#quasseldroid
" - "would match on #foobar and on any channel starting with #quassel except " - "for #quasseldroid
" + "would match on #foobar and any channel starting with #quassel " + "except for #quasseldroid
" "

If only inverted names are specified, it will match anything except for " "what's specified (implicit wildcard).

" "

Example:
" "!#quassel*; !#foobar
" - "would match anything except for #foobar or any channel starting with " + "would match anything except for #foobar or any channel starting with " "#quassel

")); int lastRow = ui.highlightTable->rowCount() - 1; @@ -310,7 +350,7 @@ void CoreHighlightSettingsPage::addNewIgnoredRow(bool enable, const QString &nam tr("

Channel: Semicolon separated list of channel names.

" "

Example:
" "#quassel*; #foobar; !#quasseldroid
" - "would match on #foobar and on any channel starting with #quassel except " + "would match on #foobar and any channel starting with #quassel except " "for #quasseldroid
" "

If only inverted names are specified, it will match anything except for " "what's specified (implicit wildcard).

" diff --git a/src/qtui/settingspages/highlightsettingspage.cpp b/src/qtui/settingspages/highlightsettingspage.cpp index 7a000e9b..e610d852 100644 --- a/src/qtui/settingspages/highlightsettingspage.cpp +++ b/src/qtui/settingspages/highlightsettingspage.cpp @@ -34,29 +34,41 @@ HighlightSettingsPage::HighlightSettingsPage(QWidget *parent) ui.highlightTable->verticalHeader()->hide(); ui.highlightTable->setShowGrid(false); + + ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::EnableColumn)->setToolTip( + tr("Enable/disable this rule")); + ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::EnableColumn)->setWhatsThis( + ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::EnableColumn)->toolTip()); + + ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::NameColumn)->setToolTip( + tr("Phrase to match")); + ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::NameColumn)->setWhatsThis( + ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::NameColumn)->toolTip()); + ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->setToolTip( - tr("RegEx: This option determines if the highlight rule should be " - "interpreted as a regular expression or just as a keyword.")); + tr("RegEx: This option determines if the highlight rule and Channel " + "should be interpreted as regular expressions or just as keywords.")); ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->setWhatsThis( ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->toolTip()); ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->setToolTip( - tr("CS: This option determines if the highlight rule should be interpreted " - "case sensitive.")); + tr("CS: This option determines if the highlight rule and Channel " + "should be interpreted case sensitive.")); ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->setWhatsThis( ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->toolTip()); ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->setToolTip( - tr("

Channel: Semicolon separated list of channel names.

" + tr("

Channel: Semicolon separated list of channel names, leave blank to " + "match any name.

" "

Example:
" "#quassel*; #foobar; !#quasseldroid
" - "would match on #foobar and on any channel starting with #quassel except " - "for #quasseldroid
" + "would match on #foobar and any channel starting with #quassel " + "except for #quasseldroid
" "

If only inverted names are specified, it will match anything except for " "what's specified (implicit wildcard).

" "

Example:
" "!#quassel*; !#foobar
" - "would match anything except for #foobar or any channel starting with " + "would match anything except for #foobar or any channel starting with " "#quassel

")); ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->setWhatsThis( ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->toolTip()); @@ -152,22 +164,23 @@ void HighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, bool en enableItem->setToolTip(tr("Enable/disable this rule")); nameItem->setToolTip(tr("Phrase to match")); regexItem->setToolTip( - tr("RegEx: This option determines if the highlight rule should be " - "interpreted as a regular expression or just as a keyword.")); + tr("RegEx: This option determines if the highlight rule and Channel " + "should be interpreted as regular expressions or just as keywords.")); csItem->setToolTip( - tr("CS: This option determines if the highlight rule should be interpreted " - "case sensitive.")); + tr("CS: This option determines if the highlight rule and Channel " + "should be interpreted case sensitive.")); chanNameItem->setToolTip( - tr("

Channel: Semicolon separated list of channel names.

" + tr("

Channel: Semicolon separated list of channel names, leave blank to " + "match any name.

" "

Example:
" "#quassel*; #foobar; !#quasseldroid
" - "would match on #foobar and on any channel starting with #quassel except " - "for #quasseldroid
" + "would match on #foobar and any channel starting with #quassel " + "except for #quasseldroid
" "

If only inverted names are specified, it will match anything except for " "what's specified (implicit wildcard).

" "

Example:
" "!#quassel*; !#foobar
" - "would match anything except for #foobar or any channel starting with " + "would match anything except for #foobar or any channel starting with " "#quassel

")); int lastRow = ui.highlightTable->rowCount()-1; diff --git a/src/qtui/settingspages/ignorelisteditdlg.ui b/src/qtui/settingspages/ignorelisteditdlg.ui index 5cf60701..1e893a6e 100644 --- a/src/qtui/settingspages/ignorelisteditdlg.ui +++ b/src/qtui/settingspages/ignorelisteditdlg.ui @@ -186,14 +186,14 @@ Whenever you disable/delete the ignore rule, the messages are shown again.</p <br /> <i>#quassel*; #foobar; !#quasseldroid</i> <br /> -would match on #foobar and on any channel starting with <i>#quassel</i> except for <i>#quasseldroid</i> +would match on <i>#foobar</i> and any channel starting with <i>#quassel</i> except for <i>#quasseldroid</i> <br /> <p>If only inverted names are specified, it will match anything except for what's specified (implicit wildcard).</p> <p><i>Example:</i> <br /> <i>!#quassel*; !#foobar</i> <br /> -would match anything except for #foobar or any channel starting with <i>#quassel</i></p> +would match anything except for <i>#foobar</i> or any channel starting with <i>#quassel</i></p>