d8d8859acac0b754de5613128c1b77ff98cfac54
[quassel.git] / src / common / highlightrulemanager.h
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 #pragma once
22
23 #include <utility>
24
25 #include <QString>
26 #include <QStringList>
27 #include <QVariantList>
28 #include <QVariantMap>
29
30 #include "expressionmatch.h"
31 #include "message.h"
32 #include "nickhighlightmatcher.h"
33 #include "syncableobject.h"
34
35 class HighlightRuleManager : public SyncableObject
36 {
37     Q_OBJECT
38     SYNCABLE_OBJECT
39
40     Q_PROPERTY(int highlightNick READ highlightNick WRITE setHighlightNick)
41     Q_PROPERTY(bool nicksCaseSensitive READ nicksCaseSensitive WRITE setNicksCaseSensitive)
42
43 public:
44     enum HighlightNickType {
45         NoNick = 0x00,
46         CurrentNick = 0x01,
47         AllNicks = 0x02
48     };
49
50     inline HighlightRuleManager(QObject *parent = nullptr) : SyncableObject(parent) { setAllowClientUpdates(true); }
51     HighlightRuleManager &operator=(const HighlightRuleManager &other);
52
53     /**
54      * Individual highlight rule
55      */
56     class HighlightRule
57     {
58     public:
59         /**
60          * Construct an empty highlight rule
61          */
62         HighlightRule() {}
63
64         /**
65          * Construct a highlight rule with the given parameters
66          *
67          * @param id               Integer ID of the rule
68          * @param contents         String representing a message contents expression to match
69          * @param isRegEx          True if regular expression, otherwise false
70          * @param isCaseSensitive  True if case sensitive, otherwise false
71          * @param isEnabled        True if enabled, otherwise false
72          * @param isInverse        True if rule is treated as highlight ignore, otherwise false
73          * @param sender           String representing a message sender expression to match
74          * @param chanName         String representing a channel name expression to match
75          */
76         HighlightRule(int id, QString contents, bool isRegEx, bool isCaseSensitive, bool isEnabled,
77                       bool isInverse, QString sender, QString chanName)
78             : _id(id), _contents(contents), _isRegEx(isRegEx), _isCaseSensitive(isCaseSensitive),
79               _isEnabled(isEnabled), _isInverse(isInverse), _sender(sender), _chanName(chanName)
80         {
81             _cacheInvalid = true;
82             // Cache expression matches on construction
83             //
84             // This provides immediate feedback on errors when loading the rule.  If profiling shows
85             // this as a performance bottleneck, this can be removed in deference to caching on
86             // first use.
87             //
88             // Inversely, if needed for validity checks, caching can be done on every update below
89             // instead of on first use.
90             determineExpressions();
91         }
92
93         /**
94          * Gets the unique ID of this rule
95          *
96          * @return Integer ID of the rule
97          */
98         inline int id() const {
99             return _id;
100         }
101         /**
102          * Sets the ID of this rule
103          *
104          * CAUTION: IDs should be kept unique!
105          *
106          * @param id Integer ID of the rule
107          */
108         inline void setId(int id) {
109             _id = id;
110         }
111
112         /**
113          * Gets the message contents this rule matches
114          *
115          * NOTE: Use HighlightRule::contentsMatcher() for performing matches
116          *
117          * @return String representing a phrase or expression to match
118          */
119         inline QString contents() const {
120             return _contents;
121         }
122         /**
123          * Sets the message contents this rule matches
124          *
125          * @param contents String representing a phrase or expression to match
126          */
127         inline void setContents(const QString &contents) {
128             _contents = contents;
129             _cacheInvalid = true;
130         }
131
132         /**
133          * Gets if this is a regular expression rule
134          *
135          * @return True if regular expression, otherwise false
136          */
137         inline bool isRegEx() const {
138             return _isRegEx;
139         }
140         /**
141          * Sets if this rule is a regular expression rule
142          *
143          * @param isRegEx True if regular expression, otherwise false
144          */
145         inline void setIsRegEx(bool isRegEx) {
146             _isRegEx = isRegEx;
147             _cacheInvalid = true;
148         }
149
150         /**
151          * Gets if this rule is case sensitive
152          *
153          * @return True if case sensitive, otherwise false
154          */
155         inline bool isCaseSensitive() const {
156             return _isCaseSensitive;
157         }
158         /**
159          * Sets if this rule is case sensitive
160          *
161          * @param isCaseSensitive True if case sensitive, otherwise false
162          */
163         inline void setIsCaseSensitive(bool isCaseSensitive) {
164             _isCaseSensitive = isCaseSensitive;
165             _cacheInvalid = true;
166         }
167
168         /**
169          * Gets if this rule is enabled and active
170          *
171          * @return True if enabled, otherwise false
172          */
173         inline bool isEnabled() const {
174             return _isEnabled;
175         }
176         /**
177          * Sets if this rule is enabled and active
178          *
179          * @param isEnabled True if enabled, otherwise false
180          */
181         inline void setIsEnabled(bool isEnabled) {
182             _isEnabled = isEnabled;
183         }
184
185         /**
186          * Gets if this rule is a highlight ignore rule
187          *
188          * @return True if rule is treated as highlight ignore, otherwise false
189          */
190         inline bool isInverse() const {
191             return _isInverse;
192         }
193         /**
194          * Sets if this rule is a highlight ignore rule
195          *
196          * @param isInverse True if rule is treated as highlight ignore, otherwise false
197          */
198         inline void setIsInverse(bool isInverse) {
199             _isInverse = isInverse;
200         }
201
202         /**
203          * Gets the message sender this rule matches
204          *
205          * NOTE: Use HighlightRule::senderMatcher() for performing matches
206          *
207          * @return String representing a phrase or expression to match
208          */
209         inline QString sender() const { return _sender; }
210         /**
211          * Sets the message sender this rule matches
212          *
213          * @param sender String representing a phrase or expression to match
214          */
215         inline void setSender(const QString &sender) {
216             _sender = sender;
217             _cacheInvalid = true;
218         }
219
220         /**
221          * Gets the channel name this rule matches
222          *
223          * NOTE: Use HighlightRule::chanNameMatcher() for performing matches
224          *
225          * @return String representing a phrase or expression to match
226          */
227         inline QString chanName() const { return _chanName; }
228         /**
229          * Sets the channel name this rule matches
230          *
231          * @param chanName String representing a phrase or expression to match
232          */
233         inline void setChanName(const QString &chanName) {
234             _chanName = chanName;
235             _cacheInvalid = true;
236         }
237
238         /**
239          * Gets the expression matcher for the message contents, caching if needed
240          *
241          * @return Expression matcher to compare with message contents
242          */
243         inline ExpressionMatch contentsMatcher() const {
244             if (_cacheInvalid) {
245                 determineExpressions();
246             }
247             return _contentsMatch;
248         }
249
250         /**
251          * Gets the expression matcher for the message sender, caching if needed
252          *
253          * @return Expression matcher to compare with message sender
254          */
255         inline ExpressionMatch senderMatcher() const {
256             if (_cacheInvalid) {
257                 determineExpressions();
258             }
259             return _senderMatch;
260         }
261
262         /**
263          * Gets the expression matcher for the channel name, caching if needed
264          *
265          * @return Expression matcher to compare with channel name
266          */
267         inline ExpressionMatch chanNameMatcher() const {
268             if (_cacheInvalid) {
269                 determineExpressions();
270             }
271             return _chanNameMatch;
272         }
273
274         bool operator!=(const HighlightRule &other) const;
275
276     private:
277         /**
278          * Update internal cache of expression matching if needed
279          */
280         void determineExpressions() const;
281
282         int _id = -1;
283         QString _contents = {};
284         bool _isRegEx = false;
285         bool _isCaseSensitive = false;
286         bool _isEnabled = true;
287         bool _isInverse = false;
288         QString _sender = {};
289         QString _chanName = {};
290
291         // These represent internal cache and should be safe to mutate in 'const' functions
292         // See https://stackoverflow.com/questions/3141087/what-is-meant-with-const-at-end-of-function-declaration
293         mutable bool _cacheInvalid = true;           ///< If true, match cache needs redone
294         mutable ExpressionMatch _contentsMatch = {}; ///< Expression match cache for message content
295         mutable ExpressionMatch _senderMatch = {};   ///< Expression match cache for sender
296         mutable ExpressionMatch _chanNameMatch = {}; ///< Expression match cache for channel name
297     };
298
299     using HighlightRuleList = QList<HighlightRule>;
300
301     int indexOf(int rule) const;
302     inline bool contains(int rule) const { return indexOf(rule) != -1; }
303     inline bool isEmpty() const { return _highlightRuleList.isEmpty(); }
304     inline int count() const { return _highlightRuleList.count(); }
305     inline void removeAt(int index) { _highlightRuleList.removeAt(index); }
306     inline void clear() { _highlightRuleList.clear(); }
307     inline HighlightRule &operator[](int i) { return _highlightRuleList[i]; }
308     inline const HighlightRule &operator[](int i) const { return _highlightRuleList.at(i); }
309     inline const HighlightRuleList &highlightRuleList() const { return _highlightRuleList; }
310
311     int nextId();
312
313     inline int highlightNick() { return _highlightNick; }
314     inline bool nicksCaseSensitive() { return _nicksCaseSensitive; }
315
316     //! Check if a message matches the HighlightRule
317     /** This method checks if a message matches the users highlight rules.
318       * \param msg The Message that should be checked
319       */
320     bool match(const Message &msg, const QString &currentNick, const QStringList &identityNicks);
321
322 public slots:
323     virtual QVariantMap initHighlightRuleList() const;
324     virtual void initSetHighlightRuleList(const QVariantMap &HighlightRuleList);
325
326     //! Request removal of an ignore rule based on the rule itself.
327     /** Use this method if you want to remove a single ignore rule
328       * and get that synced with the core immediately.
329       * \param highlightRule A valid ignore rule
330       */
331     virtual inline void requestRemoveHighlightRule(int highlightRule) { REQUEST(ARG(highlightRule)) }
332     virtual void removeHighlightRule(int highlightRule);
333
334     //! Request toggling of "isEnabled" flag of a given ignore rule.
335     /** Use this method if you want to toggle the "isEnabled" flag of a single ignore rule
336       * and get that synced with the core immediately.
337       * \param highlightRule A valid ignore rule
338       */
339     virtual inline void requestToggleHighlightRule(int highlightRule) { REQUEST(ARG(highlightRule)) }
340     virtual void toggleHighlightRule(int highlightRule);
341
342     //! Request an HighlightRule to be added to the ignore list
343     /** Items added to the list with this method, get immediately synced with the core
344       * \param name The rule
345       * \param isRegEx If the rule should be interpreted as a nickname, or a regex
346       * \param isCaseSensitive If the rule should be interpreted as case-sensitive
347       * \param isEnabled If the rule is active
348       * @param chanName The channel in which the rule should apply
349       */
350     virtual inline void requestAddHighlightRule(int id, const QString &name, bool isRegEx, bool isCaseSensitive, bool isEnabled,
351                                                 bool isInverse, const QString &sender, const QString &chanName)
352     {
353         REQUEST(ARG(id), ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isEnabled), ARG(isInverse), ARG(sender),
354                 ARG(chanName))
355     }
356
357
358     virtual void addHighlightRule(int id, const QString &name, bool isRegEx, bool isCaseSensitive, bool isEnabled,
359                                   bool isInverse, const QString &sender, const QString &chanName);
360
361     virtual inline void requestSetHighlightNick(int highlightNick)
362     {
363         REQUEST(ARG(highlightNick))
364     }
365
366     inline void setHighlightNick(int highlightNick) {
367         _highlightNick = static_cast<HighlightNickType>(highlightNick);
368         // Convert from HighlightRuleManager::HighlightNickType to
369         // NickHighlightMatcher::HighlightNickType
370         _nickMatcher.setHighlightMode(
371                     static_cast<NickHighlightMatcher::HighlightNickType>(_highlightNick));
372     }
373
374     virtual inline void requestSetNicksCaseSensitive(bool nicksCaseSensitive)
375     {
376         REQUEST(ARG(nicksCaseSensitive))
377     }
378
379     inline void setNicksCaseSensitive(bool nicksCaseSensitive) {
380         _nicksCaseSensitive = nicksCaseSensitive;
381         // Update nickname matcher, too
382         _nickMatcher.setCaseSensitive(nicksCaseSensitive);
383     }
384
385     /**
386      * Network removed from system
387      *
388      * Handles cleaning up cache from stale networks.
389      *
390      * @param id Network ID of removed network
391      */
392     inline void networkRemoved(NetworkId id) {
393         // Clean up nickname matching cache
394         _nickMatcher.removeNetwork(id);
395     }
396
397 protected:
398     void setHighlightRuleList(const QList<HighlightRule> &HighlightRuleList) { _highlightRuleList = HighlightRuleList; }
399
400     bool match(const NetworkId &netId,
401                const QString &msgContents,
402                const QString &msgSender,
403                Message::Type msgType,
404                Message::Flags msgFlags,
405                const QString &bufferName,
406                const QString &currentNick,
407                const QStringList &identityNicks);
408
409 signals:
410     void ruleAdded(QString name, bool isRegEx, bool isCaseSensitive, bool isEnabled, bool isInverse, QString sender, QString chanName);
411
412 private:
413     HighlightRuleList _highlightRuleList = {}; ///< Custom highlight rule list
414     NickHighlightMatcher _nickMatcher = {};    ///< Nickname highlight matcher
415
416     /// Nickname highlighting mode
417     HighlightNickType _highlightNick = HighlightNickType::CurrentNick;
418     bool _nicksCaseSensitive = false; ///< If true, match nicknames with exact case
419 };