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 #pragma once
0010 
0011 #include <QHash>
0012 #include <QList>
0013 #include <QObject>
0014 
0015 #include "core/modelinvariantindex.h"
0016 #include <memory>
0017 namespace MessageList
0018 {
0019 namespace Core
0020 {
0021 class ModelInvariantRowMapper;
0022 class ModelInvariantRowMapperPrivate;
0023 
0024 /**
0025  * This class is an optimizing helper for dealing with large flat QAbstractItemModel objects.
0026  *
0027  * The problem:
0028  *
0029  *   When you're an user of a _flat_ QAbstractItemModel you access its contents
0030  *   by the means of QModelIndex. The model index is basically a row index (for flat models).
0031  *   You usually fetch some data for a row and then store the row index in order
0032  *   to fetch more data later (think of a tree view that shows the "name" for an item
0033  *   but when clicked needs to open a window with the details associated to the item).
0034  *
0035  *   The problem is that your row indexes may become invalid when rows are added
0036  *   or removed from the model. For instance, if a row is added at position 10,
0037  *   then any cached index after position 10 must be updated in order to point to
0038  *   the same content. With very large models this can become a problem since
0039  *   the update must be somewhat "atomic" in order to preserve consistency.
0040  *   Atomic, then, means "unbreakable": you can't chunk it in smaller pieces.
0041  *   This also means that your application will simply freeze if you have a model
0042  *   with 100000 cached indexes and a row is added/removed at the beginning.
0043  *   Yet worse: your app will freeze EVERY time a row is added. This means
0044  *   that if you don't really optimize, or just add non contiguous rows, you
0045  *   must do the update multiple times...
0046  *
0047  * This class tries to solve this problem with a little overhead.
0048  * It basically gives you a ModelInvariantIndex for each "new" row you query.
0049  * Later the model may change by addition/removal of rows but with a ModelInvariantIndex
0050  * you will still be able to retrieve the content that the index was pointing
0051  * to at the time it was created.
0052  *
0053  * You don't need to implement any row update in your rowsInserted() / rowsRemoved()
0054  * handlers. Just call the modelRowsInserted() modelRowsRemoved() functions:
0055  * they will do everything for you in a substantially constant time.
0056  *
0057  * As the model structure changes the lookups will get a bit slower
0058  * since the row mapper must apply the changes sequentially to each invariant.
0059  * To avoid this, and to avoid storing the whole history of changes
0060  * the ModelInvariantRowMapper will perform a background update of your
0061  * ModelInvariantIndex objects. You don't need to care about this: it will happen automagically.
0062  *
0063  * The ModelInvariantIndex allocation and destruction is in fact left to you.
0064  * This is a GOOD approach because you're very likely to have some sort
0065  * of structures associated to the items you display. You may then simply
0066  * derive your objects from ModelInvariantIndex. This will save an additional
0067  * memory allocation for each one of your items (we are optimizing, aren't we ?)
0068  * and you will be able to dynamic_cast<> the ModelInvariantIndex pointers
0069  * directly to your structure pointers (which will likely save a large QHash<>...).
0070  * Even in the unlikely case in that you don't have a data structure for your items,
0071  * it's still an operator new() call that YOU make instead of this implementation.
0072  * It doesn't impact performance at all. You just have to remember to delete
0073  * your ModelInvariantIndex objects when you no longer need them.
0074  */
0075 class ModelInvariantRowMapper : public QObject
0076 {
0077     friend class ModelInvariantIndex;
0078 
0079     Q_OBJECT
0080 
0081 public:
0082     explicit ModelInvariantRowMapper();
0083     ~ModelInvariantRowMapper() override;
0084 
0085     /**
0086      * Sets the maximum time we can spend inside a single lazy update step.
0087      * The larger this time, the more resources we consume and leave less to UI processing
0088      * but also larger the update throughput (that is, we update more items per second).
0089      * This is 50 msec by default.
0090      */
0091     void setLazyUpdateChunkInterval(int chunkInterval);
0092 
0093     /**
0094      * Sets the idle time between two lazy updates in milliseconds.
0095      * The larger this time, the less resources we consume and leave more to UI processing
0096      * but also smaller the update throughput (that is, we update less items per second).
0097      * This is 50 msec by default.
0098      */
0099     void setLazyUpdateIdleInterval(int idleInterval);
0100 
0101     /**
0102      * Maps a ModelInvariantIndex to the CURRENT associated row index in the model.
0103      * As long as the underlying model is consistent, the returned row index is guaranteed to
0104      * point to the content that this ModelInvariantIndex pointed at the time it was created.
0105      *
0106      * Returns the current associated row index in the model or -1 if the invariant
0107      * does not belong to this mapper (model) or is marked as invalid at all.
0108      */
0109     int modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant);
0110 
0111     /**
0112      * Binds a ModelInvariantIndex structure to the specified CURRENT modelIndexRow.
0113      * Later you can use the ModelInvariantIndex to retrieve the model contents
0114      * that the parameter modelIndexRow refers to... even if the model changes.
0115      * Call this function only if you're sure that there is no such invariant yet,
0116      * otherwise take a look at modelIndexRowToModelInvariantIndex().
0117      *
0118      * This function ASSUMES that invariantToFill is a newly allocated ModelInvariantIndex.
0119      */
0120     void createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill);
0121 
0122     /**
0123      * Finds the existing ModelInvariantIndex that belongs to the specified CURRENT modelIndexRow.
0124      * Returns the ModelInvariantIndex found or 0 if such an invariant wasn't yet
0125      * created (by the means of createModelInvariantIndex()).
0126      */
0127     ModelInvariantIndex *modelIndexRowToModelInvariantIndex(int modelIndexRow);
0128 
0129     /**
0130      * This basically applies modelIndexRowToModelInvariantIndex() to a range of elements.
0131      * The returned pointer can be null if no existing ModelInvariantIndex object were
0132      * present in the range (this can happen if you don't request some of them). If the returned
0133      * value is not 0 then you're responsible of deleting it.
0134      */
0135     QList<ModelInvariantIndex *> *modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count);
0136 
0137     /**
0138      * Call this function when rows are inserted to the underlying model
0139      * BEFORE scanning the model for the new items. You probably
0140      * want this function to be the first call in your rowsInserted() handler
0141      * or the last call in the rowsAboutToBeInserted() handler.
0142      */
0143     void modelRowsInserted(int modelIndexRowPosition, int count);
0144 
0145     /**
0146      * Call this function when rows are removed from the underlying model
0147      * AFTER accessing the removed rows for the last time. You probably
0148      * want this function to be the first call of your rowsRemoved() handler
0149      * or the last call in the rowsAboutToBeRemoved() handler.
0150      *
0151      * This function will invalidate any ModelInvariantIndex instances
0152      * that are affected by the change. It will also do you a favor by returning
0153      * the list of the invalidated ModelInvariantIndex objects since
0154      * you'll probably want to delete them. The returned pointer can be null
0155      * if no existing ModelInvariantIndex object were affected by the change
0156      * (this can happen if you don't request some of them). If the returned
0157      * value is not 0 then you're responsible of deleting it.
0158      */
0159     QList<ModelInvariantIndex *> *modelRowsRemoved(int modelIndexRowPosition, int count);
0160 
0161     /**
0162      * Call this function from your handlers of reset() and layoutChanged()
0163      * AFTER you ve last accessed the model underlying data.
0164      * You probably want this function to be the first call of your
0165      * reset() or layoutChanged() handlers.
0166      *
0167      * This function assumes that all the ModelInvariantIndex
0168      * are being invalidated and need to be required.
0169      */
0170     void modelReset();
0171 
0172 private:
0173     std::unique_ptr<ModelInvariantRowMapperPrivate> const d;
0174 };
0175 } // namespace Core
0176 } // namespace MessageList