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

KDE's Doxygen guidelines are available online.