Messagelib

modelinvariantrowmapper.cpp
1 /******************************************************************************
2  *
3  * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <[email protected]>
4  *
5  * SPDX-License-Identifier: GPL-2.0-or-later
6  *
7  *******************************************************************************/
8 
9 #include "core/modelinvariantrowmapper.h"
10 #include "core/modelinvariantrowmapper_p.h"
11 #include "core/modelinvariantindex_p.h"
12 
13 #include <QTimer>
14 #include <QTime>
15 
16 #include "messagelist_debug.h"
17 
18 namespace MessageList {
19 namespace Core {
20 class RowShift
21 {
22 public:
23  int mMinimumRowIndex;
24  int mShift;
26 
27 public:
28  RowShift(int minRowIndex, int shift, QHash< int, ModelInvariantIndex * > *invariantHash)
29  : mMinimumRowIndex(minRowIndex)
30  , mShift(shift)
31  , mInvariantHash(invariantHash)
32  {
33  }
34 
35  ~RowShift()
36  {
37  for (const auto idx : qAsConst(*mInvariantHash)) {
38  idx->d->setRowMapper(nullptr);
39  }
40  delete mInvariantHash;
41  }
42 };
43 } // namespace Core
44 } // namespace MessageList
45 
46 using namespace MessageList::Core;
47 
48 ModelInvariantRowMapper::ModelInvariantRowMapper()
49  : d(new ModelInvariantRowMapperPrivate(this))
50 {
51  d->mRowShiftList = new QList< RowShift * >();
52  d->mCurrentShiftSerial = 0;
53  d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >();
54  d->mUpdateTimer = new QTimer(this);
55  d->mUpdateTimer->setSingleShot(true);
56  d->mLazyUpdateChunkInterval = 50;
57  d->mLazyUpdateIdleInterval = 50;
58 
59  connect(d->mUpdateTimer, &QTimer::timeout, this, [this]() {
60  d->slotPerformLazyUpdate();
61  });
62 }
63 
64 ModelInvariantRowMapper::~ModelInvariantRowMapper()
65 {
66  if (d->mUpdateTimer->isActive()) {
67  d->mUpdateTimer->stop();
68  }
69 
70  // FIXME: optimize this (it CAN be optimized)
71  for (const auto idx : qAsConst(*d->mCurrentInvariantHash)) {
72  idx->d->setRowMapper(nullptr);
73  }
74  delete d->mCurrentInvariantHash;
75 
76  if (d->mRowShiftList) {
77  while (!d->mRowShiftList->isEmpty()) {
78  delete d->mRowShiftList->takeFirst();
79  }
80 
81  delete d->mRowShiftList;
82  }
83 
84  delete d;
85 }
86 
87 void ModelInvariantRowMapperPrivate::killFirstRowShift()
88 {
89  RowShift *shift = mRowShiftList->at(0);
90 
91  Q_ASSERT(shift->mInvariantHash->isEmpty());
92 
93  delete shift;
94  mRowShiftList->removeAt(0);
95  mRemovedShiftCount++;
96  if (mRowShiftList->isEmpty()) {
97  delete mRowShiftList;
98  mRowShiftList = nullptr;
99  }
100 }
101 
102 void ModelInvariantRowMapperPrivate::indexDead(ModelInvariantIndex *invariant)
103 {
104  Q_ASSERT(invariant->d->rowMapper() == q);
105 
106  if (invariant->d->rowMapperSerial() == mCurrentShiftSerial) {
107  mCurrentInvariantHash->remove(invariant->d->modelIndexRow());
108  return;
109  }
110 
111  Q_ASSERT(invariant->d->rowMapperSerial() < mCurrentShiftSerial);
112 
113  if (!mRowShiftList) {
114  return; // not found (not requested yet or invalid index at all)
115  }
116 
117  uint invariantShiftIndex = invariant->d->rowMapperSerial() - mRemovedShiftCount;
118 
119  Q_ASSERT(invariantShiftIndex < static_cast< uint >(mRowShiftList->count()));
120 
121  RowShift *shift = mRowShiftList->at(invariantShiftIndex);
122 
123  Q_ASSERT(shift);
124 
125  shift->mInvariantHash->remove(invariant->d->modelIndexRow());
126 
127  if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
128  // no more invariants with serial <= invariant->d->rowMapperSerial()
129  killFirstRowShift();
130  }
131 }
132 
133 void ModelInvariantRowMapperPrivate::updateModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
134 {
135  // Here the invariant already belongs to this mapper. We ASSUME that it's somewhere
136  // in the history and not in the hash belonging to the current serial.
137  // modelIndexRow is the CURRENT model index row.
138  Q_ASSERT(invariantToFill->d->rowMapper() == q);
139 
140  uint invariantShiftIndex = invariantToFill->d->rowMapperSerial() - mRemovedShiftCount;
141 
142  Q_ASSERT(invariantShiftIndex < static_cast< uint >(mRowShiftList->count()));
143 
144  RowShift *shift = mRowShiftList->at(invariantShiftIndex);
145 
146  int count = shift->mInvariantHash->remove(invariantToFill->d->modelIndexRow());
147 
148  Q_ASSERT(count > 0);
149  Q_UNUSED(count);
150 
151  // update and make it belong to the current serial
152  invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
153 
154  Q_ASSERT(!mCurrentInvariantHash->contains(invariantToFill->d->modelIndexRow()));
155 
156  mCurrentInvariantHash->insert(invariantToFill->d->modelIndexRow(), invariantToFill);
157 
158  if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
159  // no more invariants with serial <= invariantToFill->rowMapperSerial()
160  killFirstRowShift();
161  }
162 }
163 
164 ModelInvariantIndex *ModelInvariantRowMapperPrivate::modelIndexRowToModelInvariantIndexInternal(int modelIndexRow, bool updateIfNeeded)
165 {
166  // First of all look it up in the current hash
167  ModelInvariantIndex *invariant = mCurrentInvariantHash->value(modelIndexRow, nullptr);
168  if (invariant) {
169  return invariant; // found: was up to date
170  }
171 
172  // Go backward in history by unapplying changes
173  if (!mRowShiftList) {
174  return nullptr; // not found (not requested yet or invalid index at all)
175  }
176 
177  int idx = mRowShiftList->count();
178  if (idx == 0) {
179  Q_ASSERT(false);
180  return nullptr; // should never happen (mRowShiftList should have been 0), but well...
181  }
182  idx--;
183 
184  int previousIndexRow = modelIndexRow;
185 
186  while (idx >= 0) {
187  RowShift *shift = mRowShiftList->at(idx);
188 
189  // this shift has taken "previousModelIndexRow" in the historic state
190  // and has executed:
191  //
192  // if ( previousIndexRow >= shift->mMinimumRowIndex )
193  // previousIndexRow += shift->mShift;
194  //
195  // so inverting it
196  //
197  // int potentialPreviousModelIndexRow = modelIndexRow - shift->mShift;
198  // if ( potentialPreviousModelIndexRow >= shift->mMinimumRowIndex )
199  // previousIndexRow = potentialPreviousModelIndexRow;
200  //
201  // or by simplifying...
202 
203  int potentialPreviousModelIndexRow = previousIndexRow - shift->mShift;
204  if (potentialPreviousModelIndexRow >= shift->mMinimumRowIndex) {
205  previousIndexRow = potentialPreviousModelIndexRow;
206  }
207 
208  invariant = shift->mInvariantHash->value(previousIndexRow, nullptr);
209  if (invariant) {
210  // found at this level in history
211  if (updateIfNeeded) { // update it too
212  updateModelInvariantIndex(modelIndexRow, invariant);
213  }
214  return invariant;
215  }
216 
217  idx--;
218  }
219 
220  qCWarning(MESSAGELIST_LOG) << "Requested invariant for storage row index "
221  << modelIndexRow << " not found in history";
222  return nullptr; // not found in history
223 }
224 
226 {
227  d->mLazyUpdateChunkInterval = chunkInterval;
228 }
229 
231 {
232  d->mLazyUpdateIdleInterval = idleInterval;
233 }
234 
236 {
237  // the invariant shift serial is the serial this mapper
238  // had at the time it emitted the invariant.
239  // mRowShiftList at that time had at most invariantShiftSerial items.
240  Q_ASSERT(invariant);
241 
242  if (invariant->d->rowMapper() != this) {
243  return -1;
244  }
245 
246  if (invariant->d->rowMapperSerial() == d->mCurrentShiftSerial) {
247  Q_ASSERT(d->mCurrentInvariantHash->value(invariant->d->modelIndexRow()) == invariant);
248  return invariant->d->modelIndexRow(); // this invariant was emitted very recently and isn't affected by any change
249  }
250 
251  // If RowShift elements weren't removed from the list then
252  // we should have mCurrentShiftSerial items in the list.
253  // But RowShifts ARE removed sequentially from the beginning of the list
254  // as the invariants are updated in the user's data.
255  // We are making sure that if a RowShift belonging to a certain
256  // serial is removed from the list then there are no more
257  // ModelInvariantIndexinstances with that (or a lower) serial around.
258  // Thus invariantShiftSerial is >= mRemovedShiftCount.
259 
260  // Example:
261  // Initial state, no shifts, current serial 0, removed shifts 0
262  // Emit ModelInvariantIndexfor model index row 6, with serial 0.
263  // User asks for model index row of invariant that has row index 10 and serial 0.
264  // The serial is equal to the current serial and we return the row index unchanged.
265  // A row arrives at position 4
266  // We add a RowShift with start index 5 and offset +1
267  // We increase current serial to 1
268  // User asks for model index row of invariant that has row index 6 with serial 0.
269  // We compute the first RowShift index as serial 0 - removed 0 = 0
270  // We apply the row shifts starting at that index.
271  // That is, since the requested row index is 6 >= 5
272  // We apply +1 shift and return row index 7 serial 1
273  // User asks for model index row of invariant that has row index 7 with serial 1
274  // The serial is equal to the current serial and we return the row index unchanged still with serial 1
275  // We update all the invariants in the user's data so that
276  // there are no more invariants with serial 0.
277  // We remove the RowShift and increase removed shift count to 1
278  // User asks for model index row of invariant that has row index 7
279  // The ModelInvariantIndex MUST have at least serial 1 because of the removal step above.
280  // The serial is equal to the current serial and we return the row index unchanged still with serial 1
281  // A row arrives at position 2
282  // We add a RowShift with start index 3 and offset +1
283  // We increase current serial to 2
284  // User asks for model index row of invariant that has row index 7 with serial 1.
285  // We compute the first RowShift index as serial 1 - removed 1 = 0
286  // We apply the row shifts starting at that index.
287  // That is, since the requested row index is 7 >= 3
288  // We apply +1 shift and return row index 8 serial 2
289  // User asks for model index row of invariant that has row index 8 and serial 2
290  // The serial is equal to the current serial and we return the row index unchanged still with serial 2
291  // Etc...
292 
293  // So if we can trust that the user doesn't mess up with serials
294  // and the requested serial is not equal to the current serial
295  // then we can be 100% sure that mRowShiftList is not null (it contains at least one item).
296  // The requested serial is surely >= than mRemovedShiftCount too.
297 
298  // To find the starting index of the RowShifts that apply to this
299  // serial we need to offset them by the removed rows.
300 
301  uint invariantShiftIndex = invariant->d->rowMapperSerial() - d->mRemovedShiftCount;
302 
303  Q_ASSERT(d->mRowShiftList);
304 
305  // For the reasoning above invariantShiftIndex is surely < than mRowShiftList.count()
306 
307  const uint count = static_cast< uint >(d->mRowShiftList->count());
308 
309  Q_ASSERT(invariantShiftIndex < count);
310 
311  int modelIndexRow = invariant->d->modelIndexRow();
312 
313  // apply shifts
314  for (uint idx = invariantShiftIndex; idx < count; idx++) {
315  RowShift *shift = d->mRowShiftList->at(idx);
316  if (modelIndexRow >= shift->mMinimumRowIndex) {
317  modelIndexRow += shift->mShift;
318  }
319  }
320 
321  // Update the invariant on-the-fly too...
322  d->updateModelInvariantIndex(modelIndexRow, invariant);
323 
324  return modelIndexRow;
325 }
326 
328 {
329  // The user is athemeg for the invariant of the item that is at the CURRENT modelIndexRow.
330  Q_ASSERT(invariantToFill->d->rowMapper() == nullptr);
331 
332  // Plain new invariant. Fill it and add to the current hash.
333  invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, d->mCurrentShiftSerial);
334  invariantToFill->d->setRowMapper(this);
335 
336  Q_ASSERT(!d->mCurrentInvariantHash->contains(modelIndexRow));
337 
338  d->mCurrentInvariantHash->insert(modelIndexRow, invariantToFill);
339 }
340 
342 {
343  return d->modelIndexRowToModelInvariantIndexInternal(modelIndexRow, false);
344 }
345 
347 {
348  if (!d->mRowShiftList) {
349  if (d->mCurrentInvariantHash->isEmpty()) {
350  return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
351  }
352  }
353 
354  // Find the invariants in range.
355  // It's somewhat impossible to split this in chunks.
356 
357  auto invariantList = new QList< ModelInvariantIndex * >();
358 
359  const int end = startIndexRow + count;
360  for (int idx = startIndexRow; idx < end; idx++) {
361  ModelInvariantIndex *invariant = d->modelIndexRowToModelInvariantIndexInternal(idx, true);
362  if (invariant) {
363  invariantList->append(invariant);
364  }
365  }
366 
367  if (invariantList->isEmpty()) {
368  delete invariantList;
369  return nullptr;
370  }
371 
372  return invariantList;
373 }
374 
375 void ModelInvariantRowMapper::modelRowsInserted(int modelIndexRowPosition, int count)
376 {
377  // Some rows were added to the model at modelIndexRowPosition.
378 
379  // FIXME: If rows are added at the end then we don't need any mapping.
380  // The fact is that we don't know which is the model's end...
381  // But maybe we can consider the end being the greatest row
382  // index emitted until now...
383 
384  if (!d->mRowShiftList) {
385  if (d->mCurrentInvariantHash->isEmpty()) {
386  return; // no invariants emitted, even if rows are changed, no invariant is affected.
387  }
388  // some invariants might be affected
389  d->mRowShiftList = new QList< RowShift * >();
390  }
391 
392  RowShift *shift;
393 
394  if (d->mCurrentInvariantHash->isEmpty()) {
395  // No invariants updated (all existing are outdated)
396 
397  Q_ASSERT(d->mRowShiftList->count() > 0); // must be true since it's not null
398 
399  // Check if we can attach to the last existing shift (very common for consecutive row additions)
400  shift = d->mRowShiftList->at(d->mRowShiftList->count() - 1);
401  Q_ASSERT(shift);
402 
403  if (shift->mShift > 0) { // the shift was positive (addition)
404  if ((shift->mMinimumRowIndex + shift->mShift) == modelIndexRowPosition) {
405  // Inserting contiguous blocks of rows, just extend this shift
406  shift->mShift += count;
407  Q_ASSERT(d->mUpdateTimer->isActive());
408  return;
409  }
410  }
411  }
412 
413  // FIXME: If we have few items, we can just shift the indexes now.
414 
415  shift = new RowShift(modelIndexRowPosition, count, d->mCurrentInvariantHash);
416  d->mRowShiftList->append(shift);
417 
418  d->mCurrentShiftSerial++;
419  d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >();
420 
421  if (d->mRowShiftList->count() > 7) { // 7 is heuristic
422  // We start loosing performance as the stack is growing too much.
423  // Start updating NOW and hope we can get it in few sweeps.
424 
425  if (d->mUpdateTimer->isActive()) {
426  d->mUpdateTimer->stop();
427  }
428 
429  d->slotPerformLazyUpdate();
430  } else {
431  // Make sure we'll get a lazy update somewhere in the future
432  if (!d->mUpdateTimer->isActive()) {
433  d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
434  }
435  }
436 }
437 
439 {
440  // Some rows were added from the model at modelIndexRowPosition.
441 
442  // FIXME: If rows are removed from the end, we don't need any mapping.
443  // The fact is that we don't know which is the model's end...
444  // But maybe we can consider the end being the greatest row
445  // index emitted until now...
446 
447  if (!d->mRowShiftList) {
448  if (d->mCurrentInvariantHash->isEmpty()) {
449  return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
450  }
451  // some invariants might be affected
452  }
453 
454  // FIXME: If we have few items, we can just shift the indexes now.
455 
456  // FIXME: Find a way to "merge" the shifts, if possible
457  // It OFTEN happens that we remove a lot of items at once (as opposed
458  // to item addition which is usually an incremental operation).
459 
460  // FIXME: HUGE PROBLEM
461  // When the items arent contiguous or are just out of order it's
462  // impossible to merge the shifts. Deleting many messages
463  // generates then a very deep delta stack. Since to delete the
464  // next message you need to traverse the whole stack, this method
465  // becomes very slow (maybe not as slow as updating all the indexes
466  // in the general case, but still *slow*).
467  //
468  // So one needs to perform updates while rows are being removed
469  // but that tends to void all your efforts to not update the
470  // whole list of items every time...
471  //
472  // Also deletions don't seem to be asynchronous (or at least
473  // they eat all the CPU power available for KMail) so the timers
474  // don't fire and we're not actually processing the model jobs...
475  //
476  // It turns out that deleting many items is just slower than
477  // reloading the view...
478 
479  // Invalidate the invariants affected by the change
480  // In most cases it's a relatively small sweep (and it's done once).
481  // It's somewhat impossible to split this in chunks.
482 
483  auto deadInvariants = new QList< ModelInvariantIndex * >();
484 
485  const int end = modelIndexRowPosition + count;
486  for (int idx = modelIndexRowPosition; idx < end; idx++) {
487  // FIXME: One could optimize this by joining the retrieval and destruction functions
488  // that is by making a special indexDead( int modelIndex )..
489  ModelInvariantIndex *dyingInvariant = d->modelIndexRowToModelInvariantIndexInternal(idx, false);
490  if (dyingInvariant) {
491  d->indexDead(dyingInvariant); // will remove from this mapper hashes
492  dyingInvariant->d->setRowMapper(nullptr); // invalidate!
493  deadInvariants->append(dyingInvariant);
494  } else {
495  // got no dying invariant
496  qCWarning(MESSAGELIST_LOG) << "Could not find invariant to invalidate at current row " << idx;
497  }
498  }
499 
500  if (!d->mRowShiftList) {
501  // have no pending shifts, look if we are keeping other invariants
502  if (d->mCurrentInvariantHash->isEmpty()) {
503  // no more invariants in this mapper, even if rows are changed, no invariant is affected.
504  if (deadInvariants->isEmpty()) {
505  // should never happen, but well...
506  delete deadInvariants;
507  return nullptr;
508  }
509  return deadInvariants;
510  }
511  // still have some invariants inside, must add a shift for them
512  d->mRowShiftList = new QList< RowShift * >();
513  } // else already have shifts
514 
515  // add a shift for this row removal
516  RowShift *shift = new RowShift(modelIndexRowPosition + count, -count, d->mCurrentInvariantHash);
517  d->mRowShiftList->append(shift);
518 
519  d->mCurrentShiftSerial++;
520  d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >();
521 
522  // trigger updates
523  if (d->mRowShiftList->count() > 7) { // 7 is heuristic
524  // We start loosing performance as the stack is growing too much.
525  // Start updating NOW and hope we can get it in few sweeps.
526 
527  if (d->mUpdateTimer->isActive()) {
528  d->mUpdateTimer->stop();
529  }
530 
531  d->slotPerformLazyUpdate();
532  } else {
533  // Make sure we'll get a lazy update somewhere in the future
534  if (!d->mUpdateTimer->isActive()) {
535  d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
536  }
537  }
538 
539  if (deadInvariants->isEmpty()) {
540  // should never happen, but well...
541  delete deadInvariants;
542  return nullptr;
543  }
544 
545  return deadInvariants;
546 }
547 
549 {
550  // FIXME: optimize this (it probably can be optimized by providing a more complex user interface)
551  QHash< int, ModelInvariantIndex * >::ConstIterator end(d->mCurrentInvariantHash->constEnd());
552 
553  for (const auto idx : qAsConst(*d->mCurrentInvariantHash)) {
554  idx->d->setRowMapper(nullptr);
555  }
556  d->mCurrentInvariantHash->clear();
557 
558  if (d->mRowShiftList) {
559  while (!d->mRowShiftList->isEmpty()) {
560  delete d->mRowShiftList->takeFirst();
561  }
562 
563  delete d->mRowShiftList;
564  d->mRowShiftList = nullptr;
565  }
566 
567  d->mCurrentShiftSerial = 0;
568  d->mRemovedShiftCount = 0;
569 }
570 
571 void ModelInvariantRowMapperPrivate::slotPerformLazyUpdate()
572 {
573  // The drawback here is that when one row is removed from the middle (say position 500 of 1000)
574  // then we require ALL the items to be updated...but:
575  //
576  // - We can do it very lazily in the background
577  // - Optimizing this would mean to ALSO keep the indexes in lists or in a large array
578  // - The list approach would require to keep the indexes sorted
579  // so it would cost at least N log (N) / 2.. which is worse than N.
580  // - We could keep a single (or multiple) array as large as the model
581  // but then we'd have a large memory consumption and large overhead
582  // when inserting / removing items from the middle.
583  //
584  // So finally I think that the multiple hash approach is a "minimum loss" approach.
585 
586  QTime startTime = QTime::currentTime();
587 
588  int curIndex = 0;
589 
590  while (mRowShiftList) {
591  // Have at least one row shift
592  uint count = static_cast< uint >(mRowShiftList->count());
593 
594  // Grab it
595  RowShift *shift = mRowShiftList->at(0);
596 
597  // and update the invariants that belong to it
598  auto it = shift->mInvariantHash->begin();
599  auto end = shift->mInvariantHash->end();
600 
601  while (it != end) {
602  ModelInvariantIndex *invariant = *it;
603 
604  it = shift->mInvariantHash->erase(it);
605 
606  // apply shifts
607  int modelIndexRow = invariant->d->modelIndexRow();
608 
609  for (uint idx = 0; idx < count; ++idx) {
610  RowShift *thatShift = mRowShiftList->at(idx);
611  if (modelIndexRow >= thatShift->mMinimumRowIndex) {
612  modelIndexRow += thatShift->mShift;
613  }
614  }
615 
616  // update and make it belong to the current serial
617  invariant->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
618 
619  mCurrentInvariantHash->insert(modelIndexRow, invariant);
620 
621  // once in a while check if we ran out of time
622  if ((curIndex % 15) == 0) { // 15 is heuristic
623  int elapsed = startTime.msecsTo(QTime::currentTime());
624  if ((elapsed > mLazyUpdateChunkInterval) || (elapsed < 0)) {
625  // interrupt
626  //qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
627  mUpdateTimer->start(mLazyUpdateIdleInterval);
628  return;
629  }
630  }
631 
632  curIndex++;
633  }
634 
635  // no more invariants with serial <= invariantToFill->rowMapperSerial()
636  killFirstRowShift();
637  }
638 
639  //qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
640 
641  // if we're here then no more work needs to be done.
642 }
643 
644 #include "moc_modelinvariantrowmapper.cpp"
void modelReset()
Call this function from your handlers of reset() and layoutChanged() AFTER you ve last accessed the m...
ModelInvariantIndex * modelIndexRowToModelInvariantIndex(int modelIndexRow)
Finds the existing ModelInvariantIndex that belongs to the specified CURRENT modelIndexRow.
int msecsTo(const QTime &t) const const
The implementation independent part of the MessageList library.
Definition: aggregation.h:21
void setLazyUpdateChunkInterval(int chunkInterval)
Sets the maximum time we can spend inside a single lazy update step.
void timeout()
void modelRowsInserted(int modelIndexRowPosition, int count)
Call this function when rows are inserted to the underlying model BEFORE scanning the model for the n...
void createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
Binds a ModelInvariantIndex structure to the specified CURRENT modelIndexRow.
QTime currentTime()
QList< ModelInvariantIndex * > * modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count)
This basically applies modelIndexRowToModelInvariantIndex() to a range of elements.
void setLazyUpdateIdleInterval(int idleInterval)
Sets the idle time between two lazy updates in milliseconds.
int modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant)
Maps a ModelInvariantIndex to the CURRENT associated row index in the model.
An invariant index that can be ALWAYS used to reference an item inside a QAbstractItemModel.
QList< ModelInvariantIndex * > * modelRowsRemoved(int modelIndexRowPosition, int count)
Call this function when rows are removed from the underlying model AFTER accessing the removed rows f...
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Sep 20 2020 23:12:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.