1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing specialized tree views. |
|
8 """ |
|
9 |
|
10 import enum |
|
11 |
|
12 from PyQt6.QtCore import pyqtSignal, Qt |
|
13 from PyQt6.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView |
|
14 |
|
15 |
|
16 class E5TreeWidgetItemsState(enum.Enum): |
|
17 """ |
|
18 Class defining the items expansion state. |
|
19 """ |
|
20 COLLAPSED = 0 |
|
21 EXPANDED = 1 |
|
22 |
|
23 |
|
24 class E5TreeWidget(QTreeWidget): |
|
25 """ |
|
26 Class implementing an extended tree widget. |
|
27 |
|
28 @signal itemControlClicked(QTreeWidgetItem) emitted after a Ctrl-Click |
|
29 on an item |
|
30 @signal itemMiddleButtonClicked(QTreeWidgetItem) emitted after a click |
|
31 of the middle button on an item |
|
32 """ |
|
33 itemControlClicked = pyqtSignal(QTreeWidgetItem) |
|
34 itemMiddleButtonClicked = pyqtSignal(QTreeWidgetItem) |
|
35 |
|
36 def __init__(self, parent=None): |
|
37 """ |
|
38 Constructor |
|
39 |
|
40 @param parent reference to the parent widget (QWidget) |
|
41 """ |
|
42 super().__init__(parent) |
|
43 |
|
44 self.__refreshAllItemsNeeded = True |
|
45 self.__allTreeItems = [] |
|
46 self.__showMode = E5TreeWidgetItemsState.COLLAPSED |
|
47 |
|
48 self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) |
|
49 |
|
50 self.itemChanged.connect(self.__scheduleRefresh) |
|
51 |
|
52 def setDefaultItemShowMode(self, mode): |
|
53 """ |
|
54 Public method to set the default item show mode. |
|
55 |
|
56 @param mode default mode |
|
57 @type E5TreeWidgetItemsState |
|
58 """ |
|
59 self.__showMode = mode |
|
60 |
|
61 def allItems(self): |
|
62 """ |
|
63 Public method to get a list of all items. |
|
64 |
|
65 @return list of all items (list of QTreeWidgetItem) |
|
66 """ |
|
67 if self.__refreshAllItemsNeeded: |
|
68 self.__allTreeItems = [] |
|
69 self.__iterateAllItems(None) |
|
70 self.__refreshAllItemsNeeded = False |
|
71 |
|
72 return self.__allTreeItems |
|
73 |
|
74 def appendToParentItem(self, parent, item): |
|
75 """ |
|
76 Public method to append an item to a parent item. |
|
77 |
|
78 @param parent text of the parent item (string) or |
|
79 the parent item (QTreeWidgetItem) |
|
80 @param item item to be appended (QTreeWidgetItem) |
|
81 @return flag indicating success (boolean) |
|
82 @exception RuntimeError raised to indicate an illegal type for |
|
83 the parent |
|
84 """ |
|
85 if not isinstance(parent, (QTreeWidgetItem, str)): |
|
86 raise RuntimeError("illegal type for parent") |
|
87 |
|
88 if isinstance(parent, QTreeWidgetItem): |
|
89 if parent is None or parent.treeWidget() != self: |
|
90 return False |
|
91 parentItem = parent |
|
92 else: |
|
93 lst = self.findItems(parent, Qt.MatchFlag.MatchExactly) |
|
94 if not lst: |
|
95 return False |
|
96 parentItem = lst[0] |
|
97 if parentItem is None: |
|
98 return False |
|
99 |
|
100 self.__allTreeItems.append(item) |
|
101 parentItem.addChild(item) |
|
102 return True |
|
103 |
|
104 def prependToParentItem(self, parent, item): |
|
105 """ |
|
106 Public method to prepend an item to a parent item. |
|
107 |
|
108 @param parent text of the parent item (string) or |
|
109 the parent item (QTreeWidgetItem) |
|
110 @param item item to be prepended (QTreeWidgetItem) |
|
111 @return flag indicating success (boolean) |
|
112 @exception RuntimeError raised to indicate an illegal type for |
|
113 the parent |
|
114 """ |
|
115 if not isinstance(parent, (QTreeWidgetItem, str)): |
|
116 raise RuntimeError("illegal type for parent") |
|
117 |
|
118 if isinstance(parent, QTreeWidgetItem): |
|
119 if parent is None or parent.treeWidget() != self: |
|
120 return False |
|
121 parentItem = parent |
|
122 else: |
|
123 lst = self.findItems(parent, Qt.MatchFlag.MatchExactly) |
|
124 if not lst: |
|
125 return False |
|
126 parentItem = lst[0] |
|
127 if parentItem is None: |
|
128 return False |
|
129 |
|
130 self.__allTreeItems.append(item) |
|
131 parentItem.insertChild(0, item) |
|
132 return True |
|
133 |
|
134 def addTopLevelItem(self, item): |
|
135 """ |
|
136 Public method to add a top level item. |
|
137 |
|
138 @param item item to be added as a top level item (QTreeWidgetItem) |
|
139 """ |
|
140 self.__allTreeItems.append(item) |
|
141 super().addTopLevelItem(item) |
|
142 |
|
143 def addTopLevelItems(self, items): |
|
144 """ |
|
145 Public method to add a list of top level items. |
|
146 |
|
147 @param items items to be added as top level items |
|
148 (list of QTreeWidgetItem) |
|
149 """ |
|
150 self.__allTreeItems.extend(items) |
|
151 super().addTopLevelItems(items) |
|
152 |
|
153 def insertTopLevelItem(self, index, item): |
|
154 """ |
|
155 Public method to insert a top level item. |
|
156 |
|
157 @param index index for the insertion (integer) |
|
158 @param item item to be inserted as a top level item (QTreeWidgetItem) |
|
159 """ |
|
160 self.__allTreeItems.append(item) |
|
161 super().insertTopLevelItem(index, item) |
|
162 |
|
163 def insertTopLevelItems(self, index, items): |
|
164 """ |
|
165 Public method to insert a list of top level items. |
|
166 |
|
167 @param index index for the insertion (integer) |
|
168 @param items items to be inserted as top level items |
|
169 (list of QTreeWidgetItem) |
|
170 """ |
|
171 self.__allTreeItems.extend(items) |
|
172 super().insertTopLevelItems(index, items) |
|
173 |
|
174 def deleteItem(self, item): |
|
175 """ |
|
176 Public method to delete an item. |
|
177 |
|
178 @param item item to be deleted (QTreeWidgetItem) |
|
179 """ |
|
180 if item in self.__allTreeItems: |
|
181 self.__allTreeItems.remove(item) |
|
182 |
|
183 self.__refreshAllItemsNeeded = True |
|
184 |
|
185 del item |
|
186 |
|
187 def deleteItems(self, items): |
|
188 """ |
|
189 Public method to delete a list of items. |
|
190 |
|
191 @param items items to be deleted (list of QTreeWidgetItem) |
|
192 """ |
|
193 for item in items: |
|
194 self.deleteItem(item) |
|
195 |
|
196 def filterString(self, filterStr): |
|
197 """ |
|
198 Public slot to set a new filter. |
|
199 |
|
200 @param filterStr filter to be set (string) |
|
201 """ |
|
202 self.expandAll() |
|
203 allItems = self.allItems() |
|
204 |
|
205 if filterStr: |
|
206 lFilter = filterStr.lower() |
|
207 for itm in allItems: |
|
208 itm.setHidden(lFilter not in itm.text(0).lower()) |
|
209 itm.setExpanded(True) |
|
210 for index in range(self.topLevelItemCount()): |
|
211 self.topLevelItem(index).setHidden(False) |
|
212 |
|
213 firstItm = self.topLevelItem(0) |
|
214 belowItm = self.itemBelow(firstItm) |
|
215 topLvlIndex = 0 |
|
216 while firstItm: |
|
217 if lFilter in firstItm.text(0).lower(): |
|
218 firstItm.setHidden(False) |
|
219 elif ( |
|
220 not firstItm.parent() and |
|
221 (not belowItm or not belowItm.parent()) |
|
222 ): |
|
223 firstItm.setHidden(True) |
|
224 elif not belowItm: |
|
225 break |
|
226 |
|
227 topLvlIndex += 1 |
|
228 firstItm = self.topLevelItem(topLvlIndex) |
|
229 belowItm = self.itemBelow(firstItm) |
|
230 else: |
|
231 for itm in allItems: |
|
232 itm.setHidden(False) |
|
233 for index in range(self.topLevelItemCount()): |
|
234 self.topLevelItem(index).setHidden(False) |
|
235 if self.__showMode == E5TreeWidgetItemsState.COLLAPSED: |
|
236 self.collapseAll() |
|
237 |
|
238 def clear(self): |
|
239 """ |
|
240 Public slot to clear the tree. |
|
241 """ |
|
242 self.__allTreeItems = [] |
|
243 super().clear() |
|
244 |
|
245 def __scheduleRefresh(self): |
|
246 """ |
|
247 Private slot to schedule a refresh of the tree. |
|
248 """ |
|
249 self.__refreshAllItemsNeeded = True |
|
250 |
|
251 def mousePressEvent(self, evt): |
|
252 """ |
|
253 Protected method handling mouse press events. |
|
254 |
|
255 @param evt mouse press event (QMouseEvent) |
|
256 """ |
|
257 if ( |
|
258 evt.modifiers() == Qt.KeyboardModifier.ControlModifier and |
|
259 evt.buttons() == Qt.MouseButton.LeftButton |
|
260 ): |
|
261 self.itemControlClicked.emit(self.itemAt(evt.position().toPoint())) |
|
262 return |
|
263 elif evt.buttons() == Qt.MouseButton.MiddleButton: |
|
264 self.itemMiddleButtonClicked.emit(self.itemAt(evt.position().toPoint())) |
|
265 return |
|
266 else: |
|
267 super().mousePressEvent(evt) |
|
268 |
|
269 def __iterateAllItems(self, parent): |
|
270 """ |
|
271 Private method to iterate over the child items of the parent. |
|
272 |
|
273 @param parent parent item to iterate (QTreeWidgetItem) |
|
274 """ |
|
275 count = parent.childCount() if parent else self.topLevelItemCount() |
|
276 |
|
277 for index in range(count): |
|
278 itm = parent.child(index) if parent else self.topLevelItem(index) |
|
279 |
|
280 if itm.childCount() == 0: |
|
281 self.__allTreeItems.append(itm) |
|
282 |
|
283 self.__iterateAllItems(itm) |
|