1 /***************************************************************************
2 * Copyright (C) 2005-2019 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 _nickMatcher.setCaseSensitive(_nicksCaseSensitive);
37 _highlightNick = notificationSettings.highlightNick();
38 _nickMatcher.setHighlightMode(static_cast<NickHighlightMatcher::HighlightNickType>(_highlightNick));
39 highlightListChanged(notificationSettings.highlightList());
40 notificationSettings.notify("Highlights/NicksCaseSensitive", this, &QtUiMessageProcessor::nicksCaseSensitiveChanged);
41 notificationSettings.notify("Highlights/CustomList", this, &QtUiMessageProcessor::highlightListChanged);
42 notificationSettings.notify("Highlights/HighlightNick", this, &QtUiMessageProcessor::highlightNickChanged);
44 _processTimer.setInterval(0);
45 connect(&_processTimer, &QTimer::timeout, this, &QtUiMessageProcessor::processNextMessage);
48 void QtUiMessageProcessor::reset()
50 if (processMode() == TimerBased) {
51 if (_processTimer.isActive())
54 _currentBatch.clear();
55 _processQueue.clear();
59 void QtUiMessageProcessor::process(Message& msg)
61 checkForHighlight(msg);
63 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);
80 _processQueue.append(msgs);
85 void QtUiMessageProcessor::startProcessing()
87 if (processMode() == TimerBased) {
88 if (_currentBatch.isEmpty() && _processQueue.isEmpty())
91 if (!_processTimer.isActive())
92 _processTimer.start();
96 void QtUiMessageProcessor::processNextMessage()
98 if (_currentBatch.isEmpty()) {
99 if (_processQueue.isEmpty()) {
100 _processTimer.stop();
104 _currentBatch = _processQueue.takeFirst();
106 Message msg = _currentBatch.takeFirst();
110 void QtUiMessageProcessor::checkForHighlight(Message& msg)
112 if (!((msg.type() & (Message::Plain | Message::Notice | Message::Action)) && !(msg.flags() & Message::Self)))
115 // Cached per network
116 const NetworkId& netId = msg.bufferInfo().networkId();
117 const Network* net = Client::network(netId);
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 // Nickname matching allowed and current nickname is known
170 // Run the nickname matcher on the unformatted string
171 if (_nickMatcher.match(stripFormatCodes(msgContents), netId, currentNick, identityNicks)) {
172 msg.setFlags(msg.flags() | Message::Highlight);
179 void QtUiMessageProcessor::nicksCaseSensitiveChanged(const QVariant& variant)
181 _nicksCaseSensitive = variant.toBool();
182 // Update nickname matcher, too
183 _nickMatcher.setCaseSensitive(_nicksCaseSensitive);
186 void QtUiMessageProcessor::highlightListChanged(const QVariant& variant)
188 QVariantList varList = variant.toList();
190 _highlightRuleList.clear();
191 QVariantList::const_iterator iter = varList.constBegin();
192 while (iter != varList.constEnd()) {
193 QVariantMap rule = iter->toMap();
194 _highlightRuleList << LegacyHighlightRule(rule["Name"].toString(),
195 rule["RegEx"].toBool(),
197 rule["Enable"].toBool(),
198 rule["Channel"].toString());
203 void QtUiMessageProcessor::highlightNickChanged(const QVariant& variant)
205 _highlightNick = (HighlightNickType)variant.toInt();
206 // Convert from QtUiMessageProcessor::HighlightNickType (which is from NotificationSettings) to
207 // NickHighlightMatcher::HighlightNickType
208 _nickMatcher.setHighlightMode(static_cast<NickHighlightMatcher::HighlightNickType>(_highlightNick));
211 void QtUiMessageProcessor::networkRemoved(NetworkId id)
213 // Clean up nickname matching cache
214 _nickMatcher.removeNetwork(id);
217 /**************************************************************************
218 * LegacyHighlightRule
219 *************************************************************************/
220 bool QtUiMessageProcessor::LegacyHighlightRule::operator!=(const LegacyHighlightRule& other) const
222 return (_contents != other._contents || _isRegEx != other._isRegEx || _isCaseSensitive != other._isCaseSensitive
223 || _isEnabled != other._isEnabled || _chanName != other._chanName);
224 // Don't compare ExpressionMatch objects as they are created as needed from the above
227 void QtUiMessageProcessor::LegacyHighlightRule::determineExpressions() const
229 // Don't update if not needed
230 if (!_cacheInvalid) {
234 // Set up matching rules
235 // Message is either phrase or regex
236 ExpressionMatch::MatchMode contentsMode = _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx : ExpressionMatch::MatchMode::MatchPhrase;
237 // Sender (when added) and channel are either multiple wildcard entries or regex
238 ExpressionMatch::MatchMode scopeMode = _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx : ExpressionMatch::MatchMode::MatchMultiWildcard;
240 _contentsMatch = ExpressionMatch(_contents, contentsMode, _isCaseSensitive);
241 _chanNameMatch = ExpressionMatch(_chanName, scopeMode, _isCaseSensitive);
243 _cacheInvalid = false;