modernize: Prefer default member init over ctor init
[quassel.git] / src / qtui / qtuimessageprocessor.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include "qtuimessageprocessor.h"
22
23 #include "client.h"
24 #include "clientsettings.h"
25 #include "identity.h"
26 #include "messagemodel.h"
27 #include "network.h"
28
29 QtUiMessageProcessor::QtUiMessageProcessor(QObject *parent)
30     : AbstractMessageProcessor(parent),
31     _processing(false),
32     _processMode(TimerBased)
33 {
34     NotificationSettings notificationSettings;
35     _nicksCaseSensitive = notificationSettings.nicksCaseSensitive();
36     _nickMatcher.setCaseSensitive(_nicksCaseSensitive);
37     _highlightNick = notificationSettings.highlightNick();
38     _nickMatcher.setHighlightMode(
39                 static_cast<NickHighlightMatcher::HighlightNickType>(_highlightNick));
40     highlightListChanged(notificationSettings.highlightList());
41     notificationSettings.notify("Highlights/NicksCaseSensitive", this, SLOT(nicksCaseSensitiveChanged(const QVariant &)));
42     notificationSettings.notify("Highlights/CustomList", this, SLOT(highlightListChanged(const QVariant &)));
43     notificationSettings.notify("Highlights/HighlightNick", this, SLOT(highlightNickChanged(const QVariant &)));
44
45     _processTimer.setInterval(0);
46     connect(&_processTimer, SIGNAL(timeout()), this, SLOT(processNextMessage()));
47 }
48
49
50 void QtUiMessageProcessor::reset()
51 {
52     if (processMode() == TimerBased) {
53         if (_processTimer.isActive()) _processTimer.stop();
54         _processing = false;
55         _currentBatch.clear();
56         _processQueue.clear();
57     }
58 }
59
60
61 void QtUiMessageProcessor::process(Message &msg)
62 {
63     checkForHighlight(msg);
64     preProcess(msg);
65     Client::messageModel()->insertMessage(msg);
66 }
67
68
69 void QtUiMessageProcessor::process(QList<Message> &msgs)
70 {
71     QList<Message>::iterator msgIter = msgs.begin();
72     QList<Message>::iterator msgIterEnd = msgs.end();
73     while (msgIter != msgIterEnd) {
74         checkForHighlight(*msgIter);
75         preProcess(*msgIter);
76         ++msgIter;
77     }
78     Client::messageModel()->insertMessages(msgs);
79     return;
80
81     if (msgs.isEmpty()) return;
82     _processQueue.append(msgs);
83     if (!isProcessing())
84         startProcessing();
85 }
86
87
88 void QtUiMessageProcessor::startProcessing()
89 {
90     if (processMode() == TimerBased) {
91         if (_currentBatch.isEmpty() && _processQueue.isEmpty())
92             return;
93         _processing = true;
94         if (!_processTimer.isActive())
95             _processTimer.start();
96     }
97 }
98
99
100 void QtUiMessageProcessor::processNextMessage()
101 {
102     if (_currentBatch.isEmpty()) {
103         if (_processQueue.isEmpty()) {
104             _processTimer.stop();
105             _processing = false;
106             return;
107         }
108         _currentBatch = _processQueue.takeFirst();
109     }
110     Message msg = _currentBatch.takeFirst();
111     process(msg);
112 }
113
114
115 void QtUiMessageProcessor::checkForHighlight(Message &msg)
116 {
117     if (!((msg.type() & (Message::Plain | Message::Notice | Message::Action)) && !(msg.flags() & Message::Self)))
118         return;
119
120     // Cached per network
121     const NetworkId &netId = msg.bufferInfo().networkId();
122     const Network *net = Client::network(netId);
123
124     if (net && !net->myNick().isEmpty()) {
125         // Get current nick
126         QString currentNick = net->myNick();
127         // Get identity nicks
128         QStringList identityNicks = {};
129         const Identity *myIdentity = Client::identity(net->identity());
130         if (myIdentity) {
131             identityNicks = myIdentity->nicks();
132         }
133
134         // Get buffer name, message contents
135         QString bufferName = msg.bufferInfo().bufferName();
136         QString msgContents = msg.contents();
137         bool matches = false;
138
139         for (int i = 0; i < _highlightRuleList.count(); i++) {
140             auto &rule = _highlightRuleList.at(i);
141             if (!rule.isEnabled())
142                 continue;
143
144             // Skip if channel name doesn't match and channel rule is not empty
145             //
146             // Match succeeds if...
147             //   Channel name matches a defined rule
148             //   Defined rule is empty
149             // And take the inverse of the above
150             if (!rule.chanNameMatcher().match(bufferName, true)) {
151                 // A channel name rule is specified and does NOT match the current buffer name, skip
152                 // this rule
153                 continue;
154             }
155
156             // Check message according to specified rule, allowing empty rules to match
157             bool contentsMatch = rule.contentsMatcher().match(stripFormatCodes(msgContents), true);
158
159             // Support for sender matching can be added here
160
161             if (contentsMatch) {
162                 // Support for inverse rules can be added here
163                 matches = true;
164             }
165         }
166
167         if (matches) {
168             msg.setFlags(msg.flags() | Message::Highlight);
169             return;
170         }
171
172         // Check nicknames
173         if (_highlightNick != HighlightNickType::NoNick && !currentNick.isEmpty()) {
174             // Nickname matching allowed and current nickname is known
175             // Run the nickname matcher on the unformatted string
176             if (_nickMatcher.match(stripFormatCodes(msgContents), netId, currentNick,
177                                    identityNicks)) {
178                 msg.setFlags(msg.flags() | Message::Highlight);
179                 return;
180             }
181         }
182     }
183 }
184
185
186 void QtUiMessageProcessor::nicksCaseSensitiveChanged(const QVariant &variant)
187 {
188     _nicksCaseSensitive = variant.toBool();
189     // Update nickname matcher, too
190     _nickMatcher.setCaseSensitive(_nicksCaseSensitive);
191 }
192
193
194 void QtUiMessageProcessor::highlightListChanged(const QVariant &variant)
195 {
196     QVariantList varList = variant.toList();
197
198     _highlightRuleList.clear();
199     QVariantList::const_iterator iter = varList.constBegin();
200     while (iter != varList.constEnd()) {
201         QVariantMap rule = iter->toMap();
202         _highlightRuleList << LegacyHighlightRule(rule["Name"].toString(),
203                 rule["RegEx"].toBool(),
204                 rule["CS"].toBool(),
205                 rule["Enable"].toBool(),
206                 rule["Channel"].toString());
207         ++iter;
208     }
209 }
210
211
212 void QtUiMessageProcessor::highlightNickChanged(const QVariant &variant)
213 {
214     _highlightNick = (HighlightNickType)variant.toInt();
215     // Convert from QtUiMessageProcessor::HighlightNickType (which is from NotificationSettings) to
216     // NickHighlightMatcher::HighlightNickType
217     _nickMatcher.setHighlightMode(
218                 static_cast<NickHighlightMatcher::HighlightNickType>(_highlightNick));
219 }
220
221
222 /**************************************************************************
223  * LegacyHighlightRule
224  *************************************************************************/
225 bool QtUiMessageProcessor::LegacyHighlightRule::operator!=(const LegacyHighlightRule &other) const
226 {
227     return (_contents != other._contents ||
228             _isRegEx != other._isRegEx ||
229             _isCaseSensitive != other._isCaseSensitive ||
230             _isEnabled != other._isEnabled ||
231             _chanName != other._chanName);
232     // Don't compare ExpressionMatch objects as they are created as needed from the above
233 }
234
235
236 void QtUiMessageProcessor::LegacyHighlightRule::determineExpressions() const
237 {
238     // Don't update if not needed
239     if (!_cacheInvalid) {
240         return;
241     }
242
243     // Set up matching rules
244     // Message is either phrase or regex
245     ExpressionMatch::MatchMode contentsMode =
246             _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
247                        ExpressionMatch::MatchMode::MatchPhrase;
248     // Sender (when added) and channel are either multiple wildcard entries or regex
249     ExpressionMatch::MatchMode scopeMode =
250             _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx :
251                        ExpressionMatch::MatchMode::MatchMultiWildcard;
252
253     _contentsMatch = ExpressionMatch(_contents, contentsMode, _isCaseSensitive);
254     _chanNameMatch = ExpressionMatch(_chanName, scopeMode, _isCaseSensitive);
255
256     _cacheInvalid = false;
257 }