X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fqtuimessageprocessor.cpp;h=f1eb47f68e5c62a5bc1d84356e268957ec67c4d5;hp=f06030d3bf89101e8555661e5647731fb01445c1;hb=54ebc1bf00f4f9a8376629925329f0e72be04662;hpb=019a59ffca44ddc32fc6b16fd6cdcc8f3e1c93c6 diff --git a/src/qtui/qtuimessageprocessor.cpp b/src/qtui/qtuimessageprocessor.cpp index f06030d3..f1eb47f6 100644 --- a/src/qtui/qtuimessageprocessor.cpp +++ b/src/qtui/qtuimessageprocessor.cpp @@ -1,22 +1,22 @@ /*************************************************************************** -* Copyright (C) 2005-08 by the Quassel Project * -* devel@quassel-irc.org * -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) version 3. * -* * -* This program is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* GNU General Public License for more details. * -* * -* You should have received a copy of the GNU General Public License * -* along with this program; if not, write to the * -* Free Software Foundation, Inc., * -* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * -***************************************************************************/ + * Copyright (C) 2005-2018 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ #include "qtuimessageprocessor.h" @@ -26,94 +26,263 @@ #include "messagemodel.h" #include "network.h" -QtUiMessageProcessor::QtUiMessageProcessor(QObject *parent) : AbstractMessageProcessor(parent) { - _processing = false; - _processMode = TimerBased; - _processTimer.setInterval(0); - connect(&_processTimer, SIGNAL(timeout()), this, SLOT(processNextMessage())); +QtUiMessageProcessor::QtUiMessageProcessor(QObject *parent) + : AbstractMessageProcessor(parent), + _processing(false), + _processMode(TimerBased) +{ + NotificationSettings notificationSettings; + _nicksCaseSensitive = notificationSettings.nicksCaseSensitive(); + _highlightNick = notificationSettings.highlightNick(); + highlightListChanged(notificationSettings.highlightList()); + notificationSettings.notify("Highlights/NicksCaseSensitive", this, SLOT(nicksCaseSensitiveChanged(const QVariant &))); + notificationSettings.notify("Highlights/CustomList", this, SLOT(highlightListChanged(const QVariant &))); + notificationSettings.notify("Highlights/HighlightNick", this, SLOT(highlightNickChanged(const QVariant &))); + + _processTimer.setInterval(0); + connect(&_processTimer, SIGNAL(timeout()), this, SLOT(processNextMessage())); } -void QtUiMessageProcessor::reset() { - if(processMode() == TimerBased) { - if(_processTimer.isActive()) _processTimer.stop(); - _processing = false; - _currentBatch.clear(); - _processQueue.clear(); - } + +void QtUiMessageProcessor::reset() +{ + if (processMode() == TimerBased) { + if (_processTimer.isActive()) _processTimer.stop(); + _processing = false; + _currentBatch.clear(); + _processQueue.clear(); + } } -void QtUiMessageProcessor::process(Message &msg) { - checkForHighlight(msg); - Client::messageModel()->insertMessage(msg); - postProcess(msg); + +void QtUiMessageProcessor::process(Message &msg) +{ + checkForHighlight(msg); + preProcess(msg); + Client::messageModel()->insertMessage(msg); } -void QtUiMessageProcessor::process(QList &msgs) { - _processQueue.append(msgs); - if(!isProcessing()) startProcessing(); + +void QtUiMessageProcessor::process(QList &msgs) +{ + QList::iterator msgIter = msgs.begin(); + QList::iterator msgIterEnd = msgs.end(); + while (msgIter != msgIterEnd) { + checkForHighlight(*msgIter); + preProcess(*msgIter); + ++msgIter; + } + Client::messageModel()->insertMessages(msgs); + return; + + if (msgs.isEmpty()) return; + _processQueue.append(msgs); + if (!isProcessing()) + startProcessing(); } -void QtUiMessageProcessor::startProcessing() { - if(processMode() == TimerBased) { - if(_currentBatch.isEmpty() && _processQueue.isEmpty()) return; - _processing = true; - if(!_processTimer.isActive()) _processTimer.start(); - } + +void QtUiMessageProcessor::startProcessing() +{ + if (processMode() == TimerBased) { + if (_currentBatch.isEmpty() && _processQueue.isEmpty()) + return; + _processing = true; + if (!_processTimer.isActive()) + _processTimer.start(); + } } -void QtUiMessageProcessor::processNextMessage() { - if(_currentBatch.isEmpty()) { - if(_processQueue.isEmpty()) { - _processTimer.stop(); - _processing = false; - return; + +void QtUiMessageProcessor::processNextMessage() +{ + if (_currentBatch.isEmpty()) { + if (_processQueue.isEmpty()) { + _processTimer.stop(); + _processing = false; + return; + } + _currentBatch = _processQueue.takeFirst(); } - _currentBatch = _processQueue.takeFirst(); - } - Message msg = _currentBatch.takeFirst(); - process(msg); + Message msg = _currentBatch.takeFirst(); + process(msg); } -// TODO optimize checkForHighlight -void QtUiMessageProcessor::checkForHighlight(Message &msg) { - if(!((msg.type() & (Message::Plain | Message::Notice | Message::Action)) && !(msg.flags() & Message::Self))) - return; - NotificationSettings notificationSettings; - const Network *net = Client::network(msg.bufferInfo().networkId()); - if(net && !net->myNick().isEmpty()) { - QStringList nickList; - if(notificationSettings.highlightNick() == NotificationSettings::CurrentNick) { - nickList << net->myNick(); - } else if(notificationSettings.highlightNick() == NotificationSettings::AllNicks) { - const Identity *myIdentity = Client::identity(net->identity()); - if(myIdentity) - nickList = myIdentity->nicks(); +void QtUiMessageProcessor::checkForHighlight(Message &msg) +{ + if (!((msg.type() & (Message::Plain | Message::Notice | Message::Action)) && !(msg.flags() & Message::Self))) + return; + + // TODO: Cache this (per network) + const Network *net = Client::network(msg.bufferInfo().networkId()); + if (net && !net->myNick().isEmpty()) { + // Get current nick + QString currentNick = net->myNick(); + // Get identity nicks + QStringList identityNicks = {}; + const Identity *myIdentity = Client::identity(net->identity()); + if (myIdentity) { + identityNicks = myIdentity->nicks(); + } + + // Get buffer name, message contents + QString bufferName = msg.bufferInfo().bufferName(); + QString msgContents = msg.contents(); + 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; + } + + // Check message according to specified rule, allowing empty rules to match + bool contentsMatch = rule.contentsMatcher().match(stripFormatCodes(msgContents), true); + + // Support for sender matching can be added here + + if (contentsMatch) { + // Support for inverse rules can be added here + matches = true; + } + } + + if (matches) { + msg.setFlags(msg.flags() | Message::Highlight); + return; + } + + // Check nicknames + if (_highlightNick != HighlightNickType::NoNick && !currentNick.isEmpty()) { + // Update cache if needed + determineNickExpressions(currentNick, identityNicks); + + // Check for a match + if (_cachedNickMatcher.isValid() + && _cachedNickMatcher.match(stripFormatCodes(msgContents))) { + // Nick matcher is valid and match found + msg.setFlags(msg.flags() | Message::Highlight); + return; + } + } } - foreach(QString nickname, nickList) { - QRegExp nickRegExp("^(.*\\W)?" + QRegExp::escape(nickname) + "(\\W.*)?$"); - if(nickRegExp.exactMatch(msg.contents())) { - msg.setFlags(msg.flags() | Message::Highlight); +} + + +void QtUiMessageProcessor::nicksCaseSensitiveChanged(const QVariant &variant) +{ + _nicksCaseSensitive = variant.toBool(); + _cacheNickConfigInvalid = true; +} + + +void QtUiMessageProcessor::highlightListChanged(const QVariant &variant) +{ + QVariantList varList = variant.toList(); + + _highlightRuleList.clear(); + QVariantList::const_iterator iter = varList.constBegin(); + while (iter != varList.constEnd()) { + QVariantMap rule = iter->toMap(); + _highlightRuleList << LegacyHighlightRule(rule["Name"].toString(), + rule["RegEx"].toBool(), + rule["CS"].toBool(), + rule["Enable"].toBool(), + rule["Channel"].toString()); + ++iter; + } +} + + +void QtUiMessageProcessor::highlightNickChanged(const QVariant &variant) +{ + _highlightNick = (NotificationSettings::HighlightNickType)variant.toInt(); + _cacheNickConfigInvalid = true; +} + + +void QtUiMessageProcessor::determineNickExpressions(const QString ¤tNick, + const QStringList identityNicks) const +{ + // Don't do anything for no nicknames + if (_highlightNick == HighlightNickType::NoNick) { return; - } } - foreach(QVariant highlight, notificationSettings.highlightList()) { - QVariantMap highlightRule = highlight.toMap(); - if(!highlightRule["enable"].toBool()) - continue; - Qt::CaseSensitivity caseSensitivity = highlightRule["cs"].toBool() ? Qt::CaseSensitive : Qt::CaseInsensitive; - QString name = highlightRule["name"].toString(); - QRegExp userRegExp; - if(highlightRule["regex"].toBool()) { - userRegExp = QRegExp(name, caseSensitivity); - } else { - userRegExp = QRegExp("^(.*\\W)?" + QRegExp::escape(name) + "(\\W.*)?$", caseSensitivity); - } - if(userRegExp.exactMatch(msg.contents())) { - msg.setFlags(msg.flags() | Message::Highlight); + // Only update if needed (check nickname config, current nick, identity nicks for change) + if (!_cacheNickConfigInvalid + && _cachedNickCurrent == currentNick + && _cachedIdentityNicks == identityNicks) { return; - } } - } + + // Add all nicknames + QStringList nickList; + if (_highlightNick == HighlightNickType::CurrentNick) { + nickList << currentNick; + } + else if (_highlightNick == HighlightNickType::AllNicks) { + nickList = identityNicks; + if (!nickList.contains(currentNick)) + nickList.prepend(currentNick); + } + + // Set up phrase matcher, joining with newlines + _cachedNickMatcher = ExpressionMatch(nickList.join("\n"), + ExpressionMatch::MatchMode::MatchMultiPhrase, + _nicksCaseSensitive); + + _cacheNickConfigInvalid = false; + _cachedNickCurrent = currentNick; + _cachedIdentityNicks = identityNicks; +} + + +/************************************************************************** + * LegacyHighlightRule + *************************************************************************/ +bool QtUiMessageProcessor::LegacyHighlightRule::operator!=(const LegacyHighlightRule &other) const +{ + return (_contents != other._contents || + _isRegEx != other._isRegEx || + _isCaseSensitive != other._isCaseSensitive || + _isEnabled != other._isEnabled || + _chanName != other._chanName); + // Don't compare ExpressionMatch objects as they are created as needed from the above +} + + +void QtUiMessageProcessor::LegacyHighlightRule::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 (when added) and channel are either multiple wildcard entries or regex + ExpressionMatch::MatchMode scopeMode = + _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx : + ExpressionMatch::MatchMode::MatchMultiWildcard; + + _contentsMatch = ExpressionMatch(_contents, contentsMode, _isCaseSensitive); + _chanNameMatch = ExpressionMatch(_chanName, scopeMode, _isCaseSensitive); + + _cacheInvalid = false; }