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