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