eric6/Helpviewer/History/HistoryTreeModel.py

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

eric ide

mercurial