1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "qtuimessageprocessor.h"
24 #include "clientsettings.h"
26 #include "messagemodel.h"
29 QtUiMessageProcessor::QtUiMessageProcessor(QObject *parent)
30 : AbstractMessageProcessor(parent),
32 _processMode(TimerBased)
34 NotificationSettings notificationSettings;
35 _nicksCaseSensitive = notificationSettings.nicksCaseSensitive();
36 _highlightNick = notificationSettings.highlightNick();
37 highlightListChanged(notificationSettings.highlightList());
38 notificationSettings.notify("Highlights/NicksCaseSensitive", this, SLOT(nicksCaseSensitiveChanged(const QVariant &)));
39 notificationSettings.notify("Highlights/CustomList", this, SLOT(highlightListChanged(const QVariant &)));
40 notificationSettings.notify("Highlights/HighlightNick", this, SLOT(highlightNickChanged(const QVariant &)));
42 _processTimer.setInterval(0);
43 connect(&_processTimer, SIGNAL(timeout()), this, SLOT(processNextMessage()));
47 void QtUiMessageProcessor::reset()
49 if (processMode() == TimerBased) {
50 if (_processTimer.isActive()) _processTimer.stop();
52 _currentBatch.clear();
53 _processQueue.clear();
58 void QtUiMessageProcessor::process(Message &msg)
60 checkForHighlight(msg);
62 Client::messageModel()->insertMessage(msg);
66 void QtUiMessageProcessor::process(QList<Message> &msgs)
68 QList<Message>::iterator msgIter = msgs.begin();
69 QList<Message>::iterator msgIterEnd = msgs.end();
70 while (msgIter != msgIterEnd) {
71 checkForHighlight(*msgIter);
75 Client::messageModel()->insertMessages(msgs);
78 if (msgs.isEmpty()) return;
79 _processQueue.append(msgs);
85 void QtUiMessageProcessor::startProcessing()
87 if (processMode() == TimerBased) {
88 if (_currentBatch.isEmpty() && _processQueue.isEmpty())
91 if (!_processTimer.isActive())
92 _processTimer.start();
97 void QtUiMessageProcessor::processNextMessage()
99 if (_currentBatch.isEmpty()) {
100 if (_processQueue.isEmpty()) {
101 _processTimer.stop();
105 _currentBatch = _processQueue.takeFirst();
107 Message msg = _currentBatch.takeFirst();
112 void QtUiMessageProcessor::checkForHighlight(Message &msg)
114 if (!((msg.type() & (Message::Plain | Message::Notice | Message::Action)) && !(msg.flags() & Message::Self)))
117 // TODO: Cache this (per network)
118 const Network *net = Client::network(msg.bufferInfo().networkId());
119 if (net && !net->myNick().isEmpty()) {
121 QString currentNick = net->myNick();
122 // Get identity nicks
123 QStringList identityNicks = {};
124 const Identity *myIdentity = Client::identity(net->identity());
126 identityNicks = myIdentity->nicks();
129 // Get buffer name, message contents
130 QString bufferName = msg.bufferInfo().bufferName();
131 QString msgContents = msg.contents();
132 bool matches = false;
134 for (int i = 0; i < _highlightRuleList.count(); i++) {
135 auto &rule = _highlightRuleList.at(i);
136 if (!rule.isEnabled())
139 // Skip if channel name doesn't match and channel rule is not empty
141 // Match succeeds if...
142 // Channel name matches a defined rule
143 // Defined rule is empty
144 // And take the inverse of the above
145 if (!rule.chanNameMatcher().match(bufferName, true)) {
146 // A channel name rule is specified and does NOT match the current buffer name, skip
151 // Check message according to specified rule, allowing empty rules to match
152 bool contentsMatch = rule.contentsMatcher().match(stripFormatCodes(msgContents), true);
154 // Support for sender matching can be added here
157 // Support for inverse rules can be added here
163 msg.setFlags(msg.flags() | Message::Highlight);
168 if (_highlightNick != HighlightNickType::NoNick && !currentNick.isEmpty()) {
169 // Update cache if needed
170 determineNickExpressions(currentNick, identityNicks);
173 if (_cachedNickMatcher.isValid()
174 && _cachedNickMatcher.match(stripFormatCodes(msgContents))) {
175 // Nick matcher is valid and match found
176 msg.setFlags(msg.flags() | Message::Highlight);
184 void QtUiMessageProcessor::nicksCaseSensitiveChanged(const QVariant &variant)
186 _nicksCaseSensitive = variant.toBool();
187 _cacheNickConfigInvalid = true;
191 void QtUiMessageProcessor::highlightListChanged(const QVariant &variant)
193 QVariantList varList = variant.toList();
195 _highlightRuleList.clear();
196 QVariantList::const_iterator iter = varList.constBegin();
197 while (iter != varList.constEnd()) {
198 QVariantMap rule = iter->toMap();
199 _highlightRuleList << LegacyHighlightRule(rule["Name"].toString(),
200 rule["RegEx"].toBool(),
202 rule["Enable"].toBool(),
203 rule["Channel"].toString());
209 void QtUiMessageProcessor::highlightNickChanged(const QVariant &variant)
211 _highlightNick = (NotificationSettings::HighlightNickType)variant.toInt();
212 _cacheNickConfigInvalid = true;
216 void QtUiMessageProcessor::determineNickExpressions(const QString ¤tNick,
217 const QStringList identityNicks) const
219 // Don't do anything for no nicknames
220 if (_highlightNick == HighlightNickType::NoNick) {
224 // Only update if needed (check nickname config, current nick, identity nicks for change)
225 if (!_cacheNickConfigInvalid
226 && _cachedNickCurrent == currentNick
227 && _cachedIdentityNicks == identityNicks) {
232 QStringList nickList;
233 if (_highlightNick == HighlightNickType::CurrentNick) {
234 nickList << currentNick;
236 else if (_highlightNick == HighlightNickType::AllNicks) {
237 nickList = identityNicks;
238 if (!nickList.contains(currentNick))
239 nickList.prepend(currentNick);
242 // Set up phrase matcher, joining with newlines
243 _cachedNickMatcher = ExpressionMatch(nickList.join("\n"),
244 ExpressionMatch::MatchMode::MatchMultiPhrase,
245 _nicksCaseSensitive);
247 _cacheNickConfigInvalid = false;
248 _cachedNickCurrent = currentNick;
249 _cachedIdentityNicks = identityNicks;
253 /**************************************************************************
254 * LegacyHighlightRule
255 *************************************************************************/
256 bool QtUiMessageProcessor::LegacyHighlightRule::operator!=(const LegacyHighlightRule &other) const
258 return (_contents != other._contents ||
259 _isRegEx != other._isRegEx ||
260 _isCaseSensitive != other._isCaseSensitive ||
261 _isEnabled != other._isEnabled ||
262 _chanName != other._chanName);
263 // Don't compare ExpressionMatch objects as they are created as needed from the above
267 void QtUiMessageProcessor::LegacyHighlightRule::determineExpressions() const
269 // Don't update if not needed
270 if (!_cacheInvalid) {
274 // Set up matching rules
275 // Message is either phrase or regex
276 ExpressionMatch::MatchMode contentsMode =
277 _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
278 ExpressionMatch::MatchMode::MatchPhrase;
279 // Sender (when added) and channel are either multiple wildcard entries or regex
280 ExpressionMatch::MatchMode scopeMode =
281 _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
282 ExpressionMatch::MatchMode::MatchMultiWildcard;
284 _contentsMatch = ExpressionMatch(_contents, contentsMode, _isCaseSensitive);
285 _chanNameMatch = ExpressionMatch(_chanName, scopeMode, _isCaseSensitive);
287 _cacheInvalid = false;