File indexing completed on 2025-04-27 03:58:40
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-06-13 0007 * Description : A QCompleter for AbstractAlbumModels 0008 * 0009 * SPDX-FileCopyrightText: 2007-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2009-2010 by Johannes Wienke <languitar at semipol dot de> 0011 * SPDX-FileCopyrightText: 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "modelcompleter.h" 0018 0019 // Qt includes 0020 0021 #include <QTimer> 0022 #include <QPointer> 0023 #include <QStringListModel> 0024 0025 // Local includes 0026 0027 #include "digikam_debug.h" 0028 0029 namespace Digikam 0030 { 0031 0032 class Q_DECL_HIDDEN ModelCompleter::Private 0033 { 0034 public: 0035 0036 explicit Private() 0037 : displayRole (Qt::DisplayRole), 0038 uniqueIdRole (Qt::DisplayRole), 0039 delayedModelTimer(nullptr), 0040 stringModel (nullptr), 0041 model (nullptr) 0042 { 0043 } 0044 0045 int displayRole; 0046 int uniqueIdRole; 0047 0048 QTimer* delayedModelTimer; 0049 QStringListModel* stringModel; 0050 QPointer<QAbstractItemModel> model; 0051 0052 /** 0053 * This map maps model indexes to their current text representation in the 0054 * completion object. This is needed because if data changes in one index, 0055 * the old text value is not known anymore, so that it cannot be removed 0056 * from the completion object. 0057 * TODO: if we want to use models that return unique strings but not integer, add support 0058 */ 0059 QHash<int, QString> idToTextHash; 0060 }; 0061 0062 ModelCompleter::ModelCompleter(QObject* const parent) 0063 : QCompleter(parent), 0064 d (new Private) 0065 { 0066 d->stringModel = new QStringListModel(this); 0067 setModel(d->stringModel); 0068 0069 setModelSorting(CaseSensitivelySortedModel); 0070 setCaseSensitivity(Qt::CaseInsensitive); 0071 setCompletionMode(PopupCompletion); 0072 setCompletionRole(Qt::DisplayRole); 0073 setFilterMode(Qt::MatchContains); 0074 setMaxVisibleItems(10); 0075 setCompletionColumn(0); 0076 0077 d->delayedModelTimer = new QTimer(this); 0078 d->delayedModelTimer->setInterval(1000); 0079 d->delayedModelTimer->setSingleShot(true); 0080 0081 connect(d->delayedModelTimer, SIGNAL(timeout()), 0082 this, SLOT(slotDelayedModelTimer())); 0083 0084 connect(this, SIGNAL(activated(QModelIndex)), 0085 this, SIGNAL(signalActivated())); 0086 0087 connect(this, SIGNAL(highlighted(QModelIndex)), 0088 this, SLOT(slotHighlighted(QModelIndex))); 0089 } 0090 0091 ModelCompleter::~ModelCompleter() 0092 { 0093 delete d; 0094 } 0095 0096 void ModelCompleter::setItemModel(QAbstractItemModel* const model, int uniqueIdRole, int displayRole) 0097 { 0098 // first release old model 0099 0100 if (d->model) 0101 { 0102 disconnect(d->model); 0103 d->idToTextHash.clear(); 0104 d->stringModel->setStringList(QStringList()); 0105 } 0106 0107 d->model = model; 0108 d->displayRole = displayRole; 0109 d->uniqueIdRole = uniqueIdRole; 0110 0111 // connect to the new model 0112 0113 if (d->model) 0114 { 0115 connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), 0116 this, SLOT(slotRowsInserted(QModelIndex,int,int))); 0117 0118 connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), 0119 this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); 0120 0121 connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 0122 this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); 0123 0124 connect(d->model, SIGNAL(modelReset()), 0125 this, SLOT(slotModelReset())); 0126 0127 // do an initial sync wit the new model 0128 0129 sync(d->model); 0130 } 0131 } 0132 0133 QAbstractItemModel* ModelCompleter::itemModel() const 0134 { 0135 return d->model; 0136 } 0137 0138 void ModelCompleter::addItem(const QString& item) 0139 { 0140 QStringList list = d->stringModel->stringList(); 0141 setList(list << item); 0142 } 0143 0144 void ModelCompleter::setList(const QStringList& list) 0145 { 0146 d->stringModel->setStringList(list); 0147 d->stringModel->sort(0); 0148 } 0149 0150 QStringList ModelCompleter::items() const 0151 { 0152 return d->stringModel->stringList(); 0153 } 0154 0155 void ModelCompleter::slotDelayedModelTimer() 0156 { 0157 QStringList list = d->idToTextHash.values(); 0158 list.removeDuplicates(); 0159 setList(list); 0160 } 0161 0162 void ModelCompleter::slotRowsInserted(const QModelIndex& parent, int start, int end) 0163 { 0164 for (int i = start ; i <= end ; ++i) 0165 { 0166 // this cannot work if this is called from rowsAboutToBeInserted 0167 // because then the model doesn't know the index yet. So never do this 0168 // ;) 0169 0170 const QModelIndex child = d->model->index(i, 0, parent); 0171 0172 if (child.isValid()) 0173 { 0174 sync(d->model, child); 0175 } 0176 else 0177 { 0178 qCDebug(DIGIKAM_WIDGETS_LOG) << "inserted rows are not valid for parent" << parent 0179 << parent.data(d->displayRole).toString() 0180 << "and child" << child; 0181 } 0182 } 0183 0184 d->delayedModelTimer->start(); 0185 } 0186 0187 void ModelCompleter::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) 0188 { 0189 for (int i = start ; i <= end ; ++i) 0190 { 0191 QModelIndex index = d->model->index(i, 0, parent); 0192 0193 if (!index.isValid()) 0194 { 0195 qCDebug(DIGIKAM_WIDGETS_LOG) << "Received an invalid index to be removed"; 0196 continue; 0197 } 0198 0199 int id = index.data(d->uniqueIdRole).toInt(); 0200 0201 if (d->idToTextHash.contains(id)) 0202 { 0203 QString itemName = d->idToTextHash.value(id); 0204 d->idToTextHash.remove(id); 0205 0206 // only delete an item in the completion object if there is no other 0207 // item with the same display name 0208 0209 if (d->idToTextHash.keys(itemName).isEmpty()) 0210 { 0211 d->delayedModelTimer->start(); 0212 } 0213 } 0214 else 0215 { 0216 qCWarning(DIGIKAM_WIDGETS_LOG) << "idToTextHash seems to be out of sync with the model." 0217 << "There is no entry for model index" << index; 0218 } 0219 } 0220 } 0221 0222 void ModelCompleter::slotModelReset() 0223 { 0224 sync(d->model); 0225 } 0226 0227 void ModelCompleter::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) 0228 { 0229 for (int row = topLeft.row() ; row <= bottomRight.row() ; ++row) 0230 { 0231 if (!d->model->hasIndex(row, topLeft.column(), topLeft.parent())) 0232 { 0233 qCDebug(DIGIKAM_WIDGETS_LOG) << "Got wrong change event for index with row" << row 0234 << ", column" << topLeft.column() 0235 << "and parent" << topLeft.parent() 0236 << "in model" << d->model 0237 << ". Ignoring it."; 0238 continue; 0239 } 0240 0241 QModelIndex index = d->model->index(row, topLeft.column(), topLeft.parent()); 0242 0243 if (!index.isValid()) 0244 { 0245 qCDebug(DIGIKAM_WIDGETS_LOG) << "illegal index in changed data"; 0246 continue; 0247 } 0248 0249 int id = index.data(d->uniqueIdRole).toInt(); 0250 QString itemName = index.data(d->displayRole).toString(); 0251 d->idToTextHash[id] = itemName; 0252 0253 d->delayedModelTimer->start(); 0254 } 0255 } 0256 0257 void ModelCompleter::sync(QAbstractItemModel* const model) 0258 { 0259 d->idToTextHash.clear(); 0260 0261 for (int i = 0 ; i < model->rowCount() ; ++i) 0262 { 0263 const QModelIndex index = model->index(i, 0); 0264 sync(model, index); 0265 } 0266 0267 d->delayedModelTimer->start(); 0268 } 0269 0270 void ModelCompleter::sync(QAbstractItemModel* const model, const QModelIndex& index) 0271 { 0272 QString itemName = index.data(d->displayRole).toString(); 0273 d->idToTextHash.insert(index.data(d->uniqueIdRole).toInt(), itemName); 0274 0275 for (int i = 0 ; i < model->rowCount(index) ; ++i) 0276 { 0277 const QModelIndex child = model->index(i, 0, index); 0278 sync(model, child); 0279 } 0280 } 0281 0282 void ModelCompleter::slotHighlighted(const QModelIndex& index) 0283 { 0284 if (index.isValid()) 0285 { 0286 QString itemName = index.data().toString(); 0287 0288 if (d->idToTextHash.values().count(itemName) == 1) 0289 { 0290 Q_EMIT signalHighlighted(d->idToTextHash.key(itemName)); 0291 } 0292 } 0293 } 0294 0295 } // namespace Digikam 0296 0297 #include "moc_modelcompleter.cpp"