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