uisupport: Provide helpers for dealing with widget changes
[quassel.git] / src / common / ignorelistmanager.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 <QString>
26 #include <QStringList>
27 #include <QRegExp>
28 #include <utility>
29
30 #include "expressionmatch.h"
31 #include "message.h"
32 #include "syncableobject.h"
33
34 class COMMON_EXPORT IgnoreListManager : public SyncableObject
35 {
36     Q_OBJECT
37     SYNCABLE_OBJECT
38
39 public:
40     inline IgnoreListManager(QObject *parent = nullptr) : SyncableObject(parent) { setAllowClientUpdates(true); }
41     IgnoreListManager &operator=(const IgnoreListManager &other);
42
43     enum IgnoreType {
44         SenderIgnore,
45         MessageIgnore,
46         CtcpIgnore
47     };
48
49     enum StrictnessType {
50         UnmatchedStrictness = 0,
51         SoftStrictness = 1,
52         HardStrictness = 2
53     };
54
55     enum ScopeType {
56         GlobalScope,
57         NetworkScope,
58         ChannelScope,
59     };
60
61     /**
62      * Individual ignore list rule
63      */
64     class COMMON_EXPORT IgnoreListItem {
65     public:
66         /**
67          * Construct an empty ignore rule
68          */
69         IgnoreListItem() = default;
70
71         /**
72          * Construct an ignore rule with the given parameters
73          *
74          * CAUTION: For legacy reasons, "contents" doubles as the identifier for the ignore rule.
75          * Duplicate entries are not allowed.
76          *
77          * @param type             Type of ignore rule
78          * @param contents         String representing a message contents expression to match
79          * @param isRegEx          True if regular expression, otherwise false
80          * @param strictness       Strictness of ignore rule
81          * @param scope            What to match scope rule against
82          * @param scopeRule        String representing a scope rule expression to match
83          * @param isEnabled        True if enabled, otherwise false
84          */
85         IgnoreListItem(IgnoreType type, QString contents, bool isRegEx,
86                        StrictnessType strictness, ScopeType scope, QString scopeRule,
87                        bool isEnabled)
88             : _contents(std::move(contents)), _isRegEx(isRegEx), _strictness(strictness),
89               _scope(scope), _scopeRule(std::move(scopeRule)), _isEnabled(isEnabled)
90         {
91             // Allow passing empty "contents" as they can happen when editing an ignore rule
92
93             // Handle CTCP ignores
94             setType(type);
95
96             _cacheInvalid = true;
97             // Cache expression matches on construction
98             //
99             // This provides immediate feedback on errors when loading the rule.  If profiling shows
100             // this as a performance bottleneck, this can be removed in deference to caching on
101             // first use.
102             //
103             // Inversely, if needed for validity checks, caching can be done on every update below
104             // instead of on first use.
105             determineExpressions();
106         }
107
108         /**
109          * Gets the type of this ignore rule
110          *
111          * @return IgnoreType of the rule
112          */
113         inline IgnoreType type() const {
114             return _type;
115         }
116         /**
117          * Sets the type of this ignore rule
118          *
119          * @param type IgnoreType of the rule
120          */
121         inline void setType(IgnoreType type) {
122             // Handle CTCP ignores
123             if (type == CtcpIgnore) {
124                 // This is not performance-intensive; sticking with QRegExp for Qt 4 is fine
125                 // Split based on whitespace characters
126                 QStringList split(contents().split(QRegExp("\\s+"), QString::SkipEmptyParts));
127                 // Match on the first item, handling empty rules/matches
128                 if (!split.isEmpty()) {
129                     // Take the first item as the sender
130                     _cacheCtcpSender = split.takeFirst();
131                     // Track the rest as CTCP types to ignore
132                     _cacheCtcpTypes = split;
133                 }
134                 else {
135                     // No match found - this can happen if a pure whitespace CTCP ignore rule is
136                     // created.  Fall back to matching all senders.
137                     if (_isRegEx) {
138                         // RegEx match everything
139                         _cacheCtcpSender = ".*";
140                     }
141                     else {
142                         // Wildcard match everything
143                         _cacheCtcpSender = "*";
144                     }
145                     // Clear the types (split is already empty)
146                     _cacheCtcpTypes = split;
147                 }
148             }
149             _type = type;
150         }
151
152         /**
153          * Gets the message contents this rule matches
154          *
155          * NOTE: Use IgnoreListItem::contentsMatcher() for performing matches
156          *
157          * CAUTION: For legacy reasons, "contents" doubles as the identifier for the ignore rule.
158          * Duplicate entries are not allowed.
159          *
160          * @return String representing a phrase or expression to match
161          */
162         inline QString contents() const {
163             return _contents;
164         }
165         /**
166          * Sets the message contents this rule matches
167          *
168          * @param contents String representing a phrase or expression to match
169          */
170         inline void setContents(const QString &contents) {
171             // Allow passing empty "contents" as they can happen when editing an ignore rule
172             _contents = contents;
173             _cacheInvalid = true;
174         }
175
176         /**
177          * Gets if this is a regular expression rule
178          *
179          * @return True if regular expression, otherwise false
180          */
181         inline bool isRegEx() const {
182             return _isRegEx;
183         }
184         /**
185          * Sets if this rule is a regular expression rule
186          *
187          * @param isRegEx True if regular expression, otherwise false
188          */
189         inline void setIsRegEx(bool isRegEx) {
190             _isRegEx = isRegEx;
191             _cacheInvalid = true;
192         }
193
194         /**
195          * Gets the strictness of this ignore rule
196          *
197          * @return StrictnessType of the rule
198          */
199         inline StrictnessType strictness() const {
200             return _strictness;
201         }
202         /**
203          * Sets the strictness of this ignore rule
204          *
205          * @param strictness StrictnessType of the rule
206          */
207         inline void setStrictness(StrictnessType strictness) {
208             _strictness = strictness;
209         }
210
211         /**
212          * Gets what to match scope rule against
213          *
214          * @return ScopeType of the rule
215          */
216         inline ScopeType scope() const {
217             return _scope;
218         }
219         /**
220          * Sets what to match scope rule against
221          *
222          * @param type ScopeType of the rule
223          */
224         inline void setScope(ScopeType scope) {
225             _scope = scope;
226         }
227
228         /**
229          * Gets the scope rule this rule matches
230          *
231          * NOTE: Use IgnoreListItem::scopeRuleMatcher() for performing matches
232          *
233          * @return String representing a phrase or expression to match
234          */
235         inline QString scopeRule() const {
236             return _scopeRule;
237         }
238         /**
239          * Sets the scope rule this rule matches
240          *
241          * @param scopeRule String representing a phrase or expression to match
242          */
243         inline void setScopeRule(const QString &scopeRule) {
244             _scopeRule = scopeRule;
245             _cacheInvalid = true;
246         }
247
248         /**
249          * Gets if this rule is enabled and active
250          *
251          * @return True if enabled, otherwise false
252          */
253         inline bool isEnabled() const {
254             return _isEnabled;
255         }
256         /**
257          * Sets if this rule is enabled and active
258          *
259          * @param isEnabled True if enabled, otherwise false
260          */
261         inline void setIsEnabled(bool isEnabled) {
262             _isEnabled = isEnabled;
263         }
264
265         /**
266          * Gets the ignored CTCP types for CTCP ignores
267          *
268          * @return List of CTCP types to ignore, or empty for all
269          */
270         inline QStringList ctcpTypes() const {
271             return _cacheCtcpTypes;
272         }
273
274         /**
275          * Gets the expression matcher for the message contents, caching if needed
276          *
277          * @return Expression matcher to compare with message contents
278          */
279         inline ExpressionMatch contentsMatcher() const {
280             if (_cacheInvalid) {
281                 determineExpressions();
282             }
283             return _contentsMatch;
284         }
285
286         /**
287          * Gets the expression matcher for the scope, caching if needed
288          *
289          * @return Expression matcher to compare with scope
290          */
291         inline ExpressionMatch scopeRuleMatcher() const {
292             if (_cacheInvalid) {
293                 determineExpressions();
294             }
295             return _scopeRuleMatch;
296         }
297
298         /**
299          * Gets the expression matcher for the message contents, caching if needed
300          *
301          * @return Expression matcher to compare with message contents
302          */
303         inline ExpressionMatch senderCTCPMatcher() const {
304             if (_cacheInvalid) {
305                 determineExpressions();
306             }
307             return _ctcpSenderMatch;
308         }
309
310         bool operator!=(const IgnoreListItem &other) const;
311
312     private:
313         /**
314          * Update internal cache of expression matching if needed
315          */
316         void determineExpressions() const;
317
318         IgnoreType _type = {};
319         QString _contents = {};
320         bool _isRegEx = false;
321         StrictnessType _strictness = {};
322         ScopeType _scope = {};
323         QString _scopeRule = {};
324         bool _isEnabled = true;
325
326         QString _cacheCtcpSender = {};                    ///< For CTCP rules, precalculate sender
327         QStringList _cacheCtcpTypes = {};                 ///< For CTCP rules, precalculate types
328
329         // These represent internal cache and should be safe to mutate in 'const' functions
330         // See https://stackoverflow.com/questions/3141087/what-is-meant-with-const-at-end-of-function-declaration
331         mutable bool _cacheInvalid = true;                ///< If true, match cache needs redone
332         mutable ExpressionMatch _contentsMatch = {};      ///< Expression match cache for message
333         mutable ExpressionMatch _scopeRuleMatch = {};     ///< Expression match cache for scope rule
334         mutable ExpressionMatch _ctcpSenderMatch = {};    ///< Expression match cache for CTCP nick
335     };
336
337     using IgnoreList = QList<IgnoreListItem>;
338
339     int indexOf(const QString &ignore) const;
340     inline bool contains(const QString &ignore) const { return indexOf(ignore) != -1; }
341     inline bool isEmpty() const { return _ignoreList.isEmpty(); }
342     inline int count() const { return _ignoreList.count(); }
343     inline void removeAt(int index) { _ignoreList.removeAt(index); }
344     inline IgnoreListItem &operator[](int i) { return _ignoreList[i]; }
345     inline const IgnoreListItem &operator[](int i) const { return _ignoreList.at(i); }
346     inline const IgnoreList &ignoreList() const { return _ignoreList; }
347
348     //! Check if a message matches the IgnoreRule
349     /** This method checks if a message matches the users ignorelist.
350       * \param msg The Message that should be checked
351       * \param network The networkname the message belongs to
352       * \return UnmatchedStrictness, HardStrictness or SoftStrictness representing the match type
353       */
354     inline StrictnessType match(const Message &msg, const QString &network = QString()) { return _match(msg.contents(), msg.sender(), msg.type(), network, msg.bufferInfo().bufferName()); }
355
356     bool ctcpMatch(const QString sender, const QString &network, const QString &type = QString());
357
358 //  virtual void addIgnoreListItem(const IgnoreListItem &item);
359
360 public slots:
361     virtual QVariantMap initIgnoreList() const;
362     virtual void initSetIgnoreList(const QVariantMap &ignoreList);
363
364     //! Request removal of an ignore rule based on the rule itself.
365     /** Use this method if you want to remove a single ignore rule
366       * and get that synced with the core immediately.
367       * \param ignoreRule A valid ignore rule
368       */
369     virtual inline void requestRemoveIgnoreListItem(const QString &ignoreRule) { REQUEST(ARG(ignoreRule)) }
370     virtual void removeIgnoreListItem(const QString &ignoreRule);
371
372     //! Request toggling of "isActive" flag of a given ignore rule.
373     /** Use this method if you want to toggle the "isActive" flag of a single ignore rule
374       * and get that synced with the core immediately.
375       * \param ignoreRule A valid ignore rule
376       */
377     virtual inline void requestToggleIgnoreRule(const QString &ignoreRule) { REQUEST(ARG(ignoreRule)) }
378     virtual void toggleIgnoreRule(const QString &ignoreRule);
379
380     //! Request an IgnoreListItem to be added to the ignore list
381     /** Items added to the list with this method, get immediately synced with the core
382       * \param type The IgnoreType of the new rule
383       * \param ignoreRule The rule itself
384       * \param isRegEx Signals if the rule should be interpreted as a regular expression
385       * \param strictness Th StrictnessType that should be applied
386       * \param scope The ScopeType that should be set
387       * \param scopeRule A string of semi-colon separated network- or channelnames
388       * \param isActive Signals if the rule is enabled or not
389       */
390     virtual inline void requestAddIgnoreListItem(int type, const QString &ignoreRule, bool isRegEx, int strictness,
391         int scope, const QString &scopeRule, bool isActive)
392     {
393         REQUEST(ARG(type), ARG(ignoreRule), ARG(isRegEx), ARG(strictness), ARG(scope), ARG(scopeRule), ARG(isActive))
394     }
395
396
397     virtual void addIgnoreListItem(int type, const QString &ignoreRule, bool isRegEx, int strictness,
398         int scope, const QString &scopeRule, bool isActive);
399
400 protected:
401     void setIgnoreList(const QList<IgnoreListItem> &ignoreList) { _ignoreList = ignoreList; }
402
403     StrictnessType _match(const QString &msgContents, const QString &msgSender, Message::Type msgType, const QString &network, const QString &bufferName);
404
405 signals:
406     void ignoreAdded(IgnoreType type, const QString &ignoreRule, bool isRegex, StrictnessType strictness, ScopeType scope, const QVariant &scopeRule, bool isActive);
407
408 private:
409     IgnoreList _ignoreList;
410 };