1 /* ============================================================
2 * Falkon - Qt web browser
3 * Copyright (C) 2010-2017 David Rosca <nowrep@gmail.com>
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 3 of the License, or
8 * (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
17 * ============================================================ */
18 #include "adblocktreewidget.h"
19 #include "adblocksubscription.h"
20
21 #include <QMenu>
22 #include <QKeyEvent>
23 #include <QClipboard>
24 #include <QApplication>
25 #include <QInputDialog>
26
AdBlockTreeWidget(AdBlockSubscription * subscription,QWidget * parent)27 AdBlockTreeWidget::AdBlockTreeWidget(AdBlockSubscription* subscription, QWidget* parent)
28 : TreeWidget(parent)
29 , m_subscription(subscription)
30 , m_topItem(0)
31 , m_itemChangingBlock(false)
32 {
33 setContextMenuPolicy(Qt::CustomContextMenu);
34 setDefaultItemShowMode(TreeWidget::ItemsExpanded);
35 setHeaderHidden(true);
36 setAlternatingRowColors(true);
37 setLayoutDirection(Qt::LeftToRight);
38
39 connect(this, &QWidget::customContextMenuRequested, this, &AdBlockTreeWidget::contextMenuRequested);
40 connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(itemChanged(QTreeWidgetItem*)));
41 connect(m_subscription, &AdBlockSubscription::subscriptionUpdated, this, &AdBlockTreeWidget::subscriptionUpdated);
42 connect(m_subscription, &AdBlockSubscription::subscriptionError, this, &AdBlockTreeWidget::subscriptionError);
43 }
44
subscription() const45 AdBlockSubscription* AdBlockTreeWidget::subscription() const
46 {
47 return m_subscription;
48 }
49
showRule(const AdBlockRule * rule)50 void AdBlockTreeWidget::showRule(const AdBlockRule* rule)
51 {
52 if (!m_topItem && rule) {
53 m_ruleToBeSelected = rule->filter();
54 }
55 else if (!m_ruleToBeSelected.isEmpty()) {
56 QList<QTreeWidgetItem*> items = findItems(m_ruleToBeSelected, Qt::MatchRecursive);
57 if (!items.isEmpty()) {
58 QTreeWidgetItem* item = items.at(0);
59
60 setCurrentItem(item);
61 scrollToItem(item, QAbstractItemView::PositionAtCenter);
62 }
63
64 m_ruleToBeSelected.clear();
65 }
66 }
67
contextMenuRequested(const QPoint & pos)68 void AdBlockTreeWidget::contextMenuRequested(const QPoint &pos)
69 {
70 if (!m_subscription->canEditRules()) {
71 return;
72 }
73
74 QTreeWidgetItem* item = itemAt(pos);
75 if (!item) {
76 return;
77 }
78
79 QMenu menu;
80 menu.addAction(tr("Add Rule"), this, &AdBlockTreeWidget::addRule);
81 menu.addSeparator();
82 QAction* deleteAction = menu.addAction(tr("Remove Rule"), this, &AdBlockTreeWidget::removeRule);
83
84 if (!item->parent()) {
85 deleteAction->setDisabled(true);
86 }
87
88 menu.exec(viewport()->mapToGlobal(pos));
89 }
90
itemChanged(QTreeWidgetItem * item)91 void AdBlockTreeWidget::itemChanged(QTreeWidgetItem* item)
92 {
93 if (!item || m_itemChangingBlock) {
94 return;
95 }
96
97 m_itemChangingBlock = true;
98
99 int offset = item->data(0, Qt::UserRole + 10).toInt();
100 const AdBlockRule* oldRule = m_subscription->rule(offset);
101
102 if (item->checkState(0) == Qt::Unchecked && oldRule->isEnabled()) {
103 // Disable rule
104 const AdBlockRule* rule = m_subscription->disableRule(offset);
105
106 adjustItemFeatures(item, rule);
107 }
108 else if (item->checkState(0) == Qt::Checked && !oldRule->isEnabled()) {
109 // Enable rule
110 const AdBlockRule* rule = m_subscription->enableRule(offset);
111
112 adjustItemFeatures(item, rule);
113 }
114 else if (m_subscription->canEditRules()) {
115 // Custom rule has been changed
116 AdBlockRule* newRule = new AdBlockRule(item->text(0), m_subscription);
117 const AdBlockRule* rule = m_subscription->replaceRule(newRule, offset);
118
119 adjustItemFeatures(item, rule);
120 }
121
122 m_itemChangingBlock = false;
123 }
124
copyFilter()125 void AdBlockTreeWidget::copyFilter()
126 {
127 QTreeWidgetItem* item = currentItem();
128 if (!item) {
129 return;
130 }
131
132 QApplication::clipboard()->setText(item->text(0));
133 }
134
addRule()135 void AdBlockTreeWidget::addRule()
136 {
137 if (!m_subscription->canEditRules()) {
138 return;
139 }
140
141 QString newRule = QInputDialog::getText(this, tr("Add Custom Rule"), tr("Please write your rule here:"));
142 if (newRule.isEmpty()) {
143 return;
144 }
145
146 AdBlockRule* rule = new AdBlockRule(newRule, m_subscription);
147 int offset = m_subscription->addRule(rule);
148
149 QTreeWidgetItem* item = new QTreeWidgetItem();
150 item->setText(0, newRule);
151 item->setData(0, Qt::UserRole + 10, offset);
152 item->setFlags(item->flags() | Qt::ItemIsEditable);
153
154 m_itemChangingBlock = true;
155 m_topItem->addChild(item);
156 m_itemChangingBlock = false;
157
158 adjustItemFeatures(item, rule);
159 }
160
removeRule()161 void AdBlockTreeWidget::removeRule()
162 {
163 QTreeWidgetItem* item = currentItem();
164 if (!item || !m_subscription->canEditRules() || item == m_topItem) {
165 return;
166 }
167
168 int offset = item->data(0, Qt::UserRole + 10).toInt();
169
170 m_subscription->removeRule(offset);
171 deleteItem(item);
172 }
173
subscriptionUpdated()174 void AdBlockTreeWidget::subscriptionUpdated()
175 {
176 refresh();
177
178 m_itemChangingBlock = true;
179 m_topItem->setText(0, tr("%1 (recently updated)").arg(m_subscription->title()));
180 m_itemChangingBlock = false;
181 }
182
subscriptionError(const QString & message)183 void AdBlockTreeWidget::subscriptionError(const QString &message)
184 {
185 refresh();
186
187 m_itemChangingBlock = true;
188 m_topItem->setText(0, tr("%1 (Error: %2)").arg(m_subscription->title(), message));
189 m_itemChangingBlock = false;
190 }
191
adjustItemFeatures(QTreeWidgetItem * item,const AdBlockRule * rule)192 void AdBlockTreeWidget::adjustItemFeatures(QTreeWidgetItem* item, const AdBlockRule* rule)
193 {
194 if (!rule->isEnabled()) {
195 item->setForeground(0, QColor(Qt::gray));
196
197 if (!rule->isComment()) {
198 QFont f = font();
199 f.setItalic(true);
200 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
201 item->setCheckState(0, Qt::Unchecked);
202 item->setFont(0, f);
203 }
204
205 return;
206 }
207
208 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
209 item->setCheckState(0, Qt::Checked);
210 item->setForeground(0, palette().windowText());
211 item->setFont(0, font());
212
213 if (rule->isException()) {
214 item->setForeground(0, QColor(Qt::darkGreen));
215 item->setFont(0, QFont());
216 }
217 else if (rule->isCssRule()) {
218 item->setForeground(0, QColor(Qt::darkBlue));
219 item->setFont(0, QFont());
220 }
221 }
222
keyPressEvent(QKeyEvent * event)223 void AdBlockTreeWidget::keyPressEvent(QKeyEvent* event)
224 {
225 if (event->key() == Qt::Key_C && event->modifiers() & Qt::ControlModifier) {
226 copyFilter();
227 }
228
229 if (event->key() == Qt::Key_Delete) {
230 removeRule();
231 }
232
233 TreeWidget::keyPressEvent(event);
234 }
235
refresh()236 void AdBlockTreeWidget::refresh()
237 {
238 m_itemChangingBlock = true;
239 clear();
240
241 QFont boldFont;
242 boldFont.setBold(true);
243
244 m_topItem = new QTreeWidgetItem(this);
245 m_topItem->setText(0, m_subscription->title());
246 m_topItem->setFont(0, boldFont);
247 m_topItem->setExpanded(true);
248 addTopLevelItem(m_topItem);
249
250 const QVector<AdBlockRule*> &allRules = m_subscription->allRules();
251
252 int index = 0;
253 for (const AdBlockRule* rule : allRules) {
254 QTreeWidgetItem* item = new QTreeWidgetItem(m_topItem);
255 item->setText(0, rule->filter());
256 item->setData(0, Qt::UserRole + 10, index);
257
258 if (m_subscription->canEditRules()) {
259 item->setFlags(item->flags() | Qt::ItemIsEditable);
260 }
261
262 adjustItemFeatures(item, rule);
263 ++index;
264 }
265
266 showRule(0);
267 m_itemChangingBlock = false;
268 }
269