File indexing completed on 2024-04-28 15:27:41
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org> 0003 * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "delegaterecycler.h" 0009 0010 #include "loggingcategory.h" 0011 #include <QDebug> 0012 #include <QQmlComponent> 0013 #include <QQmlContext> 0014 #include <QQmlEngine> 0015 0016 DelegateRecyclerAttached::DelegateRecyclerAttached(QObject *parent) 0017 : QObject(parent) 0018 { 0019 } 0020 0021 DelegateRecyclerAttached::~DelegateRecyclerAttached() 0022 { 0023 } 0024 /* 0025 void setRecycler(DelegateRecycler *recycler) 0026 { 0027 m_recycler = recycler; 0028 } 0029 0030 DelegateRecycler *recycler() const 0031 { 0032 return m_recycler; 0033 } 0034 */ 0035 0036 class DelegateCache 0037 { 0038 public: 0039 DelegateCache(); 0040 ~DelegateCache(); 0041 0042 void ref(QQmlComponent *); 0043 void deref(QQmlComponent *); 0044 0045 void insert(QQmlComponent *, QQuickItem *); 0046 QQuickItem *take(QQmlComponent *); 0047 0048 private: 0049 static const int s_cacheSize = 40; 0050 QHash<QQmlComponent *, int> m_refs; 0051 QHash<QQmlComponent *, QList<QQuickItem *>> m_unusedItems; 0052 }; 0053 0054 Q_GLOBAL_STATIC(DelegateCache, s_delegateCache) 0055 0056 DelegateCache::DelegateCache() 0057 { 0058 } 0059 0060 DelegateCache::~DelegateCache() 0061 { 0062 for (auto &item : std::as_const(m_unusedItems)) { 0063 qDeleteAll(item); 0064 } 0065 } 0066 0067 void DelegateCache::ref(QQmlComponent *component) 0068 { 0069 m_refs[component]++; 0070 } 0071 0072 void DelegateCache::deref(QQmlComponent *component) 0073 { 0074 auto itRef = m_refs.find(component); 0075 if (itRef == m_refs.end()) { 0076 return; 0077 } 0078 0079 (*itRef)--; 0080 if (*itRef <= 0) { 0081 m_refs.erase(itRef); 0082 0083 qDeleteAll(m_unusedItems.take(component)); 0084 } 0085 } 0086 0087 void DelegateCache::insert(QQmlComponent *component, QQuickItem *item) 0088 { 0089 auto &items = m_unusedItems[component]; 0090 if (items.length() >= s_cacheSize) { 0091 item->deleteLater(); 0092 return; 0093 } 0094 0095 DelegateRecyclerAttached *attached = qobject_cast<DelegateRecyclerAttached *>(qmlAttachedPropertiesObject<DelegateRecycler>(item, false)); 0096 if (attached) { 0097 Q_EMIT attached->pooled(); 0098 } 0099 0100 item->setParentItem(nullptr); 0101 items.append(item); 0102 } 0103 0104 QQuickItem *DelegateCache::take(QQmlComponent *component) 0105 { 0106 auto it = m_unusedItems.find(component); 0107 if (it != m_unusedItems.end() && !it->isEmpty()) { 0108 return it->takeFirst(); 0109 } 0110 return nullptr; 0111 } 0112 0113 DelegateRecycler::DelegateRecycler(QQuickItem *parent) 0114 : QQuickItem(parent) 0115 { 0116 setFlags(QQuickItem::ItemIsFocusScope); 0117 } 0118 0119 DelegateRecycler::~DelegateRecycler() 0120 { 0121 if (m_sourceComponent) { 0122 s_delegateCache->insert(m_sourceComponent, m_item); 0123 s_delegateCache->deref(m_sourceComponent); 0124 } 0125 } 0126 0127 void DelegateRecycler::syncIndex() 0128 { 0129 const QVariant newIndex = m_propertiesTracker->property("trackedIndex"); 0130 if (!m_item || !newIndex.isValid()) { 0131 return; 0132 } 0133 QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); 0134 ctx->setContextProperty(QStringLiteral("index"), newIndex); 0135 } 0136 0137 void DelegateRecycler::syncModel() 0138 { 0139 const QVariant newModel = m_propertiesTracker->property("trackedModel"); 0140 if (!m_item || !newModel.isValid()) { 0141 return; 0142 } 0143 QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); 0144 ctx->setContextProperty(QStringLiteral("model"), newModel); 0145 0146 // try to bind all properties 0147 QObject *modelObj = newModel.value<QObject *>(); 0148 if (modelObj) { 0149 const QMetaObject *metaObj = modelObj->metaObject(); 0150 for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { 0151 ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj)); 0152 } 0153 } 0154 } 0155 0156 void DelegateRecycler::syncModelProperties() 0157 { 0158 const QVariant model = m_propertiesTracker->property("trackedModel"); 0159 if (!m_item || !model.isValid()) { 0160 return; 0161 } 0162 QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); 0163 0164 // try to bind all properties 0165 QObject *modelObj = model.value<QObject *>(); 0166 if (modelObj) { 0167 const QMetaObject *metaObj = modelObj->metaObject(); 0168 for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { 0169 ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj)); 0170 } 0171 } 0172 } 0173 0174 void DelegateRecycler::syncModelData() 0175 { 0176 const QVariant newModelData = m_propertiesTracker->property("trackedModelData"); 0177 if (!m_item || !newModelData.isValid()) { 0178 return; 0179 } 0180 QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); 0181 ctx->setContextProperty(QStringLiteral("modelData"), newModelData); 0182 } 0183 0184 QQmlComponent *DelegateRecycler::sourceComponent() const 0185 { 0186 return m_sourceComponent; 0187 } 0188 0189 void DelegateRecycler::setSourceComponent(QQmlComponent *component) 0190 { 0191 if (component && component->parent() == this) { 0192 qCWarning(KirigamiLog) << "Error: source components cannot be declared inside DelegateRecycler"; 0193 return; 0194 } 0195 if (m_sourceComponent == component) { 0196 return; 0197 } 0198 0199 if (!m_propertiesTracker) { 0200 static QHash<QQmlEngine *, QQmlComponent *> propertiesTrackerComponent; 0201 auto engine = qmlEngine(this); 0202 auto it = propertiesTrackerComponent.find(engine); 0203 if (it == propertiesTrackerComponent.end()) { 0204 connect(engine, &QObject::destroyed, engine, [engine] { 0205 propertiesTrackerComponent.remove(engine); 0206 }); 0207 it = propertiesTrackerComponent.insert(engine, new QQmlComponent(engine, engine)); 0208 0209 /* clang-format off */ 0210 (*it)->setData(QByteArrayLiteral(R"( 0211 import QtQuick 2.3 0212 QtObject { 0213 property int trackedIndex: index 0214 property var trackedModel: typeof model != 'undefined' ? model : null 0215 property var trackedModelData: typeof modelData != 'undefined' ? modelData : null 0216 } 0217 )"), QUrl(QStringLiteral("delegaterecycler.cpp"))); 0218 } 0219 /* clang-format on */ 0220 m_propertiesTracker = (*it)->create(QQmlEngine::contextForObject(this)); 0221 0222 connect(m_propertiesTracker, SIGNAL(trackedIndexChanged()), this, SLOT(syncIndex())); 0223 connect(m_propertiesTracker, SIGNAL(trackedModelChanged()), this, SLOT(syncModel())); 0224 connect(m_propertiesTracker, SIGNAL(trackedModelDataChanged()), this, SLOT(syncModelData())); 0225 } 0226 0227 if (m_sourceComponent) { 0228 if (m_item) { 0229 disconnect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints); 0230 disconnect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints); 0231 s_delegateCache->insert(component, m_item); 0232 } 0233 s_delegateCache->deref(component); 0234 } 0235 0236 m_sourceComponent = component; 0237 s_delegateCache->ref(component); 0238 0239 m_item = s_delegateCache->take(component); 0240 0241 if (!m_item) { 0242 QQuickItem *candidate = parentItem(); 0243 QQmlContext *ctx = nullptr; 0244 if (component->creationContext()) { 0245 ctx = new QQmlContext(component->creationContext()); 0246 } 0247 while (!ctx && candidate) { 0248 QQmlContext *parentCtx = QQmlEngine::contextForObject(candidate); 0249 if (parentCtx) { 0250 ctx = new QQmlContext(parentCtx, candidate); 0251 break; 0252 } else { 0253 candidate = candidate->parentItem(); 0254 } 0255 } 0256 0257 Q_ASSERT(ctx); 0258 0259 QObject *contextObjectToSet = nullptr; 0260 { 0261 // Find the first parent that has a context object with a valid translationDomain property, i.e. is a KLocalizedContext 0262 QQmlContext *auxCtx = ctx; 0263 while (auxCtx != nullptr) { 0264 QObject *auxCtxObj = auxCtx->contextObject(); 0265 if (auxCtxObj && auxCtxObj->property("translationDomain").isValid()) { 0266 contextObjectToSet = auxCtxObj; 0267 break; 0268 } 0269 auxCtx = auxCtx->parentContext(); 0270 } 0271 } 0272 if (contextObjectToSet) { 0273 ctx->setContextObject(contextObjectToSet); 0274 } 0275 0276 QObject *modelObj = m_propertiesTracker->property("trackedModel").value<QObject *>(); 0277 if (modelObj) { 0278 const QMetaObject *metaObj = modelObj->metaObject(); 0279 for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { 0280 QMetaProperty prop = metaObj->property(i); 0281 ctx->setContextProperty(QString::fromUtf8(prop.name()), prop.read(modelObj)); 0282 if (prop.hasNotifySignal()) { 0283 QMetaMethod updateSlot = metaObject()->method(metaObject()->indexOfSlot("syncModelProperties()")); 0284 connect(modelObj, prop.notifySignal(), this, updateSlot); 0285 } 0286 } 0287 } 0288 0289 ctx->setContextProperty(QStringLiteral("model"), m_propertiesTracker->property("trackedModel")); 0290 ctx->setContextProperty(QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData")); 0291 ctx->setContextProperty(QStringLiteral("index"), m_propertiesTracker->property("trackedIndex")); 0292 ctx->setContextProperty(QStringLiteral("delegateRecycler"), this); 0293 0294 QObject *obj = component->create(ctx); 0295 m_item = qobject_cast<QQuickItem *>(obj); 0296 if (!m_item) { 0297 obj->deleteLater(); 0298 } else { 0299 connect(m_item.data(), &QObject::destroyed, ctx, &QObject::deleteLater); 0300 // if the user binded an explicit width, consider it, otherwise base upon implicit 0301 m_widthFromItem = m_item->width() > 0 && m_item->width() != m_item->implicitWidth(); 0302 m_heightFromItem = m_item->height() > 0 && m_item->height() != m_item->implicitHeight(); 0303 0304 if (m_widthFromItem && m_heightFromItem) { 0305 connect(m_item.data(), &QQuickItem::heightChanged, this, [this]() { 0306 updateSize(false); 0307 }); 0308 } 0309 } 0310 } else { 0311 syncModel(); 0312 0313 QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); 0314 ctx->setContextProperties({QQmlContext::PropertyPair{QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData")}, 0315 QQmlContext::PropertyPair{QStringLiteral("index"), m_propertiesTracker->property("trackedIndex")}, 0316 QQmlContext::PropertyPair{QStringLiteral("delegateRecycler"), QVariant::fromValue<QObject *>(this)}}); 0317 0318 DelegateRecyclerAttached *attached = qobject_cast<DelegateRecyclerAttached *>(qmlAttachedPropertiesObject<DelegateRecycler>(m_item, false)); 0319 if (attached) { 0320 Q_EMIT attached->reused(); 0321 } 0322 } 0323 0324 if (m_item) { 0325 m_item->setParentItem(this); 0326 connect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints); 0327 connect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints); 0328 0329 updateSize(true); 0330 } 0331 0332 Q_EMIT sourceComponentChanged(); 0333 } 0334 0335 void DelegateRecycler::resetSourceComponent() 0336 { 0337 s_delegateCache->deref(m_sourceComponent); 0338 m_sourceComponent = nullptr; 0339 } 0340 0341 DelegateRecyclerAttached *DelegateRecycler::qmlAttachedProperties(QObject *object) 0342 { 0343 return new DelegateRecyclerAttached(object); 0344 } 0345 0346 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0347 void DelegateRecycler::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) 0348 #else 0349 void DelegateRecycler::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) 0350 #endif 0351 { 0352 if (m_item && newGeometry.size() != oldGeometry.size()) { 0353 updateSize(true); 0354 } 0355 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0356 QQuickItem::geometryChanged(newGeometry, oldGeometry); 0357 #else 0358 QQuickItem::geometryChange(newGeometry, oldGeometry); 0359 #endif 0360 } 0361 0362 void DelegateRecycler::focusInEvent(QFocusEvent *event) 0363 { 0364 QQuickItem::focusInEvent(event); 0365 if (!m_item) { 0366 return; 0367 } 0368 0369 m_item->setFocus(event->reason()); 0370 } 0371 0372 void DelegateRecycler::updateHints() 0373 { 0374 updateSize(false); 0375 } 0376 0377 void DelegateRecycler::updateSize(bool parentResized) 0378 { 0379 if (!m_item) { 0380 return; 0381 } 0382 0383 const bool needToUpdateWidth = !m_widthFromItem && parentResized && widthValid(); 0384 const bool needToUpdateHeight = !m_heightFromItem && parentResized && heightValid(); 0385 0386 if (parentResized) { 0387 m_item->setPosition(QPoint(0, 0)); 0388 } 0389 if (needToUpdateWidth && needToUpdateHeight) { 0390 m_item->setSize(QSizeF(width(), height())); 0391 } else if (needToUpdateWidth) { 0392 m_item->setWidth(width()); 0393 } else if (needToUpdateHeight) { 0394 m_item->setHeight(height()); 0395 } 0396 0397 if (m_updatingSize) { 0398 return; 0399 } 0400 0401 m_updatingSize = true; 0402 0403 if (m_heightFromItem) { 0404 setHeight(m_item->height()); 0405 } 0406 if (m_widthFromItem) { 0407 setWidth(m_item->width()); 0408 } 0409 0410 setImplicitSize(m_item->implicitWidth() >= 0 ? m_item->implicitWidth() : m_item->width(), 0411 m_item->implicitHeight() >= 0 ? m_item->implicitHeight() : m_item->height()); 0412 0413 m_updatingSize = false; 0414 } 0415 0416 #include "moc_delegaterecycler.cpp"