cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / common / highlightrulemanager.h
1 /***************************************************************************
2  *   Copyright (C) 2005-2020 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     {
48         NoNick = 0x00,
49         CurrentNick = 0x01,
50         AllNicks = 0x02
51     };
52
53     inline HighlightRuleManager(QObject* parent = nullptr)
54         : SyncableObject(parent)
55     {
56         setAllowClientUpdates(true);
57     }
58
59     /**
60      * Individual highlight rule
61      */
62     class COMMON_EXPORT HighlightRule
63     {
64     public:
65         /**
66          * Construct an empty highlight rule
67          */
68         HighlightRule() = default;
69
70         /**
71          * Construct a highlight rule with the given parameters
72          *
73          * @param id               Integer ID of the rule
74          * @param contents         String representing a message contents expression to match
75          * @param isRegEx          True if regular expression, otherwise false
76          * @param isCaseSensitive  True if case sensitive, otherwise false
77          * @param isEnabled        True if enabled, otherwise false
78          * @param isInverse        True if rule is treated as highlight ignore, otherwise false
79          * @param sender           String representing a message sender expression to match
80          * @param chanName         String representing a channel name expression to match
81          */
82         HighlightRule(
83             int id, QString contents, bool isRegEx, bool isCaseSensitive, bool isEnabled, bool isInverse, QString sender, QString chanName)
84             : _id(id)
85             , _contents(std::move(contents))
86             , _isRegEx(isRegEx)
87             , _isCaseSensitive(isCaseSensitive)
88             , _isEnabled(isEnabled)
89             , _isInverse(isInverse)
90             , _sender(std::move(sender))
91             , _chanName(std::move(chanName))
92         {
93             _cacheInvalid = true;
94             // Cache expression matches on construction
95             //
96             // This provides immediate feedback on errors when loading the rule.  If profiling shows
97             // this as a performance bottleneck, this can be removed in deference to caching on
98             // first use.
99             //
100             // Inversely, if needed for validity checks, caching can be done on every update below
101             // instead of on first use.
102             determineExpressions();
103         }
104
105         /**
106          * Gets the unique ID of this rule
107          *
108          * @return Integer ID of the rule
109          */
110         inline int id() const { return _id; }
111         /**
112          * Sets the ID of this rule
113          *
114          * CAUTION: IDs should be kept unique!
115          *
116          * @param id Integer ID of the rule
117          */
118         inline void setId(int id) { _id = id; }
119
120         /**
121          * Gets the message contents this rule matches
122          *
123          * NOTE: Use HighlightRule::contentsMatcher() for performing matches
124          *
125          * @return String representing a phrase or expression to match
126          */
127         inline QString contents() const { return _contents; }
128         /**
129          * Sets the message contents this rule matches
130          *
131          * @param contents String representing a phrase or expression to match
132          */
133         inline void setContents(const QString& contents)
134         {
135             _contents = contents;
136             _cacheInvalid = true;
137         }
138
139         /**
140          * Gets if this is a regular expression rule
141          *
142          * @return True if regular expression, otherwise false
143          */
144         inline bool isRegEx() const { return _isRegEx; }
145         /**
146          * Sets if this rule is a regular expression rule
147          *
148          * @param isRegEx True if regular expression, otherwise false
149          */
150         inline void setIsRegEx(bool isRegEx)
151         {
152             _isRegEx = isRegEx;
153             _cacheInvalid = true;
154         }
155
156         /**
157          * Gets if this rule is case sensitive
158          *
159          * @return True if case sensitive, otherwise false
160          */
161         inline bool isCaseSensitive() const { return _isCaseSensitive; }
162         /**
163          * Sets if this rule is case sensitive
164          *
165          * @param isCaseSensitive True if case sensitive, otherwise false
166          */
167         inline void setIsCaseSensitive(bool isCaseSensitive)
168         {
169             _isCaseSensitive = isCaseSensitive;
170             _cacheInvalid = true;
171         }
172
173         /**
174          * Gets if this rule is enabled and active
175          *
176          * @return True if enabled, otherwise false
177          */
178         inline bool isEnabled() const { return _isEnabled; }
179         /**
180          * Sets if this rule is enabled and active
181          *
182          * @param isEnabled True if enabled, otherwise false
183          */
184         inline void setIsEnabled(bool isEnabled) { _isEnabled = isEnabled; }
185
186         /**
187          * Gets if this rule is a highlight ignore rule
188          *
189          * @return True if rule is treated as highlight ignore, otherwise false
190          */
191         inline bool isInverse() const { return _isInverse; }
192         /**
193          * Sets if this rule is a highlight ignore rule
194          *
195          * @param isInverse True if rule is treated as highlight ignore, otherwise false
196          */
197         inline void setIsInverse(bool isInverse) { _isInverse = isInverse; }
198
199         /**
200          * Gets the message sender this rule matches
201          *
202          * NOTE: Use HighlightRule::senderMatcher() for performing matches
203          *
204          * @return String representing a phrase or expression to match
205          */
206         inline QString sender() const { return _sender; }
207         /**
208          * Sets the message sender this rule matches
209          *
210          * @param sender String representing a phrase or expression to match
211          */
212         inline void setSender(const QString& sender)
213         {
214             _sender = sender;
215             _cacheInvalid = true;
216         }
217
218         /**
219          * Gets the channel name this rule matches
220          *
221          * NOTE: Use HighlightRule::chanNameMatcher() for performing matches
222          *
223          * @return String representing a phrase or expression to match
224          */
225         inline QString chanName() const { return _chanName; }
226         /**
227          * Sets the channel name this rule matches
228          *
229          * @param chanName String representing a phrase or expression to match
230          */
231         inline void setChanName(const QString& chanName)
232         {
233             _chanName = chanName;
234             _cacheInvalid = true;
235         }
236
237         /**
238          * Gets the expression matcher for the message contents, caching if needed
239          *
240          * @return Expression matcher to compare with message contents
241          */
242         inline ExpressionMatch contentsMatcher() const
243         {
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         {
257             if (_cacheInvalid) {
258                 determineExpressions();
259             }
260             return _senderMatch;
261         }
262
263         /**
264          * Gets the expression matcher for the channel name, caching if needed
265          *
266          * @return Expression matcher to compare with channel name
267          */
268         inline ExpressionMatch chanNameMatcher() const
269         {
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,
353                                                 const QString& name,
354                                                 bool isRegEx,
355                                                 bool isCaseSensitive,
356                                                 bool isEnabled,
357                                                 bool isInverse,
358                                                 const QString& sender,
359                                                 const QString& chanName)
360     {
361         REQUEST(ARG(id), ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isEnabled), ARG(isInverse), ARG(sender), ARG(chanName))
362     }
363
364     virtual void addHighlightRule(int id,
365                                   const QString& name,
366                                   bool isRegEx,
367                                   bool isCaseSensitive,
368                                   bool isEnabled,
369                                   bool isInverse,
370                                   const QString& sender,
371                                   const QString& chanName);
372
373     virtual inline void requestSetHighlightNick(int highlightNick) { REQUEST(ARG(highlightNick)) }
374
375     inline void setHighlightNick(int highlightNick)
376     {
377         _highlightNick = static_cast<HighlightNickType>(highlightNick);
378         // Convert from HighlightRuleManager::HighlightNickType to
379         // NickHighlightMatcher::HighlightNickType
380         _nickMatcher.setHighlightMode(static_cast<NickHighlightMatcher::HighlightNickType>(_highlightNick));
381     }
382
383     virtual inline void requestSetNicksCaseSensitive(bool nicksCaseSensitive) { REQUEST(ARG(nicksCaseSensitive)) }
384
385     inline void setNicksCaseSensitive(bool nicksCaseSensitive)
386     {
387         _nicksCaseSensitive = nicksCaseSensitive;
388         // Update nickname matcher, too
389         _nickMatcher.setCaseSensitive(nicksCaseSensitive);
390     }
391
392     /**
393      * Network removed from system
394      *
395      * Handles cleaning up cache from stale networks.
396      *
397      * @param id Network ID of removed network
398      */
399     inline void networkRemoved(NetworkId id)
400     {
401         // Clean up nickname matching cache
402         _nickMatcher.removeNetwork(id);
403     }
404
405 protected:
406     void setHighlightRuleList(const QList<HighlightRule>& HighlightRuleList) { _highlightRuleList = HighlightRuleList; }
407
408     bool match(const NetworkId& netId,
409                const QString& msgContents,
410                const QString& msgSender,
411                Message::Type msgType,
412                Message::Flags msgFlags,
413                const QString& bufferName,
414                const QString& currentNick,
415                const QStringList& identityNicks);
416
417 signals:
418     void ruleAdded(QString name, bool isRegEx, bool isCaseSensitive, bool isEnabled, bool isInverse, QString sender, QString chanName);
419
420 private:
421     HighlightRuleList _highlightRuleList = {};  ///< Custom highlight rule list
422     NickHighlightMatcher _nickMatcher = {};     ///< Nickname highlight matcher
423
424     /// Nickname highlighting mode
425     HighlightNickType _highlightNick = HighlightNickType::CurrentNick;
426     bool _nicksCaseSensitive = false;  ///< If true, match nicknames with exact case
427 };