|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2012 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a tree widget for the AdBlock configuration dialog. |
|
8 """ |
|
9 |
|
10 from PyQt6.QtCore import Qt |
|
11 from PyQt6.QtGui import QFont, QColor |
|
12 from PyQt6.QtWidgets import ( |
|
13 QAbstractItemView, QTreeWidgetItem, QInputDialog, QLineEdit, QMenu, |
|
14 QApplication |
|
15 ) |
|
16 |
|
17 from EricWidgets.EricTreeWidget import EricTreeWidget, EricTreeWidgetItemsState |
|
18 from EricGui.EricOverrideCursor import EricOverrideCursor |
|
19 from EricWidgets.EricApplication import ericApp |
|
20 |
|
21 |
|
22 class AdBlockTreeWidget(EricTreeWidget): |
|
23 """ |
|
24 Class implementing a tree widget for the AdBlock configuration dialog. |
|
25 """ |
|
26 def __init__(self, subscription, parent=None): |
|
27 """ |
|
28 Constructor |
|
29 |
|
30 @param subscription reference to the subscription |
|
31 @type AdBlockSubscription |
|
32 @param parent reference to the parent widget |
|
33 @type QWidget |
|
34 """ |
|
35 super().__init__(parent) |
|
36 |
|
37 self.__subscription = subscription |
|
38 self.__topItem = None |
|
39 self.__ruleToBeSelected = "" |
|
40 self.__itemChangingBlock = False |
|
41 |
|
42 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
|
43 self.setDefaultItemShowMode(EricTreeWidgetItemsState.EXPANDED) |
|
44 self.setHeaderHidden(True) |
|
45 self.setAlternatingRowColors(True) |
|
46 |
|
47 self.__darkMode = ericApp().usesDarkPalette() |
|
48 |
|
49 self.customContextMenuRequested.connect(self.__contextMenuRequested) |
|
50 self.itemChanged.connect(self.__itemChanged) |
|
51 self.__subscription.changed.connect(self.__subscriptionChanged) |
|
52 self.__subscription.rulesChanged.connect(self.__subscriptionChanged) |
|
53 |
|
54 def subscription(self): |
|
55 """ |
|
56 Public method to get a reference to the subscription. |
|
57 |
|
58 @return reference to the subscription |
|
59 @rtype AdBlockSubscription |
|
60 """ |
|
61 return self.__subscription |
|
62 |
|
63 def showRule(self, rule): |
|
64 """ |
|
65 Public method to highlight the given rule. |
|
66 |
|
67 @param rule AdBlock rule to be shown |
|
68 @type AdBlockRule |
|
69 """ |
|
70 if not bool(self.__topItem) and bool(rule): |
|
71 self.__ruleToBeSelected = rule.filter() |
|
72 elif self.__ruleToBeSelected: |
|
73 items = self.findItems( |
|
74 self.__ruleToBeSelected, Qt.MatchFlag.MatchRecursive) |
|
75 if items: |
|
76 item = items[0] |
|
77 self.setCurrentItem(item) |
|
78 self.scrollToItem( |
|
79 item, QAbstractItemView.ScrollHint.PositionAtCenter) |
|
80 |
|
81 self.__ruleToBeSelected = "" |
|
82 |
|
83 def refresh(self): |
|
84 """ |
|
85 Public method to refresh the tree. |
|
86 """ |
|
87 with EricOverrideCursor(): |
|
88 self.__itemChangingBlock = True |
|
89 self.clear() |
|
90 |
|
91 boldFont = QFont() |
|
92 boldFont.setBold(True) |
|
93 |
|
94 self.__topItem = QTreeWidgetItem(self) |
|
95 self.__topItem.setText(0, self.__subscription.title()) |
|
96 self.__topItem.setFont(0, boldFont) |
|
97 self.addTopLevelItem(self.__topItem) |
|
98 |
|
99 allRules = self.__subscription.allRules() |
|
100 |
|
101 for index, rule in enumerate(allRules): |
|
102 item = QTreeWidgetItem(self.__topItem) |
|
103 item.setText(0, rule.filter()) |
|
104 item.setData(0, Qt.ItemDataRole.UserRole, index) |
|
105 if self.__subscription.canEditRules(): |
|
106 item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) |
|
107 self.__adjustItemFeatures(item, rule) |
|
108 |
|
109 self.expandAll() |
|
110 self.showRule(None) |
|
111 self.__itemChangingBlock = False |
|
112 |
|
113 def addRule(self, filterRule=""): |
|
114 """ |
|
115 Public slot to add a new rule. |
|
116 |
|
117 @param filterRule filter to be added |
|
118 @type str |
|
119 """ |
|
120 if not self.__subscription.canEditRules(): |
|
121 return |
|
122 |
|
123 if not filterRule: |
|
124 filterRule, ok = QInputDialog.getText( |
|
125 self, |
|
126 self.tr("Add Custom Rule"), |
|
127 self.tr("Write your rule here:"), |
|
128 QLineEdit.EchoMode.Normal) |
|
129 if not ok or filterRule == "": |
|
130 return |
|
131 |
|
132 from .AdBlockRule import AdBlockRule |
|
133 rule = AdBlockRule(filterRule, self.__subscription) |
|
134 self.__subscription.addRule(rule) |
|
135 |
|
136 def removeRule(self): |
|
137 """ |
|
138 Public slot to remove the current rule. |
|
139 """ |
|
140 item = self.currentItem() |
|
141 if ( |
|
142 item is None or |
|
143 not self.__subscription.canEditRules() or |
|
144 item == self.__topItem |
|
145 ): |
|
146 return |
|
147 |
|
148 offset = item.data(0, Qt.ItemDataRole.UserRole) |
|
149 self.__subscription.removeRule(offset) |
|
150 self.deleteItem(item) |
|
151 |
|
152 def __contextMenuRequested(self, pos): |
|
153 """ |
|
154 Private slot to show the context menu. |
|
155 |
|
156 @param pos position for the menu |
|
157 @type QPoint |
|
158 """ |
|
159 if not self.__subscription.canEditRules(): |
|
160 return |
|
161 |
|
162 item = self.itemAt(pos) |
|
163 if item is None: |
|
164 return |
|
165 |
|
166 menu = QMenu() |
|
167 menu.addAction(self.tr("Add Rule"), self.addRule) |
|
168 menu.addSeparator() |
|
169 act = menu.addAction(self.tr("Remove Rule"), self.removeRule) |
|
170 if item.parent() is None: |
|
171 act.setDisabled(True) |
|
172 |
|
173 menu.exec(self.viewport().mapToGlobal(pos)) |
|
174 |
|
175 def __itemChanged(self, itm): |
|
176 """ |
|
177 Private slot to handle the change of an item. |
|
178 |
|
179 @param itm changed item |
|
180 @type QTreeWidgetItem |
|
181 """ |
|
182 if itm is None or self.__itemChangingBlock: |
|
183 return |
|
184 |
|
185 self.__itemChangingBlock = True |
|
186 |
|
187 offset = itm.data(0, Qt.ItemDataRole.UserRole) |
|
188 oldRule = self.__subscription.rule(offset) |
|
189 |
|
190 if ( |
|
191 itm.checkState(0) == Qt.CheckState.Unchecked and |
|
192 oldRule.isEnabled() |
|
193 ): |
|
194 # Disable rule |
|
195 rule = self.__subscription.setRuleEnabled(offset, False) |
|
196 self.__adjustItemFeatures(itm, rule) |
|
197 elif ( |
|
198 itm.checkState(0) == Qt.CheckState.Checked and |
|
199 not oldRule.isEnabled() |
|
200 ): |
|
201 # Enable rule |
|
202 rule = self.__subscription.setRuleEnabled(offset, True) |
|
203 self.__adjustItemFeatures(itm, rule) |
|
204 elif self.__subscription.canEditRules(): |
|
205 from .AdBlockRule import AdBlockRule |
|
206 # Custom rule has been changed |
|
207 rule = self.__subscription.replaceRule( |
|
208 AdBlockRule(itm.text(0), self.__subscription), offset) |
|
209 self.__adjustItemFeatures(itm, rule) |
|
210 |
|
211 self.__itemChangingBlock = False |
|
212 |
|
213 def __copyFilter(self): |
|
214 """ |
|
215 Private slot to copy the current filter to the clipboard. |
|
216 """ |
|
217 item = self.currentItem() |
|
218 if item is not None: |
|
219 QApplication.clipboard().setText(item.text(0)) |
|
220 |
|
221 def __subscriptionChanged(self): |
|
222 """ |
|
223 Private slot handling a subscription change. |
|
224 """ |
|
225 self.refresh() |
|
226 |
|
227 self.__itemChangingBlock = True |
|
228 self.__topItem.setText( |
|
229 0, self.tr("{0} (recently updated)").format( |
|
230 self.__subscription.title())) |
|
231 self.__itemChangingBlock = False |
|
232 |
|
233 def __adjustItemFeatures(self, itm, rule): |
|
234 """ |
|
235 Private method to adjust an item. |
|
236 |
|
237 @param itm item to be adjusted |
|
238 @type QTreeWidgetItem |
|
239 @param rule rule for the adjustment |
|
240 @type AdBlockRule |
|
241 """ |
|
242 if not rule.isEnabled(): |
|
243 font = QFont() |
|
244 font.setItalic(True) |
|
245 if self.__darkMode: |
|
246 itm.setForeground(0, QColor("#a3a3a3")) |
|
247 else: |
|
248 itm.setForeground(0, QColor(Qt.GlobalColor.gray)) |
|
249 |
|
250 if not rule.isComment() and not rule.isHeader(): |
|
251 itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable) |
|
252 itm.setCheckState(0, Qt.CheckState.Unchecked) |
|
253 itm.setFont(0, font) |
|
254 |
|
255 return |
|
256 |
|
257 itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable) |
|
258 itm.setCheckState(0, Qt.CheckState.Checked) |
|
259 |
|
260 if rule.isCSSRule(): |
|
261 if self.__darkMode: |
|
262 itm.setForeground(0, QColor("#7897d1")) |
|
263 else: |
|
264 itm.setForeground(0, QColor(Qt.GlobalColor.darkBlue)) |
|
265 itm.setFont(0, QFont()) |
|
266 elif rule.isException(): |
|
267 if self.__darkMode: |
|
268 itm.setForeground(0, QColor("#75d180")) |
|
269 else: |
|
270 itm.setForeground(0, QColor(Qt.GlobalColor.darkGreen)) |
|
271 itm.setFont(0, QFont()) |
|
272 else: |
|
273 if self.__darkMode: |
|
274 itm.setForeground(0, QColor("#fefefe")) |
|
275 else: |
|
276 itm.setForeground(0, QColor("#000000")) |
|
277 itm.setFont(0, QFont()) |
|
278 |
|
279 def keyPressEvent(self, evt): |
|
280 """ |
|
281 Protected method handling key presses. |
|
282 |
|
283 @param evt key press event |
|
284 @type QKeyEvent |
|
285 """ |
|
286 if ( |
|
287 evt.key() == Qt.Key.Key_C and |
|
288 evt.modifiers() & Qt.KeyboardModifier.ControlModifier |
|
289 ): |
|
290 self.__copyFilter() |
|
291 elif evt.key() == Qt.Key.Key_Delete: |
|
292 self.removeRule() |
|
293 else: |
|
294 super().keyPressEvent(evt) |