Kirigami2

delegaterecycler.cpp
1 /*
2  * SPDX-FileCopyrightText: 2011 Marco Martin <[email protected]>
3  * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <[email protected]>
4  *
5  * SPDX-License-Identifier: LGPL-2.0-or-later
6  */
7 
8 #include "delegaterecycler.h"
9 
10 #include "loggingcategory.h"
11 #include <QDebug>
12 #include <QQmlComponent>
13 #include <QQmlContext>
14 #include <QQmlEngine>
15 
16 DelegateRecyclerAttached::DelegateRecyclerAttached(QObject *parent)
17  : QObject(parent)
18 {
19 }
20 
21 DelegateRecyclerAttached::~DelegateRecyclerAttached()
22 {
23 }
24 /*
25 void setRecycler(DelegateRecycler *recycler)
26 {
27  m_recycler = recycler;
28 }
29 
30 DelegateRecycler *recycler() const
31 {
32  return m_recycler;
33 }
34 */
35 
36 class DelegateCache
37 {
38 public:
39  DelegateCache();
40  ~DelegateCache();
41 
42  void ref(QQmlComponent *);
43  void deref(QQmlComponent *);
44 
45  void insert(QQmlComponent *, QQuickItem *);
46  QQuickItem *take(QQmlComponent *);
47 
48 private:
49  static const int s_cacheSize = 40;
52 };
53 
54 Q_GLOBAL_STATIC(DelegateCache, s_delegateCache)
55 
56 DelegateCache::DelegateCache()
57 {
58 }
59 
60 DelegateCache::~DelegateCache()
61 {
62  for (auto &item : std::as_const(m_unusedItems)) {
63  qDeleteAll(item);
64  }
65 }
66 
67 void DelegateCache::ref(QQmlComponent *component)
68 {
69  m_refs[component]++;
70 }
71 
72 void DelegateCache::deref(QQmlComponent *component)
73 {
74  auto itRef = m_refs.find(component);
75  if (itRef == m_refs.end()) {
76  return;
77  }
78 
79  (*itRef)--;
80  if (*itRef <= 0) {
81  m_refs.erase(itRef);
82 
83  qDeleteAll(m_unusedItems.take(component));
84  }
85 }
86 
87 void DelegateCache::insert(QQmlComponent *component, QQuickItem *item)
88 {
89  auto &items = m_unusedItems[component];
90  if (items.length() >= s_cacheSize) {
91  item->deleteLater();
92  return;
93  }
94 
95  DelegateRecyclerAttached *attached = qobject_cast<DelegateRecyclerAttached *>(qmlAttachedPropertiesObject<DelegateRecycler>(item, false));
96  if (attached) {
97  Q_EMIT attached->pooled();
98  }
99 
100  item->setParentItem(nullptr);
101  items.append(item);
102 }
103 
104 QQuickItem *DelegateCache::take(QQmlComponent *component)
105 {
106  auto it = m_unusedItems.find(component);
107  if (it != m_unusedItems.end() && !it->isEmpty()) {
108  return it->takeFirst();
109  }
110  return nullptr;
111 }
112 
113 DelegateRecycler::DelegateRecycler(QQuickItem *parent)
114  : QQuickItem(parent)
115 {
117 }
118 
119 DelegateRecycler::~DelegateRecycler()
120 {
121  if (m_sourceComponent) {
122  s_delegateCache->insert(m_sourceComponent, m_item);
123  s_delegateCache->deref(m_sourceComponent);
124  }
125 }
126 
127 void DelegateRecycler::syncIndex()
128 {
129  const QVariant newIndex = m_propertiesTracker->property("trackedIndex");
130  if (!m_item || !newIndex.isValid()) {
131  return;
132  }
134  ctx->setContextProperty(QStringLiteral("index"), newIndex);
135 }
136 
137 void DelegateRecycler::syncModel()
138 {
139  const QVariant newModel = m_propertiesTracker->property("trackedModel");
140  if (!m_item || !newModel.isValid()) {
141  return;
142  }
144  ctx->setContextProperty(QStringLiteral("model"), newModel);
145 
146  // try to bind all properties
147  QObject *modelObj = newModel.value<QObject *>();
148  if (modelObj) {
149  const QMetaObject *metaObj = modelObj->metaObject();
150  for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
151  ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj));
152  }
153  }
154 }
155 
156 void DelegateRecycler::syncModelProperties()
157 {
158  const QVariant model = m_propertiesTracker->property("trackedModel");
159  if (!m_item || !model.isValid()) {
160  return;
161  }
163 
164  // try to bind all properties
165  QObject *modelObj = model.value<QObject *>();
166  if (modelObj) {
167  const QMetaObject *metaObj = modelObj->metaObject();
168  for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
169  ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj));
170  }
171  }
172 }
173 
174 void DelegateRecycler::syncModelData()
175 {
176  const QVariant newModelData = m_propertiesTracker->property("trackedModelData");
177  if (!m_item || !newModelData.isValid()) {
178  return;
179  }
181  ctx->setContextProperty(QStringLiteral("modelData"), newModelData);
182 }
183 
185 {
186  return m_sourceComponent;
187 }
188 
189 void DelegateRecycler::setSourceComponent(QQmlComponent *component)
190 {
191  if (component && component->parent() == this) {
192  qCWarning(KirigamiLog) << "Error: source components cannot be declared inside DelegateRecycler";
193  return;
194  }
195  if (m_sourceComponent == component) {
196  return;
197  }
198 
199  if (!m_propertiesTracker) {
200  static QHash<QQmlEngine *, QQmlComponent *> propertiesTrackerComponent;
201  auto engine = qmlEngine(this);
202  auto it = propertiesTrackerComponent.find(engine);
203  if (it == propertiesTrackerComponent.end()) {
204  connect(engine, &QObject::destroyed, engine, [engine] {
205  propertiesTrackerComponent.remove(engine);
206  });
207  it = propertiesTrackerComponent.insert(engine, new QQmlComponent(engine, engine));
208 
209  /* clang-format off */
210  (*it)->setData(QByteArrayLiteral(R"(
211 import QtQuick 2.3
212 QtObject {
213  property int trackedIndex: index
214  property var trackedModel: typeof model != 'undefined' ? model : null
215  property var trackedModelData: typeof modelData != 'undefined' ? modelData : null
216 }
217 )"), QUrl(QStringLiteral("delegaterecycler.cpp")));
218  }
219  /* clang-format on */
220  m_propertiesTracker = (*it)->create(QQmlEngine::contextForObject(this));
221 
222  connect(m_propertiesTracker, SIGNAL(trackedIndexChanged()), this, SLOT(syncIndex()));
223  connect(m_propertiesTracker, SIGNAL(trackedModelChanged()), this, SLOT(syncModel()));
224  connect(m_propertiesTracker, SIGNAL(trackedModelDataChanged()), this, SLOT(syncModelData()));
225  }
226 
227  if (m_sourceComponent) {
228  if (m_item) {
229  disconnect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints);
230  disconnect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints);
231  s_delegateCache->insert(component, m_item);
232  }
233  s_delegateCache->deref(component);
234  }
235 
236  m_sourceComponent = component;
237  s_delegateCache->ref(component);
238 
239  m_item = s_delegateCache->take(component);
240 
241  if (!m_item) {
242  QQuickItem *candidate = parentItem();
243  QQmlContext *ctx = nullptr;
244  if (component->creationContext()) {
245  ctx = new QQmlContext(component->creationContext());
246  }
247  while (!ctx && candidate) {
248  QQmlContext *parentCtx = QQmlEngine::contextForObject(candidate);
249  if (parentCtx) {
250  ctx = new QQmlContext(parentCtx, candidate);
251  break;
252  } else {
253  candidate = candidate->parentItem();
254  }
255  }
256 
257  Q_ASSERT(ctx);
258 
259  QObject *contextObjectToSet = nullptr;
260  {
261  // Find the first parent that has a context object with a valid translationDomain property, i.e. is a KLocalizedContext
262  QQmlContext *auxCtx = ctx;
263  while (auxCtx != nullptr) {
264  QObject *auxCtxObj = auxCtx->contextObject();
265  if (auxCtxObj && auxCtxObj->property("translationDomain").isValid()) {
266  contextObjectToSet = auxCtxObj;
267  break;
268  }
269  auxCtx = auxCtx->parentContext();
270  }
271  }
272  if (contextObjectToSet) {
273  ctx->setContextObject(contextObjectToSet);
274  }
275 
276  QObject *modelObj = m_propertiesTracker->property("trackedModel").value<QObject *>();
277  if (modelObj) {
278  const QMetaObject *metaObj = modelObj->metaObject();
279  for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
280  QMetaProperty prop = metaObj->property(i);
281  ctx->setContextProperty(QString::fromUtf8(prop.name()), prop.read(modelObj));
282  if (prop.hasNotifySignal()) {
283  QMetaMethod updateSlot = metaObject()->method(metaObject()->indexOfSlot("syncModelProperties()"));
284  connect(modelObj, prop.notifySignal(), this, updateSlot);
285  }
286  }
287  }
288 
289  ctx->setContextProperty(QStringLiteral("model"), m_propertiesTracker->property("trackedModel"));
290  ctx->setContextProperty(QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData"));
291  ctx->setContextProperty(QStringLiteral("index"), m_propertiesTracker->property("trackedIndex"));
292  ctx->setContextProperty(QStringLiteral("delegateRecycler"), this);
293 
294  QObject *obj = component->create(ctx);
295  m_item = qobject_cast<QQuickItem *>(obj);
296  if (!m_item) {
297  obj->deleteLater();
298  } else {
299  connect(m_item.data(), &QObject::destroyed, ctx, &QObject::deleteLater);
300  // if the user binded an explicit width, consider it, otherwise base upon implicit
301  m_widthFromItem = m_item->width() > 0 && m_item->width() != m_item->implicitWidth();
302  m_heightFromItem = m_item->height() > 0 && m_item->height() != m_item->implicitHeight();
303 
304  if (m_widthFromItem && m_heightFromItem) {
305  connect(m_item.data(), &QQuickItem::heightChanged, this, [this]() {
306  updateSize(false);
307  });
308  }
309  }
310  } else {
311  syncModel();
312 
314  ctx->setContextProperties({QQmlContext::PropertyPair{QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData")},
315  QQmlContext::PropertyPair{QStringLiteral("index"), m_propertiesTracker->property("trackedIndex")},
316  QQmlContext::PropertyPair{QStringLiteral("delegateRecycler"), QVariant::fromValue<QObject *>(this)}});
317 
318  DelegateRecyclerAttached *attached = qobject_cast<DelegateRecyclerAttached *>(qmlAttachedPropertiesObject<DelegateRecycler>(m_item, false));
319  if (attached) {
320  Q_EMIT attached->reused();
321  }
322  }
323 
324  if (m_item) {
325  m_item->setParentItem(this);
326  connect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints);
327  connect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints);
328 
329  updateSize(true);
330  }
331 
332  Q_EMIT sourceComponentChanged();
333 }
334 
335 void DelegateRecycler::resetSourceComponent()
336 {
337  s_delegateCache->deref(m_sourceComponent);
338  m_sourceComponent = nullptr;
339 }
340 
341 DelegateRecyclerAttached *DelegateRecycler::qmlAttachedProperties(QObject *object)
342 {
343  return new DelegateRecyclerAttached(object);
344 }
345 
346 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
347 void DelegateRecycler::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
348 #else
349 void DelegateRecycler::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
350 #endif
351 {
352  if (m_item && newGeometry.size() != oldGeometry.size()) {
353  updateSize(true);
354  }
355 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
356  QQuickItem::geometryChanged(newGeometry, oldGeometry);
357 #else
358  QQuickItem::geometryChange(newGeometry, oldGeometry);
359 #endif
360 }
361 
362 void DelegateRecycler::focusInEvent(QFocusEvent *event)
363 {
365  if (!m_item) {
366  return;
367  }
368 
369  m_item->setFocus(event->reason());
370 }
371 
372 void DelegateRecycler::updateHints()
373 {
374  updateSize(false);
375 }
376 
377 void DelegateRecycler::updateSize(bool parentResized)
378 {
379  if (!m_item) {
380  return;
381  }
382 
383  const bool needToUpdateWidth = !m_widthFromItem && parentResized && widthValid();
384  const bool needToUpdateHeight = !m_heightFromItem && parentResized && heightValid();
385 
386  if (parentResized) {
387  m_item->setPosition(QPoint(0, 0));
388  }
389  if (needToUpdateWidth && needToUpdateHeight) {
390  m_item->setSize(QSizeF(width(), height()));
391  } else if (needToUpdateWidth) {
392  m_item->setWidth(width());
393  } else if (needToUpdateHeight) {
394  m_item->setHeight(height());
395  }
396 
397  if (m_updatingSize) {
398  return;
399  }
400 
401  m_updatingSize = true;
402 
403  if (m_heightFromItem) {
404  setHeight(m_item->height());
405  }
406  if (m_widthFromItem) {
407  setWidth(m_item->width());
408  }
409 
410  setImplicitSize(m_item->implicitWidth() >= 0 ? m_item->implicitWidth() : m_item->width(),
411  m_item->implicitHeight() >= 0 ? m_item->implicitHeight() : m_item->height());
412 
413  m_updatingSize = false;
414 }
QQmlComponent sourceComponent
The Component the actual delegates will be built from.
bool isValid() const const
QString fromUtf8(const char *str, int size)
T value() const const
void ref()
QHash::iterator find(const Key &key)
QObject * contextObject() const const
virtual QObject * create(QQmlContext *context)
void setContextProperty(const QString &name, QObject *value)
QMetaMethod notifySignal() const const
void deref()
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
void implicitHeightChanged()
bool hasNotifySignal() const const
QHash::iterator insert(const Key &key, const T &value)
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
void destroyed(QObject *obj)
QQmlContext * contextForObject(const QObject *object)
void deleteLater()
void setParentItem(QQuickItem *parent)
QQmlContext * parentContext() const const
int propertyCount() const const
virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
virtual void focusInEvent(QFocusEvent *)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void heightChanged()
QVariant read(const QObject *object) const const
virtual const QMetaObject * metaObject() const const
const char * name() const const
KGuiItem insert()
QMetaProperty property(int index) const const
int remove(const Key &key)
void implicitWidthChanged()
void setContextProperties(const QVector< QQmlContext::PropertyPair > &properties)
QSizeF size() const const
int propertyOffset() const const
void setContextObject(QObject *object)
QObject * parent() const const
QHash::iterator end()
QQmlContext * creationContext() const const
QVariant property(const char *name) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Jan 29 2023 04:11:03 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.