File indexing completed on 2024-12-15 04:54:40

0001 /******************************************************************************
0002  *
0003  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  *
0007  *******************************************************************************/
0008 
0009 #include "core/modelinvariantrowmapper.h"
0010 #include "core/modelinvariantindex_p.h"
0011 #include "core/modelinvariantrowmapper_p.h"
0012 
0013 #include <QTime>
0014 #include <QTimer>
0015 
0016 #include "messagelist_debug.h"
0017 
0018 namespace MessageList
0019 {
0020 namespace Core
0021 {
0022 class RowShift
0023 {
0024 public:
0025     int mMinimumRowIndex;
0026     int mShift;
0027     QHash<int, ModelInvariantIndex *> *mInvariantHash;
0028 
0029 public:
0030     RowShift(int minRowIndex, int shift, QHash<int, ModelInvariantIndex *> *invariantHash)
0031         : mMinimumRowIndex(minRowIndex)
0032         , mShift(shift)
0033         , mInvariantHash(invariantHash)
0034     {
0035     }
0036 
0037     ~RowShift()
0038     {
0039         for (const auto idx : std::as_const(*mInvariantHash)) {
0040             idx->d->setRowMapper(nullptr);
0041         }
0042         delete mInvariantHash;
0043     }
0044 };
0045 } // namespace Core
0046 } // namespace MessageList
0047 
0048 using namespace MessageList::Core;
0049 
0050 ModelInvariantRowMapper::ModelInvariantRowMapper()
0051     : d(new ModelInvariantRowMapperPrivate(this))
0052 {
0053     d->mRowShiftList = new QList<RowShift *>();
0054     d->mCurrentShiftSerial = 0;
0055     d->mCurrentInvariantHash = new QHash<int, ModelInvariantIndex *>();
0056     d->mUpdateTimer = new QTimer(this);
0057     d->mUpdateTimer->setSingleShot(true);
0058     d->mLazyUpdateChunkInterval = 50;
0059     d->mLazyUpdateIdleInterval = 50;
0060 
0061     connect(d->mUpdateTimer, &QTimer::timeout, this, [this]() {
0062         d->slotPerformLazyUpdate();
0063     });
0064 }
0065 
0066 ModelInvariantRowMapper::~ModelInvariantRowMapper()
0067 {
0068     if (d->mUpdateTimer->isActive()) {
0069         d->mUpdateTimer->stop();
0070     }
0071 
0072     // FIXME: optimize this (it CAN be optimized)
0073     for (const auto idx : std::as_const(*d->mCurrentInvariantHash)) {
0074         idx->d->setRowMapper(nullptr);
0075     }
0076     delete d->mCurrentInvariantHash;
0077 
0078     if (d->mRowShiftList) {
0079         while (!d->mRowShiftList->isEmpty()) {
0080             delete d->mRowShiftList->takeFirst();
0081         }
0082 
0083         delete d->mRowShiftList;
0084     }
0085 }
0086 
0087 void ModelInvariantRowMapperPrivate::killFirstRowShift()
0088 {
0089     RowShift *shift = mRowShiftList->at(0);
0090 
0091     Q_ASSERT(shift->mInvariantHash->isEmpty());
0092 
0093     delete shift;
0094     mRowShiftList->removeAt(0);
0095     mRemovedShiftCount++;
0096     if (mRowShiftList->isEmpty()) {
0097         delete mRowShiftList;
0098         mRowShiftList = nullptr;
0099     }
0100 }
0101 
0102 void ModelInvariantRowMapperPrivate::indexDead(ModelInvariantIndex *invariant)
0103 {
0104     Q_ASSERT(invariant->d->rowMapper() == q);
0105 
0106     if (invariant->d->rowMapperSerial() == mCurrentShiftSerial) {
0107         mCurrentInvariantHash->remove(invariant->d->modelIndexRow());
0108         return;
0109     }
0110 
0111     Q_ASSERT(invariant->d->rowMapperSerial() < mCurrentShiftSerial);
0112 
0113     if (!mRowShiftList) {
0114         return; // not found (not requested yet or invalid index at all)
0115     }
0116 
0117     uint invariantShiftIndex = invariant->d->rowMapperSerial() - mRemovedShiftCount;
0118 
0119     Q_ASSERT(invariantShiftIndex < static_cast<uint>(mRowShiftList->count()));
0120 
0121     RowShift *shift = mRowShiftList->at(invariantShiftIndex);
0122 
0123     Q_ASSERT(shift);
0124 
0125     shift->mInvariantHash->remove(invariant->d->modelIndexRow());
0126 
0127     if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
0128         // no more invariants with serial <= invariant->d->rowMapperSerial()
0129         killFirstRowShift();
0130     }
0131 }
0132 
0133 void ModelInvariantRowMapperPrivate::updateModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
0134 {
0135     // Here the invariant already belongs to this mapper. We ASSUME that it's somewhere
0136     // in the history and not in the hash belonging to the current serial.
0137     // modelIndexRow is the CURRENT model index row.
0138     Q_ASSERT(invariantToFill->d->rowMapper() == q);
0139 
0140     uint invariantShiftIndex = invariantToFill->d->rowMapperSerial() - mRemovedShiftCount;
0141 
0142     Q_ASSERT(invariantShiftIndex < static_cast<uint>(mRowShiftList->count()));
0143 
0144     RowShift *shift = mRowShiftList->at(invariantShiftIndex);
0145 
0146     int count = shift->mInvariantHash->remove(invariantToFill->d->modelIndexRow());
0147 
0148     Q_ASSERT(count > 0);
0149     Q_UNUSED(count)
0150 
0151     // update and make it belong to the current serial
0152     invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
0153 
0154     Q_ASSERT(!mCurrentInvariantHash->contains(invariantToFill->d->modelIndexRow()));
0155 
0156     mCurrentInvariantHash->insert(invariantToFill->d->modelIndexRow(), invariantToFill);
0157 
0158     if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
0159         // no more invariants with serial <= invariantToFill->rowMapperSerial()
0160         killFirstRowShift();
0161     }
0162 }
0163 
0164 ModelInvariantIndex *ModelInvariantRowMapperPrivate::modelIndexRowToModelInvariantIndexInternal(int modelIndexRow, bool updateIfNeeded)
0165 {
0166     // First of all look it up in the current hash
0167     ModelInvariantIndex *invariant = mCurrentInvariantHash->value(modelIndexRow, nullptr);
0168     if (invariant) {
0169         return invariant; // found: was up to date
0170     }
0171 
0172     // Go backward in history by unapplying changes
0173     if (!mRowShiftList) {
0174         return nullptr; // not found (not requested yet or invalid index at all)
0175     }
0176 
0177     int idx = mRowShiftList->count();
0178     if (idx == 0) {
0179         Q_ASSERT(false);
0180         return nullptr; // should never happen (mRowShiftList should have been 0), but well...
0181     }
0182     idx--;
0183 
0184     int previousIndexRow = modelIndexRow;
0185 
0186     while (idx >= 0) {
0187         RowShift *shift = mRowShiftList->at(idx);
0188 
0189         // this shift has taken "previousModelIndexRow" in the historic state
0190         // and has executed:
0191         //
0192         //   if ( previousIndexRow >= shift->mMinimumRowIndex )
0193         //     previousIndexRow += shift->mShift;
0194         //
0195         // so inverting it
0196         //
0197         //   int potentialPreviousModelIndexRow = modelIndexRow - shift->mShift;
0198         //   if ( potentialPreviousModelIndexRow >= shift->mMinimumRowIndex )
0199         //     previousIndexRow = potentialPreviousModelIndexRow;
0200         //
0201         // or by simplifying...
0202 
0203         int potentialPreviousModelIndexRow = previousIndexRow - shift->mShift;
0204         if (potentialPreviousModelIndexRow >= shift->mMinimumRowIndex) {
0205             previousIndexRow = potentialPreviousModelIndexRow;
0206         }
0207 
0208         invariant = shift->mInvariantHash->value(previousIndexRow, nullptr);
0209         if (invariant) {
0210             // found at this level in history
0211             if (updateIfNeeded) { // update it too
0212                 updateModelInvariantIndex(modelIndexRow, invariant);
0213             }
0214             return invariant;
0215         }
0216 
0217         idx--;
0218     }
0219 
0220     qCWarning(MESSAGELIST_LOG) << "Requested invariant for storage row index " << modelIndexRow << " not found in history";
0221     return nullptr; // not found in history
0222 }
0223 
0224 void ModelInvariantRowMapper::setLazyUpdateChunkInterval(int chunkInterval)
0225 {
0226     d->mLazyUpdateChunkInterval = chunkInterval;
0227 }
0228 
0229 void ModelInvariantRowMapper::setLazyUpdateIdleInterval(int idleInterval)
0230 {
0231     d->mLazyUpdateIdleInterval = idleInterval;
0232 }
0233 
0234 int ModelInvariantRowMapper::modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant)
0235 {
0236     // the invariant shift serial is the serial this mapper
0237     // had at the time it emitted the invariant.
0238     // mRowShiftList at that time had at most invariantShiftSerial items.
0239     Q_ASSERT(invariant);
0240 
0241     if (invariant->d->rowMapper() != this) {
0242         return -1;
0243     }
0244 
0245     if (invariant->d->rowMapperSerial() == d->mCurrentShiftSerial) {
0246         Q_ASSERT(d->mCurrentInvariantHash->value(invariant->d->modelIndexRow()) == invariant);
0247         return invariant->d->modelIndexRow(); // this invariant was emitted very recently and isn't affected by any change
0248     }
0249 
0250     // If RowShift elements weren't removed from the list then
0251     // we should have mCurrentShiftSerial items in the list.
0252     // But RowShifts ARE removed sequentially from the beginning of the list
0253     // as the invariants are updated in the user's data.
0254     // We are making sure that if a RowShift belonging to a certain
0255     // serial is removed from the list then there are no more
0256     // ModelInvariantIndexinstances with that (or a lower) serial around.
0257     // Thus invariantShiftSerial is >= mRemovedShiftCount.
0258 
0259     // Example:
0260     //     Initial state, no shifts, current serial 0, removed shifts 0
0261     //     Emit ModelInvariantIndexfor model index row 6, with serial 0.
0262     //     User asks for model index row of invariant that has row index 10 and serial 0.
0263     //       The serial is equal to the current serial and we return the row index unchanged.
0264     //     A row arrives at position 4
0265     //       We add a RowShift with start index 5 and offset +1
0266     //       We increase current serial to 1
0267     //     User asks for model index row of invariant that has row index 6 with serial 0.
0268     //       We compute the first RowShift index as serial 0 - removed 0 = 0
0269     //       We apply the row shifts starting at that index.
0270     //         That is, since the requested row index is 6 >= 5
0271     //           We apply +1 shift and return row index 7 serial 1
0272     //     User asks for model index row of invariant that has row index 7 with serial 1
0273     //       The serial is equal to the current serial and we return the row index unchanged still with serial 1
0274     //     We update all the invariants in the user's data so that
0275     //     there are no more invariants with serial 0.
0276     //       We remove the RowShift and increase removed shift count to 1
0277     //     User asks for model index row of invariant that has row index 7
0278     //       The ModelInvariantIndex MUST have at least serial 1 because of the removal step above.
0279     //       The serial is equal to the current serial and we return the row index unchanged still with serial 1
0280     //     A row arrives at position 2
0281     //       We add a RowShift with start index 3 and offset +1
0282     //       We increase current serial to 2
0283     //     User asks for model index row of invariant that has row index 7 with serial 1.
0284     //       We compute the first RowShift index as serial 1 - removed 1 = 0
0285     //       We apply the row shifts starting at that index.
0286     //         That is, since the requested row index is 7 >= 3
0287     //           We apply +1 shift and return row index 8 serial 2
0288     //     User asks for model index row of invariant that has row index 8 and serial 2
0289     //       The serial is equal to the current serial and we return the row index unchanged still with serial 2
0290     //     Etc...
0291 
0292     // So if we can trust that the user doesn't mess up with serials
0293     // and the requested serial is not equal to the current serial
0294     // then we can be 100% sure that mRowShiftList is not null (it contains at least one item).
0295     // The requested serial is surely >= than mRemovedShiftCount too.
0296 
0297     // To find the starting index of the RowShifts that apply to this
0298     // serial we need to offset them by the removed rows.
0299 
0300     uint invariantShiftIndex = invariant->d->rowMapperSerial() - d->mRemovedShiftCount;
0301 
0302     Q_ASSERT(d->mRowShiftList);
0303 
0304     // For the reasoning above invariantShiftIndex is surely < than mRowShiftList.count()
0305 
0306     const uint count = static_cast<uint>(d->mRowShiftList->count());
0307 
0308     Q_ASSERT(invariantShiftIndex < count);
0309 
0310     int modelIndexRow = invariant->d->modelIndexRow();
0311 
0312     // apply shifts
0313     for (uint idx = invariantShiftIndex; idx < count; idx++) {
0314         RowShift *shift = d->mRowShiftList->at(idx);
0315         if (modelIndexRow >= shift->mMinimumRowIndex) {
0316             modelIndexRow += shift->mShift;
0317         }
0318     }
0319 
0320     // Update the invariant on-the-fly too...
0321     d->updateModelInvariantIndex(modelIndexRow, invariant);
0322 
0323     return modelIndexRow;
0324 }
0325 
0326 void ModelInvariantRowMapper::createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
0327 {
0328     // The user is athemeg for the invariant of the item that is at the CURRENT modelIndexRow.
0329     Q_ASSERT(invariantToFill->d->rowMapper() == nullptr);
0330 
0331     // Plain new invariant. Fill it and add to the current hash.
0332     invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, d->mCurrentShiftSerial);
0333     invariantToFill->d->setRowMapper(this);
0334 
0335     Q_ASSERT(!d->mCurrentInvariantHash->contains(modelIndexRow));
0336 
0337     d->mCurrentInvariantHash->insert(modelIndexRow, invariantToFill);
0338 }
0339 
0340 ModelInvariantIndex *ModelInvariantRowMapper::modelIndexRowToModelInvariantIndex(int modelIndexRow)
0341 {
0342     return d->modelIndexRowToModelInvariantIndexInternal(modelIndexRow, false);
0343 }
0344 
0345 QList<ModelInvariantIndex *> *ModelInvariantRowMapper::modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count)
0346 {
0347     if (!d->mRowShiftList) {
0348         if (d->mCurrentInvariantHash->isEmpty()) {
0349             return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
0350         }
0351     }
0352 
0353     // Find the invariants in range.
0354     // It's somewhat impossible to split this in chunks.
0355 
0356     auto invariantList = new QList<ModelInvariantIndex *>();
0357 
0358     const int end = startIndexRow + count;
0359     for (int idx = startIndexRow; idx < end; idx++) {
0360         ModelInvariantIndex *invariant = d->modelIndexRowToModelInvariantIndexInternal(idx, true);
0361         if (invariant) {
0362             invariantList->append(invariant);
0363         }
0364     }
0365 
0366     if (invariantList->isEmpty()) {
0367         delete invariantList;
0368         return nullptr;
0369     }
0370 
0371     return invariantList;
0372 }
0373 
0374 void ModelInvariantRowMapper::modelRowsInserted(int modelIndexRowPosition, int count)
0375 {
0376     // Some rows were added to the model at modelIndexRowPosition.
0377 
0378     // FIXME: If rows are added at the end then we don't need any mapping.
0379     //        The fact is that we don't know which is the model's end...
0380     //        But maybe we can consider the end being the greatest row
0381     //        index emitted until now...
0382 
0383     if (!d->mRowShiftList) {
0384         if (d->mCurrentInvariantHash->isEmpty()) {
0385             return; // no invariants emitted, even if rows are changed, no invariant is affected.
0386         }
0387         // some invariants might be affected
0388         d->mRowShiftList = new QList<RowShift *>();
0389     }
0390 
0391     RowShift *shift;
0392 
0393     if (d->mCurrentInvariantHash->isEmpty()) {
0394         // No invariants updated (all existing are outdated)
0395 
0396         Q_ASSERT(d->mRowShiftList->count() > 0); // must be true since it's not null
0397 
0398         // Check if we can attach to the last existing shift (very common for consecutive row additions)
0399         shift = d->mRowShiftList->at(d->mRowShiftList->count() - 1);
0400         Q_ASSERT(shift);
0401 
0402         if (shift->mShift > 0) { // the shift was positive (addition)
0403             if ((shift->mMinimumRowIndex + shift->mShift) == modelIndexRowPosition) {
0404                 // Inserting contiguous blocks of rows, just extend this shift
0405                 shift->mShift += count;
0406                 Q_ASSERT(d->mUpdateTimer->isActive());
0407                 return;
0408             }
0409         }
0410     }
0411 
0412     // FIXME: If we have few items, we can just shift the indexes now.
0413 
0414     shift = new RowShift(modelIndexRowPosition, count, d->mCurrentInvariantHash);
0415     d->mRowShiftList->append(shift);
0416 
0417     d->mCurrentShiftSerial++;
0418     d->mCurrentInvariantHash = new QHash<int, ModelInvariantIndex *>();
0419 
0420     if (d->mRowShiftList->count() > 7) { // 7 is heuristic
0421         // We start losing performance as the stack is growing too much.
0422         // Start updating NOW and hope we can get it in few sweeps.
0423 
0424         if (d->mUpdateTimer->isActive()) {
0425             d->mUpdateTimer->stop();
0426         }
0427 
0428         d->slotPerformLazyUpdate();
0429     } else {
0430         // Make sure we'll get a lazy update somewhere in the future
0431         if (!d->mUpdateTimer->isActive()) {
0432             d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
0433         }
0434     }
0435 }
0436 
0437 QList<ModelInvariantIndex *> *ModelInvariantRowMapper::modelRowsRemoved(int modelIndexRowPosition, int count)
0438 {
0439     // Some rows were added from the model at modelIndexRowPosition.
0440 
0441     // FIXME: If rows are removed from the end, we don't need any mapping.
0442     //        The fact is that we don't know which is the model's end...
0443     //        But maybe we can consider the end being the greatest row
0444     //        index emitted until now...
0445 
0446     if (!d->mRowShiftList) {
0447         if (d->mCurrentInvariantHash->isEmpty()) {
0448             return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
0449         }
0450         // some invariants might be affected
0451     }
0452 
0453     // FIXME: If we have few items, we can just shift the indexes now.
0454 
0455     // FIXME: Find a way to "merge" the shifts, if possible
0456     //        It OFTEN happens that we remove a lot of items at once (as opposed
0457     //        to item addition which is usually an incremental operation).
0458 
0459     // FIXME: HUGE PROBLEM
0460     //        When the items aren't contiguous or are just out of order it's
0461     //        impossible to merge the shifts. Deleting many messages
0462     //        generates then a very deep delta stack. Since to delete the
0463     //        next message you need to traverse the whole stack, this method
0464     //        becomes very slow (maybe not as slow as updating all the indexes
0465     //        in the general case, but still *slow*).
0466     //
0467     //        So one needs to perform updates while rows are being removed
0468     //        but that tends to void all your efforts to not update the
0469     //        whole list of items every time...
0470     //
0471     //        Also deletions don't seem to be asynchronous (or at least
0472     //        they eat all the CPU power available for KMail) so the timers
0473     //        don't fire and we're not actually processing the model jobs...
0474     //
0475     //        It turns out that deleting many items is just slower than
0476     //        reloading the view...
0477 
0478     // Invalidate the invariants affected by the change
0479     // In most cases it's a relatively small sweep (and it's done once).
0480     // It's somewhat impossible to split this in chunks.
0481 
0482     auto deadInvariants = new QList<ModelInvariantIndex *>();
0483 
0484     const int end = modelIndexRowPosition + count;
0485     for (int idx = modelIndexRowPosition; idx < end; idx++) {
0486         // FIXME: One could optimize this by joining the retrieval and destruction functions
0487         //        that is by making a special indexDead( int modelIndex )..
0488         ModelInvariantIndex *dyingInvariant = d->modelIndexRowToModelInvariantIndexInternal(idx, false);
0489         if (dyingInvariant) {
0490             d->indexDead(dyingInvariant); // will remove from this mapper hashes
0491             dyingInvariant->d->setRowMapper(nullptr); // invalidate!
0492             deadInvariants->append(dyingInvariant);
0493         } else {
0494             // got no dying invariant
0495             qCWarning(MESSAGELIST_LOG) << "Could not find invariant to invalidate at current row " << idx;
0496         }
0497     }
0498 
0499     if (!d->mRowShiftList) {
0500         // have no pending shifts, look if we are keeping other invariants
0501         if (d->mCurrentInvariantHash->isEmpty()) {
0502             // no more invariants in this mapper, even if rows are changed, no invariant is affected.
0503             if (deadInvariants->isEmpty()) {
0504                 // should never happen, but well...
0505                 delete deadInvariants;
0506                 return nullptr;
0507             }
0508             return deadInvariants;
0509         }
0510         // still have some invariants inside, must add a shift for them
0511         d->mRowShiftList = new QList<RowShift *>();
0512     } // else already have shifts
0513 
0514     // add a shift for this row removal
0515     auto shift = new RowShift(modelIndexRowPosition + count, -count, d->mCurrentInvariantHash);
0516     d->mRowShiftList->append(shift);
0517 
0518     d->mCurrentShiftSerial++;
0519     d->mCurrentInvariantHash = new QHash<int, ModelInvariantIndex *>();
0520 
0521     // trigger updates
0522     if (d->mRowShiftList->count() > 7) { // 7 is heuristic
0523         // We start losing performance as the stack is growing too much.
0524         // Start updating NOW and hope we can get it in few sweeps.
0525 
0526         if (d->mUpdateTimer->isActive()) {
0527             d->mUpdateTimer->stop();
0528         }
0529 
0530         d->slotPerformLazyUpdate();
0531     } else {
0532         // Make sure we'll get a lazy update somewhere in the future
0533         if (!d->mUpdateTimer->isActive()) {
0534             d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
0535         }
0536     }
0537 
0538     if (deadInvariants->isEmpty()) {
0539         // should never happen, but well...
0540         delete deadInvariants;
0541         return nullptr;
0542     }
0543 
0544     return deadInvariants;
0545 }
0546 
0547 void ModelInvariantRowMapper::modelReset()
0548 {
0549     // FIXME: optimize this (it probably can be optimized by providing a more complex user interface)
0550 
0551     for (const auto idx : std::as_const(*d->mCurrentInvariantHash)) {
0552         idx->d->setRowMapper(nullptr);
0553     }
0554     d->mCurrentInvariantHash->clear();
0555 
0556     if (d->mRowShiftList) {
0557         while (!d->mRowShiftList->isEmpty()) {
0558             delete d->mRowShiftList->takeFirst();
0559         }
0560 
0561         delete d->mRowShiftList;
0562         d->mRowShiftList = nullptr;
0563     }
0564 
0565     d->mCurrentShiftSerial = 0;
0566     d->mRemovedShiftCount = 0;
0567 }
0568 
0569 void ModelInvariantRowMapperPrivate::slotPerformLazyUpdate()
0570 {
0571     // The drawback here is that when one row is removed from the middle (say position 500 of 1000)
0572     // then we require ALL the items to be updated...but:
0573     //
0574     // - We can do it very lazily in the background
0575     // - Optimizing this would mean to ALSO keep the indexes in lists or in a large array
0576     //   - The list approach would require to keep the indexes sorted
0577     //     so it would cost at least N log (N) / 2.. which is worse than N.
0578     //   - We could keep a single (or multiple) array as large as the model
0579     //     but then we'd have a large memory consumption and large overhead
0580     //     when inserting / removing items from the middle.
0581     //
0582     // So finally I think that the multiple hash approach is a "minimum loss" approach.
0583 
0584     QTime startTime = QTime::currentTime();
0585 
0586     int curIndex = 0;
0587 
0588     while (mRowShiftList) {
0589         // Have at least one row shift
0590         uint count = static_cast<uint>(mRowShiftList->count());
0591 
0592         // Grab it
0593         RowShift *shift = mRowShiftList->at(0);
0594 
0595         // and update the invariants that belong to it
0596         auto it = shift->mInvariantHash->begin();
0597         auto end = shift->mInvariantHash->end();
0598 
0599         while (it != end) {
0600             ModelInvariantIndex *invariant = *it;
0601 
0602             it = shift->mInvariantHash->erase(it);
0603 
0604             // apply shifts
0605             int modelIndexRow = invariant->d->modelIndexRow();
0606 
0607             for (uint idx = 0; idx < count; ++idx) {
0608                 RowShift *thatShift = mRowShiftList->at(idx);
0609                 if (modelIndexRow >= thatShift->mMinimumRowIndex) {
0610                     modelIndexRow += thatShift->mShift;
0611                 }
0612             }
0613 
0614             // update and make it belong to the current serial
0615             invariant->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
0616 
0617             mCurrentInvariantHash->insert(modelIndexRow, invariant);
0618 
0619             // once in a while check if we ran out of time
0620             if ((curIndex % 15) == 0) { // 15 is heuristic
0621                 int elapsed = startTime.msecsTo(QTime::currentTime());
0622                 if ((elapsed > mLazyUpdateChunkInterval) || (elapsed < 0)) {
0623                     // interrupt
0624                     // qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
0625                     mUpdateTimer->start(mLazyUpdateIdleInterval);
0626                     return;
0627                 }
0628             }
0629 
0630             curIndex++;
0631         }
0632 
0633         // no more invariants with serial <= invariantToFill->rowMapperSerial()
0634         killFirstRowShift();
0635     }
0636 
0637     // qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
0638 
0639     // if we're here then no more work needs to be done.
0640 }
0641 
0642 #include "moc_modelinvariantrowmapper.cpp"