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