1 /* ============================================================
2 * QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader
3 * Copyright (C) 2011-2020 QuiteRSS Team <quiterssteam@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 <https://www.gnu.org/licenses/>.
17 * ============================================================ */
18 /* ============================================================
19 * QupZilla - WebKit based browser
20 * Copyright (C) 2010-2014  David Rosca <nowrep@gmail.com>
21 *
22 * This program is free software: you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation, either version 3 of the License, or
25 * (at your option) any later version.
26 *
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
34 * ============================================================ */
35 #include "adblocktreewidget.h"
36 #include "adblocksubscription.h"
37 
38 #include <QMenu>
39 #include <QKeyEvent>
40 #include <QClipboard>
41 #include <QApplication>
42 #include <QInputDialog>
43 
AdBlockTreeWidget(AdBlockSubscription * subscription,QWidget * parent)44 AdBlockTreeWidget::AdBlockTreeWidget(AdBlockSubscription* subscription, QWidget* parent)
45   : QTreeWidget(parent)
46   , m_subscription(subscription)
47   , m_topItem(0)
48   , m_itemChangingBlock(false)
49   , m_refreshAllItemsNeeded(true)
50 {
51   setContextMenuPolicy(Qt::CustomContextMenu);
52   setHeaderHidden(true);
53   setAlternatingRowColors(true);
54   setFrameStyle(QFrame::NoFrame);
55 
56   connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint)));
57   connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(itemChanged(QTreeWidgetItem*)));
58   connect(m_subscription, SIGNAL(subscriptionUpdated()), this, SLOT(subscriptionUpdated()));
59   connect(m_subscription, SIGNAL(subscriptionError(QString)), this, SLOT(subscriptionError(QString)));
60 }
61 
subscription() const62 AdBlockSubscription* AdBlockTreeWidget::subscription() const
63 {
64   return m_subscription;
65 }
66 
showRule(const AdBlockRule * rule)67 void AdBlockTreeWidget::showRule(const AdBlockRule* rule)
68 {
69   if (!m_topItem && rule) {
70     m_ruleToBeSelected = rule->filter();
71   }
72   else if (!m_ruleToBeSelected.isEmpty()) {
73     QList<QTreeWidgetItem*> items = findItems(m_ruleToBeSelected, Qt::MatchRecursive);
74     if (!items.isEmpty()) {
75       QTreeWidgetItem* item = items.at(0);
76 
77       setCurrentItem(item);
78       scrollToItem(item, QAbstractItemView::PositionAtCenter);
79     }
80 
81     m_ruleToBeSelected.clear();
82   }
83 }
84 
contextMenuRequested(const QPoint & pos)85 void AdBlockTreeWidget::contextMenuRequested(const QPoint &pos)
86 {
87   if (!m_subscription->canEditRules()) {
88     return;
89   }
90 
91   QTreeWidgetItem* item = itemAt(pos);
92   if (!item) {
93     return;
94   }
95 
96   QMenu menu;
97   menu.addAction(tr("Add Rule"), this, SLOT(addRule()));
98   menu.addSeparator();
99   QAction* deleteAction = menu.addAction(tr("Remove Rule"), this, SLOT(removeRule()));
100 
101   if (!item->parent()) {
102     deleteAction->setDisabled(true);
103   }
104 
105   menu.exec(viewport()->mapToGlobal(pos));
106 }
107 
itemChanged(QTreeWidgetItem * item)108 void AdBlockTreeWidget::itemChanged(QTreeWidgetItem* item)
109 {
110   m_refreshAllItemsNeeded = true;
111 
112   if (!item || m_itemChangingBlock) {
113     return;
114   }
115 
116   m_itemChangingBlock = true;
117 
118   int offset = item->data(0, Qt::UserRole + 10).toInt();
119   const AdBlockRule* oldRule = m_subscription->rule(offset);
120 
121   if (item->checkState(0) == Qt::Unchecked && oldRule->isEnabled()) {
122     // Disable rule
123     const AdBlockRule* rule = m_subscription->disableRule(offset);
124 
125     adjustItemFeatures(item, rule);
126   }
127   else if (item->checkState(0) == Qt::Checked && !oldRule->isEnabled()) {
128     // Enable rule
129     const AdBlockRule* rule = m_subscription->enableRule(offset);
130 
131     adjustItemFeatures(item, rule);
132   }
133   else if (m_subscription->canEditRules()) {
134     // Custom rule has been changed
135     AdBlockRule* newRule = new AdBlockRule(item->text(0), m_subscription);
136     const AdBlockRule* rule = m_subscription->replaceRule(newRule, offset);
137 
138     adjustItemFeatures(item, rule);
139   }
140 
141   m_itemChangingBlock = false;
142 }
143 
copyFilter()144 void AdBlockTreeWidget::copyFilter()
145 {
146   QTreeWidgetItem* item = currentItem();
147   if (!item) {
148     return;
149   }
150 
151   QApplication::clipboard()->setText(item->text(0));
152 }
153 
addRule()154 void AdBlockTreeWidget::addRule()
155 {
156   if (!m_subscription->canEditRules()) {
157     return;
158   }
159 
160   QString newRule = QInputDialog::getText(this, tr("Add Custom Rule"), tr("Please write your rule here:"));
161   if (newRule.isEmpty()) {
162     return;
163   }
164 
165   AdBlockRule* rule = new AdBlockRule(newRule, m_subscription);
166   int offset = m_subscription->addRule(rule);
167 
168   QTreeWidgetItem* item = new QTreeWidgetItem();
169   item->setText(0, newRule);
170   item->setData(0, Qt::UserRole + 10, offset);
171   item->setFlags(item->flags() | Qt::ItemIsEditable);
172 
173   m_itemChangingBlock = true;
174   m_topItem->addChild(item);
175   m_itemChangingBlock = false;
176 
177   adjustItemFeatures(item, rule);
178 }
179 
removeRule()180 void AdBlockTreeWidget::removeRule()
181 {
182   QTreeWidgetItem* item = currentItem();
183   if (!item || !m_subscription->canEditRules() || item == m_topItem) {
184     return;
185   }
186 
187   int offset = item->data(0, Qt::UserRole + 10).toInt();
188 
189   m_subscription->removeRule(offset);
190   delete item;
191 }
192 
subscriptionUpdated()193 void AdBlockTreeWidget::subscriptionUpdated()
194 {
195   refresh();
196 
197   m_itemChangingBlock = true;
198   m_topItem->setText(0, tr("%1 (recently updated)").arg(m_subscription->title()));
199   m_itemChangingBlock = false;
200 }
201 
subscriptionError(const QString & message)202 void AdBlockTreeWidget::subscriptionError(const QString &message)
203 {
204   refresh();
205 
206   m_itemChangingBlock = true;
207   m_topItem->setText(0, tr("%1 (Error: %2)").arg(m_subscription->title(), message));
208   m_itemChangingBlock = false;
209 }
210 
adjustItemFeatures(QTreeWidgetItem * item,const AdBlockRule * rule)211 void AdBlockTreeWidget::adjustItemFeatures(QTreeWidgetItem* item, const AdBlockRule* rule)
212 {
213   if (!rule->isEnabled()) {
214     QFont font;
215     font.setItalic(true);
216     item->setForeground(0, QColor(Qt::gray));
217 
218     if (!rule->isComment()) {
219       item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
220       item->setCheckState(0, Qt::Unchecked);
221       item->setFont(0, font);
222     }
223 
224     return;
225   }
226 
227   item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
228   item->setCheckState(0, Qt::Checked);
229 
230   if (rule->isException()) {
231     item->setForeground(0, QColor(Qt::darkGreen));
232     item->setFont(0, QFont());
233   }
234   else if (rule->isCssRule()) {
235     item->setForeground(0, QColor(Qt::darkBlue));
236     item->setFont(0, QFont());
237   }
238   else {
239     item->setForeground(0, QColor());
240     item->setFont(0, QFont());
241   }
242 }
243 
keyPressEvent(QKeyEvent * event)244 void AdBlockTreeWidget::keyPressEvent(QKeyEvent* event)
245 {
246   if (event->key() == Qt::Key_C && event->modifiers() & Qt::ControlModifier) {
247     copyFilter();
248   }
249 
250   if (event->key() == Qt::Key_Delete) {
251     removeRule();
252   }
253 
254   QTreeWidget::keyPressEvent(event);
255 }
256 
refresh()257 void AdBlockTreeWidget::refresh()
258 {
259   m_itemChangingBlock = true;
260   clear();
261 
262   QFont boldFont;
263   boldFont.setBold(true);
264 
265   m_topItem = new QTreeWidgetItem(this);
266   m_topItem->setText(0, m_subscription->title());
267   m_topItem->setFont(0, boldFont);
268   m_topItem->setExpanded(true);
269   addTopLevelItem(m_topItem);
270 
271   const QVector<AdBlockRule*> &allRules = m_subscription->allRules();
272 
273   int index = 0;
274   foreach (const AdBlockRule* rule, allRules) {
275     QTreeWidgetItem* item = new QTreeWidgetItem(m_topItem);
276     item->setText(0, rule->filter());
277     item->setData(0, Qt::UserRole + 10, index);
278 
279     if (m_subscription->canEditRules()) {
280       item->setFlags(item->flags() | Qt::ItemIsEditable);
281     }
282 
283     adjustItemFeatures(item, rule);
284     ++index;
285   }
286 
287   showRule(0);
288   m_itemChangingBlock = false;
289 }
290 
clear()291 void AdBlockTreeWidget::clear()
292 {
293   QTreeWidget::clear();
294   m_allTreeItems.clear();
295 }
296 
addTopLevelItem(QTreeWidgetItem * item)297 void AdBlockTreeWidget::addTopLevelItem(QTreeWidgetItem* item)
298 {
299   m_allTreeItems.append(item);
300   QTreeWidget::addTopLevelItem(item);
301 }
302 
iterateAllItems(QTreeWidgetItem * parent)303 void AdBlockTreeWidget::iterateAllItems(QTreeWidgetItem* parent)
304 {
305   int count = parent ? parent->childCount() : topLevelItemCount();
306 
307   for (int i = 0; i < count; i++) {
308     QTreeWidgetItem* item = parent ? parent->child(i) : topLevelItem(i);
309 
310     if (item->childCount() == 0) {
311       m_allTreeItems.append(item);
312     }
313 
314     iterateAllItems(item);
315   }
316 }
317 
allItems()318 QList<QTreeWidgetItem*> AdBlockTreeWidget::allItems()
319 {
320   if (m_refreshAllItemsNeeded) {
321     m_allTreeItems.clear();
322     iterateAllItems(0);
323     m_refreshAllItemsNeeded = false;
324   }
325 
326   return m_allTreeItems;
327 }
328 
filterString(const QString & string)329 void AdBlockTreeWidget::filterString(const QString &string)
330 {
331   QList<QTreeWidgetItem*> _allItems = allItems();
332   QList<QTreeWidgetItem*> parents;
333   bool stringIsEmpty = string.isEmpty();
334   foreach (QTreeWidgetItem* item, _allItems) {
335     bool containsString = stringIsEmpty || item->text(0).contains(string, Qt::CaseInsensitive);
336     if (containsString) {
337       item->setHidden(false);
338       if (item->parent()) {
339         if (!parents.contains(item->parent())) {
340           parents << item->parent();
341         }
342       }
343     }
344     else {
345       item->setHidden(true);
346       if (item->parent()) {
347         item->parent()->setHidden(true);
348       }
349     }
350   }
351 
352   for (int i = 0; i < parents.size(); ++i) {
353     QTreeWidgetItem* parentItem = parents.at(i);
354     parentItem->setHidden(false);
355     parentItem->setExpanded(true);
356 
357     if (parentItem->parent() && !parents.contains(parentItem->parent())) {
358       parents << parentItem->parent();
359     }
360   }
361 }
362