Helpviewer/History/HistoryFilterModel.py

changeset 0
de9c2efb9d02
child 6
52e8c820d0dd
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the history filter model.
8 """
9
10 from PyQt4.QtCore import *
11 from PyQt4.QtGui import QAbstractProxyModel
12
13 from HistoryModel import HistoryModel
14
15 class HistoryData(object):
16 """
17 Class storing some history data.
18 """
19 def __init__(self, offset, frequency = 0):
20 """
21 Constructor
22
23 @param offset tail offset (integer)
24 @param frequency frequency (integer)
25 """
26 self.tailOffset = offset
27 self.frequency = frequency
28
29 def __eq__(self, other):
30 """
31 Special method implementing equality.
32
33 @param other reference to the object to check against (HistoryData)
34 @return flag indicating equality (boolean)
35 """
36 return self.tailOffset == other.tailOffset and \
37 (self.frequency == -1 or other.frequency == -1 or \
38 self.frequency == other.frequency)
39
40 def __lt__(self, other):
41 """
42 Special method determining less relation.
43
44 Note: Like the actual history entries the index mapping is sorted in reverse
45 order by offset
46
47 @param other reference to the history data object to compare against
48 (HistoryEntry)
49 @return flag indicating less (boolean)
50 """
51 return self.tailOffset > other.tailOffset
52
53 class HistoryFilterModel(QAbstractProxyModel):
54 """
55 Class implementing the history filter model.
56 """
57 FrequencyRole = HistoryModel.MaxRole + 1
58 MaxRole = FrequencyRole
59
60 def __init__(self, sourceModel, parent = None):
61 """
62 Constructor
63
64 @param sourceModel reference to the source model (QAbstractItemModel)
65 @param parent reference to the parent object (QObject)
66 """
67 QAbstractProxyModel.__init__(self, parent)
68
69 self.__loaded = False
70 self.__filteredRows = []
71 self.__historyDict = {}
72 self.__scaleTime = QDateTime()
73
74 self.setSourceModel(sourceModel)
75
76 def historyContains(self, url):
77 """
78 Public method to check the history for an entry.
79
80 @param url URL to check for (string)
81 @return flag indicating success (boolean)
82 """
83 self.__load()
84 return url in self.__historyDict
85
86 def historyLocation(self, url):
87 """
88 Public method to get the row number of an entry in the source model.
89
90 @param url URL to check for (tring)
91 @return row number in the source model (integer)
92 """
93 self.__load()
94 if url not in self.__historyDict:
95 return 0
96
97 return self.sourceModel().rowCount() - self.__historyDict[url]
98
99 def data(self, index, role = Qt.DisplayRole):
100 """
101 Public method to get data from the model.
102
103 @param index index of history entry to get data for (QModelIndex)
104 @param role data role (integer)
105 @return history entry data (QVariant)
106 """
107 if role == self.FrequencyRole and index.isValid():
108 return QVariant(self.__filteredRows[index.row()].frequency)
109
110 return QAbstractProxyModel.data(self, index, role)
111
112 def setSourceModel(self, sourceModel):
113 """
114 Public method to set the source model.
115
116 @param sourceModel reference to the source model (QAbstractItemModel)
117 """
118 if self.sourceModel() is not None:
119 self.disconnect(self.sourceModel(),
120 SIGNAL("modelReset()"),
121 self.__sourceReset)
122 self.disconnect(self.sourceModel(),
123 SIGNAL("dataChanged(const QModelIndex&, const QModelIndex&)"),
124 self.__sourceDataChanged)
125 self.disconnect(self.sourceModel(),
126 SIGNAL("rowsInserted(const QModelIndex &, int, int)"),
127 self.__sourceRowsInserted)
128 self.disconnect(self.sourceModel(),
129 SIGNAL("rowsRemoved(const QModelIndex &, int, int)"),
130 self.__sourceRowsRemoved)
131
132 QAbstractProxyModel.setSourceModel(self, sourceModel)
133
134 if self.sourceModel() is not None:
135 self.__loaded = False
136 self.connect(self.sourceModel(),
137 SIGNAL("modelReset()"),
138 self.__sourceReset)
139 self.connect(self.sourceModel(),
140 SIGNAL("dataChanged(const QModelIndex&, const QModelIndex&)"),
141 self.__sourceDataChanged)
142 self.connect(self.sourceModel(),
143 SIGNAL("rowsInserted(const QModelIndex &, int, int)"),
144 self.__sourceRowsInserted)
145 self.connect(self.sourceModel(),
146 SIGNAL("rowsRemoved(const QModelIndex &, int, int)"),
147 self.__sourceRowsRemoved)
148
149 def __sourceDataChanged(self, topLeft, bottomRight):
150 """
151 Private slot to handle the change of data of the source model.
152
153 @param topLeft index of top left data element (QModelIndex)
154 @param bottomRight index of bottom right data element (QModelIndex)
155 """
156 self.emit(SIGNAL("dataChanged(const QModelIndex&, const QModelIndex&)"),
157 self.mapFromSource(topLeft), self.mapFromSource(bottomRight))
158
159 def headerData(self, section, orientation, role = Qt.DisplayRole):
160 """
161 Public method to get the header data.
162
163 @param section section number (integer)
164 @param orientation header orientation (Qt.Orientation)
165 @param role data role (integer)
166 @return header data (QVariant)
167 """
168 return self.sourceModel().headerData(section, orientation, role)
169
170 def recalculateFrequencies(self):
171 """
172 Public method to recalculate the frequencies.
173 """
174 self.__sourceReset()
175
176 def __sourceReset(self):
177 """
178 Private slot to handle a reset of the source model.
179 """
180 self.__loaded = False
181 self.reset()
182
183 def rowCount(self, parent = QModelIndex()):
184 """
185 Public method to determine the number of rows.
186
187 @param parent index of parent (QModelIndex)
188 @return number of rows (integer)
189 """
190 self.__load()
191 if parent.isValid():
192 return 0
193 return len(self.__historyDict)
194
195 def columnCount(self, parent = QModelIndex()):
196 """
197 Public method to get the number of columns.
198
199 @param parent index of parent (QModelIndex)
200 @return number of columns (integer)
201 """
202 return self.sourceModel().columnCount(self.mapToSource(parent))
203
204 def mapToSource(self, proxyIndex):
205 """
206 Public method to map an index to the source model index.
207
208 @param proxyIndex reference to a proxy model index (QModelIndex)
209 @return source model index (QModelIndex)
210 """
211 self.__load()
212 sourceRow = self.sourceModel().rowCount() - proxyIndex.internalId()
213 return self.sourceModel().index(sourceRow, proxyIndex.column())
214
215 def mapFromSource(self, sourceIndex):
216 """
217 Public method to map an index to the proxy model index.
218
219 @param sourceIndex reference to a source model index (QModelIndex)
220 @return proxy model index (QModelIndex)
221 """
222 self.__load()
223 url = unicode(sourceIndex.data(HistoryModel.UrlStringRole).toString())
224 if url not in self.__historyDict:
225 return QModelIndex()
226
227 sourceOffset = self.sourceModel().rowCount() - sourceIndex.row()
228
229 try:
230 row = self.__filteredRows.index(HistoryData(sourceOffset, -1))
231 except ValueError:
232 return QModelIndex()
233
234 return self.createIndex(row, sourceIndex.column(), sourceOffset)
235
236 def index(self, row, column, parent = QModelIndex()):
237 """
238 Public method to create an index.
239
240 @param row row number for the index (integer)
241 @param column column number for the index (integer)
242 @param parent index of the parent item (QModelIndex)
243 @return requested index (QModelIndex)
244 """
245 self.__load()
246 if row < 0 or row >= self.rowCount(parent) or \
247 column < 0 or column >= self.columnCount(parent):
248 return QModelIndex()
249
250 return self.createIndex(row, column, self.__filteredRows[row].tailOffset)
251
252 def parent(self, index):
253 """
254 Public method to get the parent index.
255
256 @param index index of item to get parent (QModelIndex)
257 @return index of parent (QModelIndex)
258 """
259 return QModelIndex()
260
261 def __load(self):
262 """
263 Private method to load the model data.
264 """
265 if self.__loaded:
266 return
267
268 self.__filteredRows = []
269 self.__historyDict = {}
270 self.__scaleTime = QDateTime.currentDateTime()
271
272 for sourceRow in range(self.sourceModel().rowCount()):
273 idx = self.sourceModel().index(sourceRow, 0)
274 url = unicode(idx.data(HistoryModel.UrlStringRole).toString())
275 if url not in self.__historyDict:
276 sourceOffset = self.sourceModel().rowCount() - sourceRow
277 self.__filteredRows.append(
278 HistoryData(sourceOffset, self.__frequencyScore(idx)))
279 self.__historyDict[url] = sourceOffset
280 else:
281 # the url is known already, so just update the frequency score
282 row = self.__filteredRows.index(HistoryData(self.__historyDict[url], -1))
283 self.__filteredRows[row].frequency += self.__frequencyScore(idx)
284
285 self.__loaded = True
286
287 def __sourceRowsInserted(self, parent, start, end):
288 """
289 Private slot to handle the insertion of data in the source model.
290
291 @param parent reference to the parent index (QModelIndex)
292 @param start start row (integer)
293 @param end end row (integer)
294 """
295 if start == end and start == 0:
296 if not self.__loaded:
297 return
298
299 idx = self.sourceModel().index(start, 0, parent)
300 url = idx.data(HistoryModel.UrlStringRole).toString()
301 currentFrequency = 0
302 if url in self.__historyDict:
303 row = self.__filteredRows.index(HistoryData(self.__historyDict[url], -1))
304 currentFrequency = self.__filteredRows[row].frequency
305 self.beginRemoveRows(QModelIndex(), row, row)
306 del self.__filteredRows[row]
307 del self.__historyDict[url]
308 self.endRemoveRows()
309
310 self.beginInsertRows(QModelIndex(), 0, 0)
311 self.__filteredRows.insert(0, HistoryData(self.sourceModel().rowCount(),
312 self.__frequencyScore(idx) + currentFrequency))
313 self.__historyDict[url] = self.sourceModel().rowCount()
314 self.endInsertRows()
315
316 def __sourceRowsRemoved(self, parent, start, end):
317 """
318 Private slot to handle the removal of data in the source model.
319
320 @param parent reference to the parent index (QModelIndex)
321 @param start start row (integer)
322 @param end end row (integer)
323 """
324 self.__sourceReset()
325
326 def removeRows(self, row, count, parent = QModelIndex()):
327 """
328 Public method to remove entries from the model.
329
330 @param row row of the first entry to remove (integer)
331 @param count number of entries to remove (integer)
332 @param index of the parent entry (QModelIndex)
333 @return flag indicating successful removal (boolean)
334 """
335 if row < 0 or \
336 count <= 0 or \
337 row + count > self.rowCount(parent) or \
338 parent.isValid():
339 return False
340
341 lastRow = row + count - 1
342 self.disconnect(self.sourceModel(),
343 SIGNAL("rowsRemoved(const QModelIndex &, int, int)"),
344 self.__sourceRowsRemoved)
345 self.beginRemoveRows(parent, row, lastRow)
346 oldCount = self.rowCount()
347 start = self.sourceModel().rowCount() - self.__filteredRows[row].tailOffset
348 end = self.sourceModel().rowCount() - self.__filteredRows[lastRow].tailOffset
349 self.sourceModel().removeRows(start, end - start + 1)
350 self.endRemoveRows()
351 self.connect(self.sourceModel(),
352 SIGNAL("rowsRemoved(const QModelIndex &, int, int)"),
353 self.__sourceRowsRemoved)
354 self.__loaded = False
355 if oldCount - count != self.rowCount():
356 self.reset()
357 return True
358
359 def __frequencyScore(self, sourceIndex):
360 """
361 Private method to calculate the frequency score.
362
363 @param sourceIndex index of the source model (QModelIndex)
364 @return frequency score (integer)
365 """
366 loadTime = \
367 self.sourceModel().data(sourceIndex, HistoryModel.DateTimeRole).toDateTime()
368 days = loadTime.daysTo(self.__scaleTime)
369
370 if days <= 1:
371 return 100
372 elif days < 8: # within the last week
373 return 90
374 elif days < 15: # within the last two weeks
375 return 70
376 elif days < 31: # within the last month
377 return 50
378 elif days < 91: # within the last 3 months
379 return 30
380 else:
381 return 10

eric ide

mercurial