cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / common / highlightrulemanager.cpp
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 #include "highlightrulemanager.h"
22
23 #include <QDebug>
24
25 #include "expressionmatch.h"
26 #include "util.h"
27
28 int HighlightRuleManager::indexOf(int id) const
29 {
30     for (int i = 0; i < _highlightRuleList.count(); i++) {
31         if (_highlightRuleList[i].id() == id)
32             return i;
33     }
34     return -1;
35 }
36
37 int HighlightRuleManager::nextId()
38 {
39     int max = 0;
40     for (int i = 0; i < _highlightRuleList.count(); i++) {
41         int id = _highlightRuleList[i].id();
42         if (id > max) {
43             max = id;
44         }
45     }
46     return max + 1;
47 }
48
49 QVariantMap HighlightRuleManager::initHighlightRuleList() const
50 {
51     QVariantList id;
52     QVariantMap highlightRuleListMap;
53     QStringList name;
54     QVariantList isRegEx;
55     QVariantList isCaseSensitive;
56     QVariantList isActive;
57     QVariantList isInverse;
58     QStringList sender;
59     QStringList channel;
60
61     for (int i = 0; i < _highlightRuleList.count(); i++) {
62         id << _highlightRuleList[i].id();
63         name << _highlightRuleList[i].contents();
64         isRegEx << _highlightRuleList[i].isRegEx();
65         isCaseSensitive << _highlightRuleList[i].isCaseSensitive();
66         isActive << _highlightRuleList[i].isEnabled();
67         isInverse << _highlightRuleList[i].isInverse();
68         sender << _highlightRuleList[i].sender();
69         channel << _highlightRuleList[i].chanName();
70     }
71
72     highlightRuleListMap["id"] = id;
73     highlightRuleListMap["name"] = name;
74     highlightRuleListMap["isRegEx"] = isRegEx;
75     highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
76     highlightRuleListMap["isEnabled"] = isActive;
77     highlightRuleListMap["isInverse"] = isInverse;
78     highlightRuleListMap["sender"] = sender;
79     highlightRuleListMap["channel"] = channel;
80     return highlightRuleListMap;
81 }
82
83 void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap& highlightRuleList)
84 {
85     QVariantList id = highlightRuleList["id"].toList();
86     QStringList name = highlightRuleList["name"].toStringList();
87     QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
88     QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
89     QVariantList isActive = highlightRuleList["isEnabled"].toList();
90     QVariantList isInverse = highlightRuleList["isInverse"].toList();
91     QStringList sender = highlightRuleList["sender"].toStringList();
92     QStringList channel = highlightRuleList["channel"].toStringList();
93
94     int count = id.count();
95     if (count != name.count() || count != isRegEx.count() || count != isCaseSensitive.count() || count != isActive.count()
96         || count != isInverse.count() || count != sender.count() || count != channel.count()) {
97         qWarning() << "Corrupted HighlightRuleList settings! (Count mismatch)";
98         return;
99     }
100
101     _highlightRuleList.clear();
102     for (int i = 0; i < name.count(); i++) {
103         _highlightRuleList << HighlightRule(id[i].toInt(),
104                                             name[i],
105                                             isRegEx[i].toBool(),
106                                             isCaseSensitive[i].toBool(),
107                                             isActive[i].toBool(),
108                                             isInverse[i].toBool(),
109                                             sender[i],
110                                             channel[i]);
111     }
112 }
113
114 void HighlightRuleManager::addHighlightRule(
115     int id, const QString& name, bool isRegEx, bool isCaseSensitive, bool isActive, bool isInverse, const QString& sender, const QString& channel)
116 {
117     if (contains(id)) {
118         return;
119     }
120
121     HighlightRule newItem = HighlightRule(id, name, isRegEx, isCaseSensitive, isActive, isInverse, sender, channel);
122     _highlightRuleList << newItem;
123
124     SYNC(ARG(id), ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(isInverse), ARG(sender), ARG(channel))
125 }
126
127 bool HighlightRuleManager::match(const NetworkId& netId,
128                                  const QString& msgContents,
129                                  const QString& msgSender,
130                                  Message::Type msgType,
131                                  Message::Flags msgFlags,
132                                  const QString& bufferName,
133                                  const QString& currentNick,
134                                  const QStringList& identityNicks)
135 {
136     if (!((msgType & (Message::Plain | Message::Notice | Message::Action)) && !(msgFlags & Message::Self))) {
137         return false;
138     }
139
140     bool matches = false;
141
142     for (int i = 0; i < _highlightRuleList.count(); i++) {
143         auto& rule = _highlightRuleList.at(i);
144         if (!rule.isEnabled())
145             continue;
146
147         // Skip if channel name doesn't match and channel rule is not empty
148         //
149         // Match succeeds if...
150         //   Channel name matches a defined rule
151         //   Defined rule is empty
152         // And take the inverse of the above
153         if (!rule.chanNameMatcher().match(bufferName, true)) {
154             // A channel name rule is specified and does NOT match the current buffer name, skip
155             // this rule
156             continue;
157         }
158
159         // Check message according to specified rule, allowing empty rules to match
160         bool contentsMatch = rule.contentsMatcher().match(stripFormatCodes(msgContents), true);
161
162         // Check sender according to specified rule, allowing empty rules to match
163         bool senderMatch = rule.senderMatcher().match(msgSender, true);
164
165         if (contentsMatch && senderMatch) {
166             // If an inverse rule matches, then we know that we never want to return a highlight.
167             if (rule.isInverse()) {
168                 return false;
169             }
170             else {
171                 matches = true;
172             }
173         }
174     }
175
176     if (matches)
177         return true;
178
179     // Check nicknames
180     if (_highlightNick != HighlightNickType::NoNick && !currentNick.isEmpty()) {
181         // Nickname matching allowed and current nickname is known
182         // Run the nickname matcher on the unformatted string
183         if (_nickMatcher.match(stripFormatCodes(msgContents), netId, currentNick, identityNicks)) {
184             return true;
185         }
186     }
187
188     return false;
189 }
190
191 void HighlightRuleManager::removeHighlightRule(int highlightRule)
192 {
193     removeAt(indexOf(highlightRule));
194     SYNC(ARG(highlightRule))
195 }
196
197 void HighlightRuleManager::toggleHighlightRule(int highlightRule)
198 {
199     int idx = indexOf(highlightRule);
200     if (idx == -1)
201         return;
202     _highlightRuleList[idx].setIsEnabled(!_highlightRuleList[idx].isEnabled());
203     SYNC(ARG(highlightRule))
204 }
205
206 bool HighlightRuleManager::match(const Message& msg, const QString& currentNick, const QStringList& identityNicks)
207 {
208     return match(msg.bufferInfo().networkId(),
209                  msg.contents(),
210                  msg.sender(),
211                  msg.type(),
212                  msg.flags(),
213                  msg.bufferInfo().bufferName(),
214                  currentNick,
215                  identityNicks);
216 }
217
218 /**************************************************************************
219  * HighlightRule
220  *************************************************************************/
221 bool HighlightRuleManager::HighlightRule::operator!=(const HighlightRule& other) const
222 {
223     return (_id != other._id || _contents != other._contents || _isRegEx != other._isRegEx || _isCaseSensitive != other._isCaseSensitive
224             || _isEnabled != other._isEnabled || _isInverse != other._isInverse || _sender != other._sender || _chanName != other._chanName);
225     // Don't compare ExpressionMatch objects as they are created as needed from the above
226 }
227
228 void HighlightRuleManager::HighlightRule::determineExpressions() const
229 {
230     // Don't update if not needed
231     if (!_cacheInvalid) {
232         return;
233     }
234
235     // Set up matching rules
236     // Message is either phrase or regex
237     ExpressionMatch::MatchMode contentsMode = _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx : ExpressionMatch::MatchMode::MatchPhrase;
238     // Sender and channel are either multiple wildcard entries or regex
239     ExpressionMatch::MatchMode scopeMode = _isRegEx ? ExpressionMatch::MatchMode::MatchRegEx : ExpressionMatch::MatchMode::MatchMultiWildcard;
240
241     _contentsMatch = ExpressionMatch(_contents, contentsMode, _isCaseSensitive);
242     _senderMatch = ExpressionMatch(_sender, scopeMode, _isCaseSensitive);
243     _chanNameMatch = ExpressionMatch(_chanName, scopeMode, _isCaseSensitive);
244
245     _cacheInvalid = false;
246 }