diff --git a/messagelist/src/core/modelinvariantrowmapper.cpp b/messagelist/src/core/modelinvariantrowmapper.cpp index ac712e2c..5a0872b3 100644 --- a/messagelist/src/core/modelinvariantrowmapper.cpp +++ b/messagelist/src/core/modelinvariantrowmapper.cpp @@ -1,655 +1,654 @@ /****************************************************************************** * * Copyright 2008 Szymon Tomasz Stefanek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * *******************************************************************************/ #include "core/modelinvariantrowmapper.h" #include "core/modelinvariantrowmapper_p.h" #include "core/modelinvariantindex_p.h" #include #include #include "messagelist_debug.h" namespace MessageList { namespace Core { class RowShift { public: int mMinimumRowIndex; int mShift; QHash< int, ModelInvariantIndex * > *mInvariantHash; public: RowShift(int minRowIndex, int shift, QHash< int, ModelInvariantIndex * > *invariantHash) : mMinimumRowIndex(minRowIndex) , mShift(shift) , mInvariantHash(invariantHash) { } ~RowShift() { for (const auto idx : qAsConst(*mInvariantHash)) { idx->d->setRowMapper(nullptr); } delete mInvariantHash; } }; } // namespace Core } // namespace MessageList using namespace MessageList::Core; ModelInvariantRowMapper::ModelInvariantRowMapper() : d(new ModelInvariantRowMapperPrivate(this)) { d->mRowShiftList = new QList< RowShift * >(); d->mCurrentShiftSerial = 0; d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >(); d->mUpdateTimer = new QTimer(this); d->mUpdateTimer->setSingleShot(true); d->mLazyUpdateChunkInterval = 50; d->mLazyUpdateIdleInterval = 50; - connect(d->mUpdateTimer, SIGNAL(timeout()), - SLOT(slotPerformLazyUpdate())); + connect(d->mUpdateTimer, &QTimer::timeout, this, [this]() { d->slotPerformLazyUpdate(); }); } ModelInvariantRowMapper::~ModelInvariantRowMapper() { if (d->mUpdateTimer->isActive()) { d->mUpdateTimer->stop(); } // FIXME: optimize this (it CAN be optimized) for (const auto idx : qAsConst(*d->mCurrentInvariantHash)) { idx->d->setRowMapper(nullptr); } delete d->mCurrentInvariantHash; if (d->mRowShiftList) { while (!d->mRowShiftList->isEmpty()) { delete d->mRowShiftList->takeFirst(); } delete d->mRowShiftList; } delete d; } void ModelInvariantRowMapperPrivate::killFirstRowShift() { RowShift *shift = mRowShiftList->at(0); Q_ASSERT(shift->mInvariantHash->isEmpty()); delete shift; mRowShiftList->removeAt(0); mRemovedShiftCount++; if (mRowShiftList->isEmpty()) { delete mRowShiftList; mRowShiftList = nullptr; } } void ModelInvariantRowMapperPrivate::indexDead(ModelInvariantIndex *invariant) { Q_ASSERT(invariant->d->rowMapper() == q); if (invariant->d->rowMapperSerial() == mCurrentShiftSerial) { mCurrentInvariantHash->remove(invariant->d->modelIndexRow()); return; } Q_ASSERT(invariant->d->rowMapperSerial() < mCurrentShiftSerial); if (!mRowShiftList) { return; // not found (not requested yet or invalid index at all) } uint invariantShiftIndex = invariant->d->rowMapperSerial() - mRemovedShiftCount; Q_ASSERT(invariantShiftIndex < static_cast< uint >(mRowShiftList->count())); RowShift *shift = mRowShiftList->at(invariantShiftIndex); Q_ASSERT(shift); shift->mInvariantHash->remove(invariant->d->modelIndexRow()); if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) { // no more invariants with serial <= invariant->d->rowMapperSerial() killFirstRowShift(); } } void ModelInvariantRowMapperPrivate::updateModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill) { // Here the invariant already belongs to this mapper. We ASSUME that it's somewhere // in the history and not in the hash belonging to the current serial. // modelIndexRow is the CURRENT model index row. Q_ASSERT(invariantToFill->d->rowMapper() == q); uint invariantShiftIndex = invariantToFill->d->rowMapperSerial() - mRemovedShiftCount; Q_ASSERT(invariantShiftIndex < static_cast< uint >(mRowShiftList->count())); RowShift *shift = mRowShiftList->at(invariantShiftIndex); int count = shift->mInvariantHash->remove(invariantToFill->d->modelIndexRow()); Q_ASSERT(count > 0); Q_UNUSED(count); // update and make it belong to the current serial invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial); Q_ASSERT(!mCurrentInvariantHash->contains(invariantToFill->d->modelIndexRow())); mCurrentInvariantHash->insert(invariantToFill->d->modelIndexRow(), invariantToFill); if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) { // no more invariants with serial <= invariantToFill->rowMapperSerial() killFirstRowShift(); } } ModelInvariantIndex *ModelInvariantRowMapperPrivate::modelIndexRowToModelInvariantIndexInternal(int modelIndexRow, bool updateIfNeeded) { // First of all look it up in the current hash ModelInvariantIndex *invariant = mCurrentInvariantHash->value(modelIndexRow, nullptr); if (invariant) { return invariant; // found: was up to date } // Go backward in history by unapplying changes if (!mRowShiftList) { return nullptr; // not found (not requested yet or invalid index at all) } int idx = mRowShiftList->count(); if (idx == 0) { Q_ASSERT(false); return nullptr; // should never happen (mRowShiftList should have been 0), but well... } idx--; int previousIndexRow = modelIndexRow; while (idx >= 0) { RowShift *shift = mRowShiftList->at(idx); // this shift has taken "previousModelIndexRow" in the historic state // and has executed: // // if ( previousIndexRow >= shift->mMinimumRowIndex ) // previousIndexRow += shift->mShift; // // so inverting it // // int potentialPreviousModelIndexRow = modelIndexRow - shift->mShift; // if ( potentialPreviousModelIndexRow >= shift->mMinimumRowIndex ) // previousIndexRow = potentialPreviousModelIndexRow; // // or by simplyfying... int potentialPreviousModelIndexRow = previousIndexRow - shift->mShift; if (potentialPreviousModelIndexRow >= shift->mMinimumRowIndex) { previousIndexRow = potentialPreviousModelIndexRow; } invariant = shift->mInvariantHash->value(previousIndexRow, nullptr); if (invariant) { // found at this level in history if (updateIfNeeded) { // update it too updateModelInvariantIndex(modelIndexRow, invariant); } return invariant; } idx--; } qCWarning(MESSAGELIST_LOG) << "Requested invariant for storage row index " << modelIndexRow << " not found in history"; return nullptr; // not found in history } void ModelInvariantRowMapper::setLazyUpdateChunkInterval(int chunkInterval) { d->mLazyUpdateChunkInterval = chunkInterval; } void ModelInvariantRowMapper::setLazyUpdateIdleInterval(int idleInterval) { d->mLazyUpdateIdleInterval = idleInterval; } int ModelInvariantRowMapper::modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant) { // the invariant shift serial is the serial this mapper // had at the time it emitted the invariant. // mRowShiftList at that time had at most invariantShiftSerial items. Q_ASSERT(invariant); if (invariant->d->rowMapper() != this) { return -1; } if (invariant->d->rowMapperSerial() == d->mCurrentShiftSerial) { Q_ASSERT(d->mCurrentInvariantHash->value(invariant->d->modelIndexRow()) == invariant); return invariant->d->modelIndexRow(); // this invariant was emitted very recently and isn't affected by any change } // If RowShift elements weren't removed from the list then // we should have mCurrentShiftSerial items in the list. // But RowShifts ARE removed sequentially from the beginning of the list // as the invariants are updated in the user's data. // We are making sure that if a RowShift belonging to a certain // serial is removed from the list then there are no more // ModelInvariantIndexinstances with that (or a lower) serial around. // Thus invariantShiftSerial is >= mRemovedShiftCount. // Example: // Initial state, no shifts, current serial 0, removed shifts 0 // Emit ModelInvariantIndexfor model index row 6, with serial 0. // User asks for model index row of invariant that has row index 10 and serial 0. // The serial is equal to the current serial and we return the row index unchanged. // A row arrives at position 4 // We add a RowShift with start index 5 and offset +1 // We increase current serial to 1 // User asks for model index row of invariant that has row index 6 with serial 0. // We compute the first RowShift index as serial 0 - removed 0 = 0 // We apply the row shifts starting at that index. // That is, since the requested row index is 6 >= 5 // We apply +1 shift and return row index 7 serial 1 // User asks for model index row of invariant that has row index 7 with serial 1 // The serial is equal to the current serial and we return the row index unchanged still with serial 1 // We update all the invariants in the user's data so that // there are no more invariants with serial 0. // We remove the RowShift and increase removed shift count to 1 // User asks for model index row of invariant that has row index 7 // The ModelInvariantIndex MUST have at least serial 1 because of the removal step above. // The serial is equal to the current serial and we return the row index unchanged still with serial 1 // A row arrives at position 2 // We add a RowShift with start index 3 and offset +1 // We increase current serial to 2 // User asks for model index row of invariant that has row index 7 with serial 1. // We compute the first RowShift index as serial 1 - removed 1 = 0 // We apply the row shifts starting at that index. // That is, since the requested row index is 7 >= 3 // We apply +1 shift and return row index 8 serial 2 // User asks for model index row of invariant that has row index 8 and serial 2 // The serial is equal to the current serial and we return the row index unchanged still with serial 2 // Etc... // So if we can trust that the user doesn't mess up with serials // and the requested serial is not equal to the current serial // then we can be 100% sure that mRowShiftList is not null (it contains at least one item). // The requested serial is surely >= than mRemovedShiftCount too. // To find the starting index of the RowShifts that apply to this // serial we need to offset them by the removed rows. uint invariantShiftIndex = invariant->d->rowMapperSerial() - d->mRemovedShiftCount; Q_ASSERT(d->mRowShiftList); // For the reasoning above invariantShiftIndex is surely < than mRowShiftList.count() const uint count = static_cast< uint >(d->mRowShiftList->count()); Q_ASSERT(invariantShiftIndex < count); int modelIndexRow = invariant->d->modelIndexRow(); // apply shifts for (uint idx = invariantShiftIndex; idx < count; idx++) { RowShift *shift = d->mRowShiftList->at(idx); if (modelIndexRow >= shift->mMinimumRowIndex) { modelIndexRow += shift->mShift; } } // Update the invariant on-the-fly too... d->updateModelInvariantIndex(modelIndexRow, invariant); return modelIndexRow; } void ModelInvariantRowMapper::createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill) { // The user is athemeg for the invariant of the item that is at the CURRENT modelIndexRow. Q_ASSERT(invariantToFill->d->rowMapper() == nullptr); // Plain new invariant. Fill it and add to the current hash. invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, d->mCurrentShiftSerial); invariantToFill->d->setRowMapper(this); Q_ASSERT(!d->mCurrentInvariantHash->contains(modelIndexRow)); d->mCurrentInvariantHash->insert(modelIndexRow, invariantToFill); } ModelInvariantIndex *ModelInvariantRowMapper::modelIndexRowToModelInvariantIndex(int modelIndexRow) { return d->modelIndexRowToModelInvariantIndexInternal(modelIndexRow, false); } QList< ModelInvariantIndex * > *ModelInvariantRowMapper::modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count) { if (!d->mRowShiftList) { if (d->mCurrentInvariantHash->isEmpty()) { return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected. } } // Find the invariants in range. // It's somewhat impossible to split this in chunks. auto invariantList = new QList< ModelInvariantIndex * >(); const int end = startIndexRow + count; for (int idx = startIndexRow; idx < end; idx++) { ModelInvariantIndex *invariant = d->modelIndexRowToModelInvariantIndexInternal(idx, true); if (invariant) { invariantList->append(invariant); } } if (invariantList->isEmpty()) { delete invariantList; return nullptr; } return invariantList; } void ModelInvariantRowMapper::modelRowsInserted(int modelIndexRowPosition, int count) { // Some rows were added to the model at modelIndexRowPosition. // FIXME: If rows are added at the end then we don't need any mapping. // The fact is that we don't know which is the model's end... // But maybe we can consider the end being the greatest row // index emitted until now... if (!d->mRowShiftList) { if (d->mCurrentInvariantHash->isEmpty()) { return; // no invariants emitted, even if rows are changed, no invariant is affected. } // some invariants might be affected d->mRowShiftList = new QList< RowShift * >(); } RowShift *shift; if (d->mCurrentInvariantHash->isEmpty()) { // No invariants updated (all existing are outdated) Q_ASSERT(d->mRowShiftList->count() > 0); // must be true since it's not null // Check if we can attach to the last existing shift (very common for consecutive row additions) shift = d->mRowShiftList->at(d->mRowShiftList->count() - 1); Q_ASSERT(shift); if (shift->mShift > 0) { // the shift was positive (addition) if ((shift->mMinimumRowIndex + shift->mShift) == modelIndexRowPosition) { // Inserting contiguous blocks of rows, just extend this shift shift->mShift += count; Q_ASSERT(d->mUpdateTimer->isActive()); return; } } } // FIXME: If we have few items, we can just shift the indexes now. shift = new RowShift(modelIndexRowPosition, count, d->mCurrentInvariantHash); d->mRowShiftList->append(shift); d->mCurrentShiftSerial++; d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >(); if (d->mRowShiftList->count() > 7) { // 7 is heuristic // We start loosing performance as the stack is growing too much. // Start updating NOW and hope we can get it in few sweeps. if (d->mUpdateTimer->isActive()) { d->mUpdateTimer->stop(); } d->slotPerformLazyUpdate(); } else { // Make sure we'll get a lazy update somewhere in the future if (!d->mUpdateTimer->isActive()) { d->mUpdateTimer->start(d->mLazyUpdateIdleInterval); } } } QList< ModelInvariantIndex * > *ModelInvariantRowMapper::modelRowsRemoved(int modelIndexRowPosition, int count) { // Some rows were added from the model at modelIndexRowPosition. // FIXME: If rows are removed from the end, we don't need any mapping. // The fact is that we don't know which is the model's end... // But maybe we can consider the end being the greatest row // index emitted until now... if (!d->mRowShiftList) { if (d->mCurrentInvariantHash->isEmpty()) { return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected. } // some invariants might be affected } // FIXME: If we have few items, we can just shift the indexes now. // FIXME: Find a way to "merge" the shifts, if possible // It OFTEN happens that we remove a lot of items at once (as opposed // to item addition which is usually an incremental operation). // FIXME: HUGE PROBLEM // When the items arent contiguous or are just out of order it's // impossible to merge the shifts. Deleting many messages // generates then a very deep delta stack. Since to delete the // next message you need to traverse the whole stack, this method // becomes very slow (maybe not as slow as updating all the indexes // in the general case, but still *slow*). // // So one needs to perform updates while rows are being removed // but that tends to void all your efforts to not update the // whole list of items every time... // // Also deletions don't seem to be asynchronous (or at least // they eat all the CPU power available for KMail) so the timers // don't fire and we're not actually processing the model jobs... // // It turns out that deleting many items is just slower than // reloading the view... // Invalidate the invariants affected by the change // In most cases it's a relatively small sweep (and it's done once). // It's somewhat impossible to split this in chunks. auto deadInvariants = new QList< ModelInvariantIndex * >(); const int end = modelIndexRowPosition + count; for (int idx = modelIndexRowPosition; idx < end; idx++) { // FIXME: One could optimize this by joining the retrieval and destruction functions // that is by making a special indexDead( int modelIndex ).. ModelInvariantIndex *dyingInvariant = d->modelIndexRowToModelInvariantIndexInternal(idx, false); if (dyingInvariant) { d->indexDead(dyingInvariant); // will remove from this mapper hashes dyingInvariant->d->setRowMapper(nullptr); // invalidate! deadInvariants->append(dyingInvariant); } else { // got no dying invariant qCWarning(MESSAGELIST_LOG) << "Could not find invariant to invalidate at current row " << idx; } } if (!d->mRowShiftList) { // have no pending shifts, look if we are keeping other invariants if (d->mCurrentInvariantHash->isEmpty()) { // no more invariants in this mapper, even if rows are changed, no invariant is affected. if (deadInvariants->isEmpty()) { // should never happen, but well... delete deadInvariants; return nullptr; } return deadInvariants; } // still have some invariants inside, must add a shift for them d->mRowShiftList = new QList< RowShift * >(); } // else already have shifts // add a shift for this row removal RowShift *shift = new RowShift(modelIndexRowPosition + count, -count, d->mCurrentInvariantHash); d->mRowShiftList->append(shift); d->mCurrentShiftSerial++; d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >(); // trigger updates if (d->mRowShiftList->count() > 7) { // 7 is heuristic // We start loosing performance as the stack is growing too much. // Start updating NOW and hope we can get it in few sweeps. if (d->mUpdateTimer->isActive()) { d->mUpdateTimer->stop(); } d->slotPerformLazyUpdate(); } else { // Make sure we'll get a lazy update somewhere in the future if (!d->mUpdateTimer->isActive()) { d->mUpdateTimer->start(d->mLazyUpdateIdleInterval); } } if (deadInvariants->isEmpty()) { // should never happen, but well... delete deadInvariants; return nullptr; } return deadInvariants; } void ModelInvariantRowMapper::modelReset() { // FIXME: optimize this (it probably can be optimized by providing a more complex user interface) QHash< int, ModelInvariantIndex * >::ConstIterator end(d->mCurrentInvariantHash->constEnd()); for (const auto idx : qAsConst(*d->mCurrentInvariantHash)) { idx->d->setRowMapper(nullptr); } d->mCurrentInvariantHash->clear(); if (d->mRowShiftList) { while (!d->mRowShiftList->isEmpty()) { delete d->mRowShiftList->takeFirst(); } delete d->mRowShiftList; d->mRowShiftList = nullptr; } d->mCurrentShiftSerial = 0; d->mRemovedShiftCount = 0; } void ModelInvariantRowMapperPrivate::slotPerformLazyUpdate() { // The drawback here is that when one row is removed from the middle (say position 500 of 1000) // then we require ALL the items to be updated...but: // // - We can do it very lazily in the background // - Optimizing this would mean to ALSO keep the indexes in lists or in a large array // - The list approach would require to keep the indexes sorted // so it would cost at least N log (N) / 2.. which is worse than N. // - We could keep a single (or multiple) array as large as the model // but then we'd have a large memory consumption and large overhead // when inserting / removing items from the middle. // // So finally I think that the multiple hash approach is a "minimum loss" approach. QTime startTime = QTime::currentTime(); int curIndex = 0; while (mRowShiftList) { // Have at least one row shift uint count = static_cast< uint >(mRowShiftList->count()); // Grab it RowShift *shift = mRowShiftList->at(0); // and update the invariants that belong to it auto it = shift->mInvariantHash->begin(); auto end = shift->mInvariantHash->end(); while (it != end) { ModelInvariantIndex *invariant = *it; it = shift->mInvariantHash->erase(it); // apply shifts int modelIndexRow = invariant->d->modelIndexRow(); for (uint idx = 0; idx < count; ++idx) { RowShift *thatShift = mRowShiftList->at(idx); if (modelIndexRow >= thatShift->mMinimumRowIndex) { modelIndexRow += thatShift->mShift; } } // update and make it belong to the current serial invariant->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial); mCurrentInvariantHash->insert(modelIndexRow, invariant); // once in a while check if we ran out of time if ((curIndex % 15) == 0) { // 15 is heuristic int elapsed = startTime.msecsTo(QTime::currentTime()); if ((elapsed > mLazyUpdateChunkInterval) || (elapsed < 0)) { // interrupt //qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants "; mUpdateTimer->start(mLazyUpdateIdleInterval); return; } } curIndex++; } // no more invariants with serial <= invariantToFill->rowMapperSerial() killFirstRowShift(); } //qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants "; // if we're here then no more work needs to be done. } #include "moc_modelinvariantrowmapper.cpp" diff --git a/messagelist/src/core/modelinvariantrowmapper.h b/messagelist/src/core/modelinvariantrowmapper.h index 9f219f67..fc2bdc5c 100644 --- a/messagelist/src/core/modelinvariantrowmapper.h +++ b/messagelist/src/core/modelinvariantrowmapper.h @@ -1,191 +1,189 @@ /****************************************************************************** * * Copyright 2008 Szymon Tomasz Stefanek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * *******************************************************************************/ #ifndef __MESSAGELIST_CORE_MODELINVARIANTROWMAPPER_H__ #define __MESSAGELIST_CORE_MODELINVARIANTROWMAPPER_H__ #include #include #include #include namespace MessageList { namespace Core { class ModelInvariantRowMapper; class ModelInvariantRowMapperPrivate; /** * This class is an optimizing helper for dealing with large flat QAbstractItemModel objects. * * The problem: * * When you're an user of a _flat_ QAbstractItemModel you access its contents * by the means of QModelIndex. The model index is basically a row index (for flat models). * You usually fetch some data for a row and then store the row index in order * to fetch more data later (think of a tree view that shows the "name" for an item * but when clicked needs to open a window with the details associated to the item). * * The problem is that your row indexes may become invalid when rows are added * or removed from the model. For instance, if a row is added at position 10, * then any cached index after position 10 must be updated in order to point to * the same content. With very large models this can become a problem since * the update must be somewhat "atomic" in order to preserve consistency. * Atomic, then, means "unbreakable": you can't chunk it in smaller pieces. * This also means that your application will simply freeze if you have a model * with 100000 cached indexes and a row is added/removed at the beginning. * Yet worse: your app will freeze EVERY time a row is added. This means * that if you don't really optimize, or just add non contiguous rows, you * must do the update multiple times... * * This class tries to solve this problem with a little overhead. * It basically gives you a ModelInvariantIndex for each "new" row you query. * Later the model may change by addition/removal of rows but with a ModelInvariantIndex * you will still be able to retrieve the content that the index was pointing * to at the time it was created. * * You don't need to implement any row update in your rowsInserted() / rowsRemoved() * handlers. Just call the modelRowsInserted() modelRowsRemoved() functions: * they will do everything for you in a substantially constant time. * * As the model structure changes the lookups will get a bit slower * since the row mapper must apply the changes sequentially to each invariant. * To avoid this, and to avoid storing the whole history of changes * the ModelInvariantRowMapper will perform a background update of your * ModelInvariantIndex objects. You don't need to care about this: it will happen automagically. * * The ModelInvariantIndex allocation and destruction is in fact left to you. * This is a GOOD approach because you're very likely to have some sort * of structures associated to the items you display. You may then simply * derive your objects from ModelInvariantIndex. This will save an additional * memory allocation for each one of your items (we are optimizing, aren't we ?) * and you will be able to dynamic_cast<> the ModelInvariantIndex pointers * directly to your structure pointers (which will likely save a large QHash<>...). * Even in the unlikely case in that you don't have a data structure for your items, * it's still an operator new() call that YOU make instead of this implementation. * It doesn't impact performance at all. You just have to remember to delete * your ModelInvariantIndex objects when you no longer need them. */ class ModelInvariantRowMapper : public QObject { friend class ModelInvariantIndex; Q_OBJECT public: explicit ModelInvariantRowMapper(); virtual ~ModelInvariantRowMapper(); /** * Sets the maximum time we can spend inside a single lazy update step. * The larger this time, the more resources we consume and leave less to UI processing * but also larger the update throughput (that is, we update more items per second). * This is 50 msec by default. */ void setLazyUpdateChunkInterval(int chunkInterval); /** * Sets the idle time between two lazy updates in milliseconds. * The larger this time, the less resources we consume and leave more to UI processing * but also smaller the update throughput (that is, we update less items per second). * This is 50 msec by default. */ void setLazyUpdateIdleInterval(int idleInterval); /** * Maps a ModelInvariantIndex to the CURRENT associated row index in the model. * As long as the underlying model is consistent, the returned row index is guaranteed to * point to the content that this ModelInvariantIndex pointed at the time it was created. * * Returns the current associated row index in the model or -1 if the invariant * does not belong to this mapper (model) or is marked as invalid at all. */ int modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant); /** * Binds a ModelInvariantIndex structure to the specified CURRENT modelIndexRow. * Later you can use the ModelInvariantIndex to retrieve the model contents * that the parameter modelIndexRow refers to... even if the model changes. * Call this function only if you're sure that there is no such invariant yet, * otherwise take a look at modelIndexRowToModelInvariantIndex(). * * This function ASSUMES that invariantToFill is a newly allocated ModelInvariantIndex. */ void createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill); /** * Finds the existing ModelInvariantIndex that belongs to the specified CURRENT modelIndexRow. * Returns the ModelInvariantIndex found or 0 if such an invariant wasn't yet * created (by the means of createModelInvariantIndex()). */ ModelInvariantIndex *modelIndexRowToModelInvariantIndex(int modelIndexRow); /** * This basically applies modelIndexRowToModelInvariantIndex() to a range of elements. * The returned pointer can be null if no existing ModelInvariantIndex object were * present in the range (this can happen if you don't request some of them). If the returned * value is not 0 then you're responsable of deleting it. */ QList< ModelInvariantIndex * > *modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count); /** * Call this function when rows are inserted to the underlying model * BEFORE scanning the model for the new items. You probably * want this function to be the first call in your rowsInserted() handler * or the last call in the rowsAboutToBeInserted() handler. */ void modelRowsInserted(int modelIndexRowPosition, int count); /** * Call this function when rows are removed from the underlying model * AFTER accessing the removed rows for the last time. You probably * want this function to be the first call of your rowsRemoved() handler * or the last call in the rowsAboutToBeRemoved() handler. * * This function will invalidate any ModelInvariantIndex instances * that are affected by the change. It will also do you a favor by returning * the list of the invalidated ModelInvariantIndex objects since * you'll probably want to delete them. The returned pointer can be null * if no existing ModelInvariantIndex object were affected by the change * (this can happen if you don't request some of them). If the returned * value is not 0 then you're responsable of deleting it. */ QList< ModelInvariantIndex * > *modelRowsRemoved(int modelIndexRowPosition, int count); /** * Call this function from your handlers of reset() and layoutChanged() * AFTER you ve last accessed the model underlying data. * You probably want this function to be the first call of your * reset() or layoutChanged() handlers. * * This function assumes that all the ModelInvariantIndex * are being invalidated and need to be requeried. */ void modelReset(); private: - Q_PRIVATE_SLOT(d, void slotPerformLazyUpdate()) - ModelInvariantRowMapperPrivate *const d; }; } // namespace Core } // namespace MessageList #endif //!__MESSAGELIST_CORE_MODELINVARIANTROWMAPPER_H__