Semi-yearly copyright bump
[quassel.git] / src / common / highlightrulemanager.cpp
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 #include "highlightrulemanager.h"
22
23 #include "util.h"
24
25 #include <QDebug>
26
27 INIT_SYNCABLE_OBJECT(HighlightRuleManager)
28 HighlightRuleManager &HighlightRuleManager::operator=(const HighlightRuleManager &other)
29 {
30     if (this == &other)
31         return *this;
32
33     SyncableObject::operator=(other);
34     _highlightRuleList = other._highlightRuleList;
35     _nicksCaseSensitive = other._nicksCaseSensitive;
36     _highlightNick = other._highlightNick;
37     return *this;
38 }
39
40
41 int HighlightRuleManager::indexOf(const QString &name) const
42 {
43     for (int i = 0; i < _highlightRuleList.count(); i++) {
44         if (_highlightRuleList[i].name == name)
45             return i;
46     }
47     return -1;
48 }
49
50
51 QVariantMap HighlightRuleManager::initHighlightRuleList() const
52 {
53     QVariantMap highlightRuleListMap;
54     QStringList name;
55     QVariantList isRegEx;
56     QVariantList isCaseSensitive;
57     QVariantList isActive;
58     QVariantList isInverse;
59     QStringList sender;
60     QStringList channel;
61
62     for (int i = 0; i < _highlightRuleList.count(); i++) {
63         name << _highlightRuleList[i].name;
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["name"] = name;
73     highlightRuleListMap["isRegEx"] = isRegEx;
74     highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
75     highlightRuleListMap["isEnabled"] = isActive;
76     highlightRuleListMap["isInverse"] = isInverse;
77     highlightRuleListMap["sender"] = sender;
78     highlightRuleListMap["channel"] = channel;
79     highlightRuleListMap["highlightNick"] = _highlightNick;
80     highlightRuleListMap["nicksCaseSensitive"] = _nicksCaseSensitive;
81     return highlightRuleListMap;
82 }
83
84
85 void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap &highlightRuleList)
86 {
87     QStringList name = highlightRuleList["name"].toStringList();
88     QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
89     QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
90     QVariantList isActive = highlightRuleList["isEnabled"].toList();
91     QVariantList isInverse = highlightRuleList["isInverse"].toList();
92     QStringList sender = highlightRuleList["sender"].toStringList();
93     QStringList channel = highlightRuleList["channel"].toStringList();
94
95     int count = name.count();
96     if (count != isRegEx.count() || count != isCaseSensitive.count() || count != isActive.count() ||
97         count != isInverse.count() || count != sender.count() || count != channel.count()) {
98         qWarning() << "Corrupted HighlightRuleList settings! (Count mismatch)";
99         return;
100     }
101
102     _highlightRuleList.clear();
103     for (int i = 0; i < name.count(); i++) {
104         _highlightRuleList << HighlightRule(name[i], isRegEx[i].toBool(), isCaseSensitive[i].toBool(),
105                                             isActive[i].toBool(), isInverse[i].toBool(), sender[i], channel[i]);
106     }
107     _highlightNick = HighlightNickType(highlightRuleList["highlightNick"].toInt());
108     _nicksCaseSensitive = highlightRuleList["nicksCaseSensitive"].toBool();
109 }
110
111 void HighlightRuleManager::addHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive, bool isActive,
112                                             bool isInverse, const QString &sender, const QString &channel)
113 {
114     if (contains(name)) {
115         return;
116     }
117
118     HighlightRule newItem = HighlightRule(name, isRegEx, isCaseSensitive, isActive, isInverse, sender, channel);
119     _highlightRuleList << newItem;
120
121     SYNC(ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(isInverse), ARG(sender), ARG(channel))
122 }
123
124
125 bool HighlightRuleManager::match(const QString &msgContents,
126                                  const QString &msgSender,
127                                  Message::Type msgType,
128                                  Message::Flags msgFlags,
129                                  const QString &bufferName,
130                                  const QString &currentNick,
131                                  const QStringList identityNicks)
132 {
133     if (!((msgType & (Message::Plain | Message::Notice | Message::Action)) && !(msgFlags & Message::Self))) {
134        return false;
135     }
136
137     bool matches = false;
138
139     for (int i = 0; i < _highlightRuleList.count(); i++) {
140         const HighlightRule &rule = _highlightRuleList.at(i);
141         if (!rule.isEnabled)
142             continue;
143
144         if (rule.chanName.size() > 0 && rule.chanName.compare(".*") != 0) {
145             if (rule.chanName.startsWith("!")) {
146                 QRegExp rx(rule.chanName.mid(1), Qt::CaseInsensitive);
147                 if (rx.exactMatch(bufferName))
148                     continue;
149             }
150             else {
151                 QRegExp rx(rule.chanName, Qt::CaseInsensitive);
152                 if (!rx.exactMatch(bufferName))
153                     continue;
154             }
155         }
156
157         QRegExp rx;
158         if (rule.isRegEx) {
159             rx = QRegExp(rule.name, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
160         } else {
161             rx = QRegExp("(^|\\W)" + QRegExp::escape(rule.name) + "(\\W|$)", rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
162         }
163         bool nameMatch = (rx.indexIn(stripFormatCodes(msgContents)) >= 0);
164
165         bool senderMatch;
166         if (rule.sender.isEmpty()) {
167             senderMatch = true;
168         } else {
169             if (rule.isRegEx) {
170                 rx = QRegExp(rule.sender, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
171             } else {
172                 rx = QRegExp(rule.sender, Qt::CaseInsensitive, QRegExp::Wildcard);
173             }
174             senderMatch = rx.exactMatch(msgSender);
175         }
176
177         if (nameMatch && senderMatch) {
178             // If an inverse rule matches, then we know that we never want to return a highlight.
179             if (rule.isInverse) {
180                 return false;
181             } else {
182                 matches = true;
183             }
184         }
185     }
186
187     if (matches)
188         return true;
189
190     if (!currentNick.isEmpty()) {
191         QStringList nickList;
192         if (_highlightNick == CurrentNick) {
193             nickList << currentNick;
194         }
195         else if (_highlightNick == AllNicks) {
196             nickList = identityNicks;
197             if (!nickList.contains(currentNick))
198                 nickList.prepend(currentNick);
199         }
200
201         for(const QString &nickname : nickList) {
202             QRegExp nickRegExp("(^|\\W)" + QRegExp::escape(nickname) + "(\\W|$)", _nicksCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
203             if (nickRegExp.indexIn(stripFormatCodes(msgContents)) >= 0) {
204                 return true;
205             }
206         }
207     }
208
209     return false;
210 }
211
212 void HighlightRuleManager::removeHighlightRule(const QString &highlightRule)
213 {
214     removeAt(indexOf(highlightRule));
215     SYNC(ARG(highlightRule))
216 }
217
218
219 void HighlightRuleManager::toggleHighlightRule(const QString &highlightRule)
220 {
221     int idx = indexOf(highlightRule);
222     if (idx == -1)
223         return;
224     _highlightRuleList[idx].isEnabled = !_highlightRuleList[idx].isEnabled;
225     SYNC(ARG(highlightRule))
226 }
227
228 bool HighlightRuleManager::match(const Message &msg, const QString &currentNick, const QStringList &identityNicks)
229 {
230     return match(msg.contents(), msg.sender(), msg.type(), msg.flags(), msg.bufferInfo().bufferName(), currentNick, identityNicks);
231 }