src/eric7/WebBrowser/History/HistoryTreeModel.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the history tree model.
8 """
9
10 import bisect
11
12 from PyQt6.QtCore import Qt, QModelIndex, QDate, QAbstractProxyModel
13
14 from .HistoryModel import HistoryModel
15
16 import UI.PixmapCache
17
18
19 class HistoryTreeModel(QAbstractProxyModel):
20 """
21 Class implementing the history tree model.
22 """
23 def __init__(self, sourceModel, parent=None):
24 """
25 Constructor
26
27 @param sourceModel reference to the source model (QAbstractItemModel)
28 @param parent reference to the parent object (QObject)
29 """
30 super().__init__(parent)
31
32 self.__sourceRowCache = []
33 self.__removingDown = False
34
35 self.setSourceModel(sourceModel)
36
37 def headerData(self, section, orientation,
38 role=Qt.ItemDataRole.DisplayRole):
39 """
40 Public method to get the header data.
41
42 @param section section number (integer)
43 @param orientation header orientation (Qt.Orientation)
44 @param role data role (Qt.ItemDataRole)
45 @return header data
46 """
47 return self.sourceModel().headerData(section, orientation, role)
48
49 def data(self, index, role=Qt.ItemDataRole.DisplayRole):
50 """
51 Public method to get data from the model.
52
53 @param index index of history entry to get data for (QModelIndex)
54 @param role data role (integer)
55 @return history entry data
56 """
57 if role in [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole]:
58 start = index.internalId()
59 if start == 0:
60 offset = self.__sourceDateRow(index.row())
61 if index.column() == 0:
62 idx = self.sourceModel().index(offset, 0)
63 date = idx.data(HistoryModel.DateRole)
64 if date == QDate.currentDate():
65 return self.tr("Earlier Today")
66 return date.toString("yyyy-MM-dd")
67 if index.column() == 1:
68 return self.tr(
69 "%n item(s)", "",
70 self.rowCount(index.sibling(index.row(), 0)))
71
72 elif (
73 role == Qt.ItemDataRole.DecorationRole and
74 index.column() == 0 and
75 not index.parent().isValid()
76 ):
77 return UI.PixmapCache.getIcon("history")
78
79 elif (
80 role == HistoryModel.DateRole and
81 index.column() == 0 and
82 index.internalId() == 0
83 ):
84 offset = self.__sourceDateRow(index.row())
85 idx = self.sourceModel().index(offset, 0)
86 return idx.data(HistoryModel.DateRole)
87
88 return QAbstractProxyModel.data(self, index, role)
89
90 def columnCount(self, parent=None):
91 """
92 Public method to get the number of columns.
93
94 @param parent index of parent (QModelIndex)
95 @return number of columns (integer)
96 """
97 if parent is None:
98 parent = QModelIndex()
99
100 return self.sourceModel().columnCount(self.mapToSource(parent))
101
102 def rowCount(self, parent=None):
103 """
104 Public method to determine the number of rows.
105
106 @param parent index of parent (QModelIndex)
107 @return number of rows (integer)
108 """
109 if parent is None:
110 parent = QModelIndex()
111
112 if (
113 parent.internalId() != 0 or
114 parent.column() > 0 or
115 self.sourceModel() is None
116 ):
117 return 0
118
119 # row count OF dates
120 if not parent.isValid():
121 if self.__sourceRowCache:
122 return len(self.__sourceRowCache)
123
124 currentDate = QDate()
125 rows = 0
126 totalRows = self.sourceModel().rowCount()
127
128 for row in range(totalRows):
129 rowDate = self.sourceModel().index(row, 0).data(
130 HistoryModel.DateRole)
131 if rowDate != currentDate:
132 self.__sourceRowCache.append(row)
133 currentDate = rowDate
134 rows += 1
135 return rows
136
137 # row count FOR a date
138 start = self.__sourceDateRow(parent.row())
139 end = self.__sourceDateRow(parent.row() + 1)
140 return end - start
141
142 def __sourceDateRow(self, row):
143 """
144 Private method to translate the top level date row into the offset
145 where that date starts.
146
147 @param row row number of the date (integer)
148 @return offset where that date starts (integer)
149 """
150 if row <= 0:
151 return 0
152
153 if len(self.__sourceRowCache) == 0:
154 self.rowCount(QModelIndex())
155
156 if row >= len(self.__sourceRowCache):
157 if self.sourceModel() is None:
158 return 0
159 return self.sourceModel().rowCount()
160
161 return self.__sourceRowCache[row]
162
163 def mapToSource(self, proxyIndex):
164 """
165 Public method to map an index to the source model index.
166
167 @param proxyIndex reference to a proxy model index (QModelIndex)
168 @return source model index (QModelIndex)
169 """
170 offset = proxyIndex.internalId()
171 if offset == 0:
172 return QModelIndex()
173 startDateRow = self.__sourceDateRow(offset - 1)
174 return self.sourceModel().index(
175 startDateRow + proxyIndex.row(), proxyIndex.column())
176
177 def index(self, row, column, parent=None):
178 """
179 Public method to create an index.
180
181 @param row row number for the index (integer)
182 @param column column number for the index (integer)
183 @param parent index of the parent item (QModelIndex)
184 @return requested index (QModelIndex)
185 """
186 if parent is None:
187 parent = QModelIndex()
188
189 if (
190 row < 0 or
191 column < 0 or
192 column >= self.columnCount(parent) or
193 parent.column() > 0
194 ):
195 return QModelIndex()
196
197 if not parent.isValid():
198 return self.createIndex(row, column, 0)
199 return self.createIndex(row, column, parent.row() + 1)
200
201 def parent(self, index):
202 """
203 Public method to get the parent index.
204
205 @param index index of item to get parent (QModelIndex)
206 @return index of parent (QModelIndex)
207 """
208 offset = index.internalId()
209 if offset == 0 or not index.isValid():
210 return QModelIndex()
211 return self.createIndex(offset - 1, 0, 0)
212
213 def hasChildren(self, parent=None):
214 """
215 Public method to check, if an entry has some children.
216
217 @param parent index of the entry to check (QModelIndex)
218 @return flag indicating the presence of children (boolean)
219 """
220 if parent is None:
221 parent = QModelIndex()
222
223 grandparent = parent.parent()
224 if not grandparent.isValid():
225 return True
226 return False
227
228 def flags(self, index):
229 """
230 Public method to get the item flags.
231
232 @param index index of the item (QModelIndex)
233 @return flags (Qt.ItemFlags)
234 """
235 return (
236 Qt.ItemFlag.NoItemFlags
237 if not index.isValid() else
238 (Qt.ItemFlag.ItemIsSelectable |
239 Qt.ItemFlag.ItemIsEnabled |
240 Qt.ItemFlag.ItemIsDragEnabled)
241 )
242
243 def setSourceModel(self, sourceModel):
244 """
245 Public method to set the source model.
246
247 @param sourceModel reference to the source model (QAbstractItemModel)
248 """
249 if self.sourceModel() is not None:
250 self.sourceModel().modelReset.disconnect(self.__sourceReset)
251 self.sourceModel().layoutChanged.disconnect(self.__sourceReset)
252 self.sourceModel().rowsInserted.disconnect(
253 self.__sourceRowsInserted)
254 self.sourceModel().rowsRemoved.disconnect(self.__sourceRowsRemoved)
255
256 super().setSourceModel(sourceModel)
257
258 if self.sourceModel() is not None:
259 self.__loaded = False
260 self.sourceModel().modelReset.connect(self.__sourceReset)
261 self.sourceModel().layoutChanged.connect(self.__sourceReset)
262 self.sourceModel().rowsInserted.connect(self.__sourceRowsInserted)
263 self.sourceModel().rowsRemoved.connect(self.__sourceRowsRemoved)
264
265 self.beginResetModel()
266 self.endResetModel()
267
268 def __sourceReset(self):
269 """
270 Private slot to handle a reset of the source model.
271 """
272 self.beginResetModel()
273 self.__sourceRowCache = []
274 self.endResetModel()
275
276 def __sourceRowsInserted(self, parent, start, end):
277 """
278 Private slot to handle the insertion of data in the source model.
279
280 @param parent reference to the parent index (QModelIndex)
281 @param start start row (integer)
282 @param end end row (integer)
283 """
284 if not parent.isValid():
285 if start != 0 or start != end:
286 self.beginResetModel()
287 self.__sourceRowCache = []
288 self.endResetModel()
289 return
290
291 self.__sourceRowCache = []
292 treeIndex = self.mapFromSource(self.sourceModel().index(start, 0))
293 treeParent = treeIndex.parent()
294 if self.rowCount(treeParent) == 1:
295 self.beginInsertRows(QModelIndex(), 0, 0)
296 self.endInsertRows()
297 else:
298 self.beginInsertRows(treeParent, treeIndex.row(),
299 treeIndex.row())
300 self.endInsertRows()
301
302 def mapFromSource(self, sourceIndex):
303 """
304 Public method to map an index to the proxy model index.
305
306 @param sourceIndex reference to a source model index (QModelIndex)
307 @return proxy model index (QModelIndex)
308 """
309 if not sourceIndex.isValid():
310 return QModelIndex()
311
312 if len(self.__sourceRowCache) == 0:
313 self.rowCount(QModelIndex())
314
315 try:
316 row = self.__sourceRowCache.index(sourceIndex.row())
317 except ValueError:
318 row = bisect.bisect_left(self.__sourceRowCache, sourceIndex.row())
319 if (
320 row == len(self.__sourceRowCache) or
321 self.__sourceRowCache[row] != sourceIndex.row()
322 ):
323 row -= 1
324 dateRow = max(0, row)
325 row = sourceIndex.row() - self.__sourceRowCache[dateRow]
326 return self.createIndex(row, sourceIndex.column(), dateRow + 1)
327
328 def removeRows(self, row, count, parent=None):
329 """
330 Public method to remove entries from the model.
331
332 @param row row of the first entry to remove (integer)
333 @param count number of entries to remove (integer)
334 @param parent index of the parent entry (QModelIndex)
335 @return flag indicating successful removal (boolean)
336 """
337 if parent is None:
338 parent = QModelIndex()
339
340 if (
341 row < 0 or
342 count <= 0 or
343 row + count > self.rowCount(parent)
344 ):
345 return False
346
347 self.__removingDown = True
348 if parent.isValid() and self.rowCount(parent) == count - row:
349 self.beginRemoveRows(QModelIndex(), parent.row(), parent.row())
350 else:
351 self.beginRemoveRows(parent, row, row + count - 1)
352 if parent.isValid():
353 # removing pages
354 offset = self.__sourceDateRow(parent.row())
355 return self.sourceModel().removeRows(offset + row, count)
356 else:
357 # removing whole dates
358 for i in range(row + count - 1, row - 1, -1):
359 dateParent = self.index(i, 0)
360 offset = self.__sourceDateRow(dateParent.row())
361 if not self.sourceModel().removeRows(
362 offset, self.rowCount(dateParent)):
363 return False
364 return True
365
366 def __sourceRowsRemoved(self, parent, start, end):
367 """
368 Private slot to handle the removal of data in the source model.
369
370 @param parent reference to the parent index (QModelIndex)
371 @param start start row (integer)
372 @param end end row (integer)
373 """
374 if not self.__removingDown:
375 self.beginResetModel()
376 self.__sourceRowCache = []
377 self.endResetModel()
378 return
379
380 if not parent.isValid():
381 if self.__sourceRowCache:
382 i = end
383 while i >= start:
384 try:
385 ind = self.__sourceRowCache.index(i)
386 except ValueError:
387 ind = bisect.bisect_left(self.__sourceRowCache, i)
388 if (
389 ind == len(self.__sourceRowCache) or
390 self.__sourceRowCache[ind] != i
391 ):
392 ind -= 1
393 row = max(0, ind)
394 offset = self.__sourceRowCache[row]
395 dateParent = self.index(row, 0)
396 # If we can remove all the rows in the date do that
397 # and skip over them.
398 rc = self.rowCount(dateParent)
399 if i - rc + 1 == offset and start <= i - rc + 1:
400 del self.__sourceRowCache[row]
401 i -= rc + 1
402 else:
403 row += 1
404 i -= 1
405 for j in range(row, len(self.__sourceRowCache)):
406 self.__sourceRowCache[j] -= 1
407
408 if self.__removingDown:
409 self.endRemoveRows()
410 self.__removingDown = False

eric ide

mercurial