eric7/WebBrowser/History/HistoryTreeModel.py

branch
eric7
changeset 8312
800c432b34c8
parent 8227
349308e84eeb
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the history tree model.
8 """
9
10 import bisect
11
12 from PyQt5.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 if not index.isValid():
236 return Qt.ItemFlags(Qt.ItemFlag.NoItemFlags)
237 return Qt.ItemFlags(
238 Qt.ItemFlag.ItemIsSelectable |
239 Qt.ItemFlag.ItemIsEnabled |
240 Qt.ItemFlag.ItemIsDragEnabled)
241
242 def setSourceModel(self, sourceModel):
243 """
244 Public method to set the source model.
245
246 @param sourceModel reference to the source model (QAbstractItemModel)
247 """
248 if self.sourceModel() is not None:
249 self.sourceModel().modelReset.disconnect(self.__sourceReset)
250 self.sourceModel().layoutChanged.disconnect(self.__sourceReset)
251 self.sourceModel().rowsInserted.disconnect(
252 self.__sourceRowsInserted)
253 self.sourceModel().rowsRemoved.disconnect(self.__sourceRowsRemoved)
254
255 super().setSourceModel(sourceModel)
256
257 if self.sourceModel() is not None:
258 self.__loaded = False
259 self.sourceModel().modelReset.connect(self.__sourceReset)
260 self.sourceModel().layoutChanged.connect(self.__sourceReset)
261 self.sourceModel().rowsInserted.connect(self.__sourceRowsInserted)
262 self.sourceModel().rowsRemoved.connect(self.__sourceRowsRemoved)
263
264 self.beginResetModel()
265 self.endResetModel()
266
267 def __sourceReset(self):
268 """
269 Private slot to handle a reset of the source model.
270 """
271 self.beginResetModel()
272 self.__sourceRowCache = []
273 self.endResetModel()
274
275 def __sourceRowsInserted(self, parent, start, end):
276 """
277 Private slot to handle the insertion of data in the source model.
278
279 @param parent reference to the parent index (QModelIndex)
280 @param start start row (integer)
281 @param end end row (integer)
282 """
283 if not parent.isValid():
284 if start != 0 or start != end:
285 self.beginResetModel()
286 self.__sourceRowCache = []
287 self.endResetModel()
288 return
289
290 self.__sourceRowCache = []
291 treeIndex = self.mapFromSource(self.sourceModel().index(start, 0))
292 treeParent = treeIndex.parent()
293 if self.rowCount(treeParent) == 1:
294 self.beginInsertRows(QModelIndex(), 0, 0)
295 self.endInsertRows()
296 else:
297 self.beginInsertRows(treeParent, treeIndex.row(),
298 treeIndex.row())
299 self.endInsertRows()
300
301 def mapFromSource(self, sourceIndex):
302 """
303 Public method to map an index to the proxy model index.
304
305 @param sourceIndex reference to a source model index (QModelIndex)
306 @return proxy model index (QModelIndex)
307 """
308 if not sourceIndex.isValid():
309 return QModelIndex()
310
311 if len(self.__sourceRowCache) == 0:
312 self.rowCount(QModelIndex())
313
314 try:
315 row = self.__sourceRowCache.index(sourceIndex.row())
316 except ValueError:
317 row = bisect.bisect_left(self.__sourceRowCache, sourceIndex.row())
318 if (
319 row == len(self.__sourceRowCache) or
320 self.__sourceRowCache[row] != sourceIndex.row()
321 ):
322 row -= 1
323 dateRow = max(0, row)
324 row = sourceIndex.row() - self.__sourceRowCache[dateRow]
325 return self.createIndex(row, sourceIndex.column(), dateRow + 1)
326
327 def removeRows(self, row, count, parent=None):
328 """
329 Public method to remove entries from the model.
330
331 @param row row of the first entry to remove (integer)
332 @param count number of entries to remove (integer)
333 @param parent index of the parent entry (QModelIndex)
334 @return flag indicating successful removal (boolean)
335 """
336 if parent is None:
337 parent = QModelIndex()
338
339 if (
340 row < 0 or
341 count <= 0 or
342 row + count > self.rowCount(parent)
343 ):
344 return False
345
346 self.__removingDown = True
347 if parent.isValid() and self.rowCount(parent) == count - row:
348 self.beginRemoveRows(QModelIndex(), parent.row(), parent.row())
349 else:
350 self.beginRemoveRows(parent, row, row + count - 1)
351 if parent.isValid():
352 # removing pages
353 offset = self.__sourceDateRow(parent.row())
354 return self.sourceModel().removeRows(offset + row, count)
355 else:
356 # removing whole dates
357 for i in range(row + count - 1, row - 1, -1):
358 dateParent = self.index(i, 0)
359 offset = self.__sourceDateRow(dateParent.row())
360 if not self.sourceModel().removeRows(
361 offset, self.rowCount(dateParent)):
362 return False
363 return True
364
365 def __sourceRowsRemoved(self, parent, start, end):
366 """
367 Private slot to handle the removal of data in the source model.
368
369 @param parent reference to the parent index (QModelIndex)
370 @param start start row (integer)
371 @param end end row (integer)
372 """
373 if not self.__removingDown:
374 self.beginResetModel()
375 self.__sourceRowCache = []
376 self.endResetModel()
377 return
378
379 if not parent.isValid():
380 if self.__sourceRowCache:
381 i = end
382 while i >= start:
383 try:
384 ind = self.__sourceRowCache.index(i)
385 except ValueError:
386 ind = bisect.bisect_left(self.__sourceRowCache, i)
387 if (
388 ind == len(self.__sourceRowCache) or
389 self.__sourceRowCache[ind] != i
390 ):
391 ind -= 1
392 row = max(0, ind)
393 offset = self.__sourceRowCache[row]
394 dateParent = self.index(row, 0)
395 # If we can remove all the rows in the date do that
396 # and skip over them.
397 rc = self.rowCount(dateParent)
398 if i - rc + 1 == offset and start <= i - rc + 1:
399 del self.__sourceRowCache[row]
400 i -= rc + 1
401 else:
402 row += 1
403 i -= 1
404 for j in range(row, len(self.__sourceRowCache)):
405 self.__sourceRowCache[j] -= 1
406
407 if self.__removingDown:
408 self.endRemoveRows()
409 self.__removingDown = False

eric ide

mercurial