1/*
2 * SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "columnview.h"
8#include "columnview_p.h"
9
10#include "loggingcategory.h"
11#include <QAbstractItemModel>
12#include <QGuiApplication>
13#include <QJsonDocument>
14#include <QJsonObject>
15#include <QJsonParseError>
16#include <QPropertyAnimation>
17#include <QQmlComponent>
18#include <QQmlContext>
19#include <QQmlEngine>
20#include <QStyleHints>
21#include <algorithm>
22#include <qnamespace.h>
23#include <qpropertyanimation.h>
24
25#include "platform/units.h"
26
27class QmlComponentsPoolSingleton
28{
29public:
30 QmlComponentsPoolSingleton()
31 {
32 }
33 static QmlComponentsPool *instance(QQmlEngine *engine);
34
35private:
36 QHash<QQmlEngine *, QmlComponentsPool *> m_instances;
37};
38
39Q_GLOBAL_STATIC(QmlComponentsPoolSingleton, privateQmlComponentsPoolSelf)
40
41QmlComponentsPool *QmlComponentsPoolSingleton::instance(QQmlEngine *engine)
42{
43 Q_ASSERT(engine);
44 auto componentPool = privateQmlComponentsPoolSelf->m_instances.value(key: engine);
45
46 if (componentPool) {
47 return componentPool;
48 }
49
50 componentPool = new QmlComponentsPool(engine);
51
52 const auto removePool = [engine]() {
53 // NB: do not dereference engine. it may be dangling already!
54 if (privateQmlComponentsPoolSelf) {
55 privateQmlComponentsPoolSelf->m_instances.remove(key: engine);
56 }
57 };
58 QObject::connect(sender: engine, signal: &QObject::destroyed, context: engine, slot: removePool);
59 QObject::connect(sender: componentPool, signal: &QObject::destroyed, context: componentPool, slot: removePool);
60
61 privateQmlComponentsPoolSelf->m_instances[engine] = componentPool;
62 return componentPool;
63}
64
65QmlComponentsPool::QmlComponentsPool(QQmlEngine *engine)
66 : QObject(engine)
67 , m_separatorComponent(engine)
68{
69 m_separatorComponent.loadFromModule(uri: "org.kde.kirigami.layouts.private", typeName: "ColumnViewSeparator");
70
71 if (m_separatorComponent.isError()) {
72 qCFatal(KirigamiLayoutsLog) << m_separatorComponent.errors();
73 }
74
75 m_units = engine->singletonInstance<Kirigami::Platform::Units *>(uri: "org.kde.kirigami.platform", typeName: "Units");
76 Q_ASSERT(m_units);
77
78 connect(sender: m_units, signal: &Kirigami::Platform::Units::gridUnitChanged, context: this, slot: &QmlComponentsPool::gridUnitChanged);
79 connect(sender: m_units, signal: &Kirigami::Platform::Units::longDurationChanged, context: this, slot: &QmlComponentsPool::longDurationChanged);
80}
81
82QmlComponentsPool::~QmlComponentsPool()
83{
84}
85
86/////////
87
88ColumnViewAttached::ColumnViewAttached(QObject *parent)
89 : QObject(parent)
90{
91}
92
93ColumnViewAttached::~ColumnViewAttached()
94{
95}
96
97void ColumnViewAttached::setIndex(int index)
98{
99 if (!m_customFillWidth && m_view) {
100 const bool oldFillWidth = m_fillWidth;
101 m_fillWidth = index == m_view->count() - 1;
102 if (oldFillWidth != m_fillWidth) {
103 Q_EMIT fillWidthChanged();
104 }
105 }
106
107 if (index == m_index) {
108 return;
109 }
110
111 m_index = index;
112 Q_EMIT indexChanged();
113}
114
115int ColumnViewAttached::index() const
116{
117 return m_index;
118}
119
120void ColumnViewAttached::setFillWidth(bool fill)
121{
122 if (m_view) {
123 disconnect(sender: m_view.data(), signal: &ColumnView::countChanged, receiver: this, zero: nullptr);
124 }
125 m_customFillWidth = true;
126
127 if (fill == m_fillWidth) {
128 return;
129 }
130
131 m_fillWidth = fill;
132 Q_EMIT fillWidthChanged();
133
134 if (m_view) {
135 m_view->polish();
136 }
137}
138
139bool ColumnViewAttached::fillWidth() const
140{
141 return m_fillWidth;
142}
143
144qreal ColumnViewAttached::reservedSpace() const
145{
146 if (m_customReservedSpace) {
147 return m_reservedSpace;
148 } else if (!m_view) {
149 return 0.0;
150 } else if (m_view->columnResizeMode() == ColumnView::FixedColumns) {
151 return m_view->columnWidth();
152 } else {
153 QQuickItem *prevItem = m_view->get(index: m_index - 1);
154 QQuickItem *nextItem = m_view->get(index: m_index + 1);
155
156 if (prevItem) {
157 ColumnViewAttached *prevAttached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: prevItem, create: true));
158
159 if (!prevAttached->fillWidth()) {
160 return prevItem->width();
161 }
162 }
163 if (nextItem) {
164 ColumnViewAttached *prevAttached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: nextItem, create: true));
165
166 if (!prevAttached->fillWidth()) {
167 return nextItem->width();
168 }
169 }
170 }
171 return m_view->columnWidth();
172}
173
174void ColumnViewAttached::setReservedSpace(qreal space)
175{
176 if (m_view) {
177 disconnect(sender: m_view.data(), signal: &ColumnView::columnWidthChanged, receiver: this, slot: &ColumnViewAttached::reservedSpaceChanged);
178 }
179 m_customReservedSpace = true;
180
181 if (qFuzzyCompare(p1: space, p2: m_reservedSpace)) {
182 return;
183 }
184
185 m_reservedSpace = space;
186 Q_EMIT reservedSpaceChanged();
187
188 if (m_view) {
189 m_view->polish();
190 }
191}
192
193qreal ColumnViewAttached::minimumWidth() const
194{
195 return m_minimumWidth;
196}
197
198void ColumnViewAttached::setMinimumWidth(qreal width)
199{
200 if (qFuzzyCompare(p1: width, p2: m_minimumWidth)) {
201 return;
202 }
203
204 m_minimumWidth = width;
205
206 Q_EMIT minimumWidthChanged();
207
208 if (m_view) {
209 m_view->polish();
210 }
211}
212
213qreal ColumnViewAttached::preferredWidth() const
214{
215 qreal preferred = m_preferredWidth;
216 // Fallback to the item implicitWidth
217 if (preferred < 0) {
218 preferred = static_cast<QQuickItem *>(parent())->implicitWidth();
219 }
220 return std::max(a: m_minimumWidth, b: std::min(a: m_maximumWidth, b: preferred));
221}
222
223void ColumnViewAttached::setPreferredWidth(qreal width)
224{
225 if (qFuzzyCompare(p1: width, p2: m_preferredWidth)) {
226 return;
227 }
228
229 m_preferredWidth = width;
230
231 Q_EMIT preferredWidthChanged();
232
233 if (m_view) {
234 m_view->polish();
235 }
236}
237
238qreal ColumnViewAttached::maximumWidth() const
239{
240 return m_maximumWidth;
241}
242
243void ColumnViewAttached::setMaximumWidth(qreal width)
244{
245 if (qFuzzyCompare(p1: width, p2: m_maximumWidth)) {
246 return;
247 }
248
249 m_maximumWidth = width;
250
251 Q_EMIT maximumWidthChanged();
252
253 if (m_view) {
254 m_view->polish();
255 }
256}
257
258ColumnView *ColumnViewAttached::view()
259{
260 return m_view;
261}
262
263void ColumnViewAttached::setView(ColumnView *view)
264{
265 if (view == m_view) {
266 return;
267 }
268
269 if (m_view) {
270 disconnect(sender: m_view.data(), signal: nullptr, receiver: this, member: nullptr);
271 }
272 m_view = view;
273
274 if (!m_customFillWidth && m_view) {
275 m_fillWidth = m_index == m_view->count() - 1;
276 connect(sender: m_view.data(), signal: &ColumnView::countChanged, context: this, slot: [this]() {
277 m_fillWidth = m_index == m_view->count() - 1;
278 Q_EMIT fillWidthChanged();
279 });
280 }
281 if (!m_customReservedSpace && m_view) {
282 connect(sender: m_view.data(), signal: &ColumnView::columnWidthChanged, context: this, slot: &ColumnViewAttached::reservedSpaceChanged);
283 }
284
285 Q_EMIT viewChanged();
286}
287
288QQuickItem *ColumnViewAttached::originalParent() const
289{
290 return m_originalParent;
291}
292
293void ColumnViewAttached::setOriginalParent(QQuickItem *parent)
294{
295 m_originalParent = parent;
296}
297
298bool ColumnViewAttached::shouldDeleteOnRemove() const
299{
300 return m_shouldDeleteOnRemove;
301}
302
303void ColumnViewAttached::setShouldDeleteOnRemove(bool del)
304{
305 m_shouldDeleteOnRemove = del;
306}
307
308bool ColumnViewAttached::preventStealing() const
309{
310 return m_preventStealing;
311}
312
313void ColumnViewAttached::setPreventStealing(bool prevent)
314{
315 if (prevent == m_preventStealing) {
316 return;
317 }
318
319 m_preventStealing = prevent;
320 Q_EMIT preventStealingChanged();
321}
322
323bool ColumnViewAttached::isPinned() const
324{
325 return m_pinned;
326}
327
328void ColumnViewAttached::setPinned(bool pinned)
329{
330 if (pinned == m_pinned) {
331 return;
332 }
333
334 m_pinned = pinned;
335
336 Q_EMIT pinnedChanged();
337
338 if (m_view) {
339 m_view->polish();
340 }
341}
342
343bool ColumnViewAttached::inViewport() const
344{
345 return m_inViewport;
346}
347
348void ColumnViewAttached::setInViewport(bool inViewport)
349{
350 if (m_inViewport == inViewport) {
351 return;
352 }
353
354 m_inViewport = inViewport;
355
356 Q_EMIT inViewportChanged();
357}
358
359bool ColumnViewAttached::interactiveResizeEnabled() const
360{
361 return m_interactiveResizeEnabled;
362}
363
364void ColumnViewAttached::setInteractiveResizeEnabled(bool interactive)
365{
366 if (interactive == m_interactiveResizeEnabled) {
367 return;
368 }
369
370 m_interactiveResizeEnabled = interactive;
371
372 Q_EMIT interactiveResizeEnabledChanged();
373}
374
375bool ColumnViewAttached::interactiveResizing() const
376{
377 return m_interactiveResizing;
378}
379
380void ColumnViewAttached::setInteractiveResizing(bool interactive)
381{
382 if (interactive == m_interactiveResizing) {
383 return;
384 }
385
386 m_interactiveResizing = interactive;
387
388 Q_EMIT interactiveResizingChanged();
389 if (!m_interactiveResizing && m_view) {
390 Q_EMIT m_view->savedStateChanged();
391 }
392}
393
394QQuickItem *ColumnViewAttached::globalHeader() const
395{
396 return m_globalHeader;
397}
398
399void ColumnViewAttached::setGlobalHeader(QQuickItem *header)
400{
401 if (header == m_globalHeader) {
402 return;
403 }
404
405 QQuickItem *oldHeader = m_globalHeader;
406 if (m_globalHeader) {
407 disconnect(sender: m_globalHeader, signal: nullptr, receiver: this, member: nullptr);
408 }
409
410 m_globalHeader = header;
411
412 connect(sender: header, signal: &QObject::destroyed, context: this, slot: [this, header]() {
413 globalHeaderChanged(oldHeader: header, newHeader: nullptr);
414 });
415
416 Q_EMIT globalHeaderChanged(oldHeader, newHeader: header);
417}
418
419QQuickItem *ColumnViewAttached::globalFooter() const
420{
421 return m_globalFooter;
422}
423
424void ColumnViewAttached::setGlobalFooter(QQuickItem *footer)
425{
426 if (footer == m_globalFooter) {
427 return;
428 }
429
430 QQuickItem *oldFooter = m_globalFooter;
431 if (m_globalFooter) {
432 disconnect(sender: m_globalFooter, signal: nullptr, receiver: this, member: nullptr);
433 }
434
435 m_globalFooter = footer;
436
437 connect(sender: footer, signal: &QObject::destroyed, context: this, slot: [this, footer]() {
438 globalFooterChanged(oldFooter: footer, newFooter: nullptr);
439 });
440
441 Q_EMIT globalFooterChanged(oldFooter, newFooter: footer);
442}
443
444/////////
445
446ContentItem::ContentItem(ColumnView *parent)
447 : QQuickItem(parent)
448 , m_view(parent)
449{
450 m_globalHeaderParent = new QQuickItem(this);
451 // Move above the pages
452 m_globalHeaderParent->setZ(1);
453 m_globalFooterParent = new QQuickItem(this);
454 m_globalFooterParent->setZ(1);
455
456 setFlags(flags() | ItemIsFocusScope);
457 m_slideAnim = new QPropertyAnimation(this);
458 m_slideAnim->setTargetObject(this);
459 m_slideAnim->setPropertyName("x");
460 // NOTE: the duration will be taken from kirigami units upon classBegin
461 m_slideAnim->setDuration(0);
462 m_slideAnim->setEasingCurve(QEasingCurve(QEasingCurve::OutExpo));
463 connect(sender: m_slideAnim, signal: &QPropertyAnimation::finished, context: this, slot: [this]() {
464 while (!m_disappearingItems.isEmpty()) {
465 m_view->removeItem(item: m_disappearingItems.first());
466 }
467 if (!m_view->currentItem()) {
468 m_view->setCurrentIndex(m_items.indexOf(t: m_viewAnchorItem));
469 } else {
470 QRectF mapped = m_view->currentItem()->mapRectToItem(item: m_view, rect: QRectF(QPointF(0, 0), m_view->currentItem()->size()));
471 if (!QRectF(QPointF(0, 0), m_view->size()).intersects(r: mapped)) {
472 m_view->setCurrentIndex(m_items.indexOf(t: m_viewAnchorItem));
473 }
474 }
475 });
476
477 connect(sender: this, signal: &QQuickItem::xChanged, context: this, slot: &ContentItem::layoutPinnedItems);
478 m_creationInProgress = false;
479}
480
481ContentItem::~ContentItem()
482{
483}
484
485void ContentItem::setBoundedX(qreal x)
486{
487 if (!parentItem()) {
488 return;
489 }
490 m_slideAnim->stop();
491 setX(qRound(d: qBound(min: qMin(a: 0.0, b: -width() + parentItem()->width()), val: x, max: 0.0)));
492}
493
494void ContentItem::animateX(qreal newX)
495{
496 if (!parentItem()) {
497 return;
498 }
499
500 const qreal to = qRound(d: qBound(min: qMin(a: 0.0, b: -width() + parentItem()->width()), val: newX, max: 0.0));
501
502 m_slideAnim->stop();
503 m_slideAnim->setStartValue(x());
504 m_slideAnim->setEndValue(to);
505 m_slideAnim->start();
506}
507
508void ContentItem::snapToItem()
509{
510 QQuickItem *firstItem = childAt(x: viewportLeft(), y: height() / 2);
511 if (!firstItem) {
512 return;
513 }
514 QQuickItem *nextItem = childAt(x: firstItem->x() + firstItem->width() + 1, y: height() / 2);
515
516 // need to make the last item visible?
517 if (nextItem && //
518 ((m_view->dragging() && m_lastDragDelta < 0) //
519 || (!m_view->dragging() //
520 && (width() - viewportRight()) < (viewportLeft() - firstItem->x())))) {
521 m_viewAnchorItem = nextItem;
522 animateX(newX: -nextItem->x() + m_leftPinnedSpace);
523
524 // The first one found?
525 } else if ((m_view->dragging() && m_lastDragDelta >= 0) //
526 || (!m_view->dragging() && (viewportLeft() <= (firstItem->x() + (firstItem->width() / 2)))) //
527 || !nextItem) {
528 m_viewAnchorItem = firstItem;
529 animateX(newX: -firstItem->x() + m_leftPinnedSpace);
530
531 // the second?
532 } else {
533 m_viewAnchorItem = nextItem;
534 animateX(newX: -nextItem->x() + m_leftPinnedSpace);
535 }
536}
537
538qreal ContentItem::viewportLeft() const
539{
540 return -x() + m_leftPinnedSpace;
541}
542
543qreal ContentItem::viewportRight() const
544{
545 return -x() + m_view->width() - m_rightPinnedSpace;
546}
547
548qreal ContentItem::childWidth(QQuickItem *child)
549{
550 if (!parentItem()) {
551 return 0.0;
552 }
553
554 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: child, create: true));
555
556 if (m_columnResizeMode == ColumnView::SingleColumn) {
557 return qRound(d: parentItem()->width());
558
559 } else if (attached->fillWidth()) {
560 if (m_view->count() == 1) {
561 // single column
562 return qRound(d: parentItem()->width());
563 }
564
565 return qRound(d: qBound(min: m_columnWidth, val: (parentItem()->width() - attached->reservedSpace()), max: std::max(a: m_columnWidth, b: parentItem()->width())));
566
567 } else if (m_columnResizeMode == ColumnView::FixedColumns) {
568 return qRound(d: qMin(a: parentItem()->width(), b: m_columnWidth));
569
570 // DynamicColumns
571 } else {
572 // TODO:look for Layout size hints
573 qreal width = attached->preferredWidth();
574
575 if (width < 1.0) {
576 width = m_columnWidth;
577 }
578
579 if (attached->minimumWidth() >= 0 && attached->maximumWidth() >= 0) {
580 width = std::clamp(val: width, lo: attached->minimumWidth(), hi: attached->maximumWidth());
581 } else if (attached->minimumWidth() >= 0) {
582 width = std::max(a: width, b: attached->minimumWidth());
583 } else if (attached->maximumWidth() >= 0) {
584 width = std::min(a: width, b: attached->maximumWidth());
585 }
586
587 return qRound(d: qMin(a: m_view->width(), b: width));
588 }
589}
590
591void ContentItem::layoutItems()
592{
593 setY(m_view->topPadding());
594 setHeight(m_view->height() - m_view->topPadding() - m_view->bottomPadding());
595
596 qreal implicitWidth = 0;
597 qreal implicitHeight = 0;
598 qreal partialWidth = 0;
599 int i = 0;
600 m_leftPinnedSpace = 0;
601 m_rightPinnedSpace = 0;
602
603 bool reverse = qApp->layoutDirection() == Qt::RightToLeft;
604 auto it = !reverse ? m_items.begin() : m_items.end() - 1;
605 int increment = reverse ? -1 : +1;
606 auto lastPos = reverse ? m_items.begin() - 1 : m_items.end();
607
608 QQuickItem *previousChild = nullptr;
609
610 for (; it != lastPos; it += increment) {
611 // for (QQuickItem *child : std::as_const(m_items)) {
612 QQuickItem *child = *it;
613 QQuickItem *nextChild = nullptr;
614 if (reverse) {
615 if (it != m_items.begin()) {
616 nextChild = *(it - 1);
617 }
618 } else {
619 if ((it + 1) != m_items.end()) {
620 nextChild = *(it + 1);
621 }
622 }
623
624 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: child, create: true));
625 if (child == m_globalHeaderParent || child == m_globalFooterParent) {
626 continue;
627 }
628
629 if (child->isVisible()) {
630 if (attached->isPinned() && m_view->columnResizeMode() != ColumnView::SingleColumn) {
631 const qreal width = childWidth(child);
632 const qreal widthDiff = std::max(a: 0.0, b: m_view->width() - child->width()); // it's possible for the view width to be smaller than the child width
633 const qreal pageX = std::clamp(val: partialWidth, lo: -x(), hi: -x() + widthDiff);
634 qreal headerHeight = .0;
635 qreal footerHeight = .0;
636 if (QQuickItem *header = attached->globalHeader()) {
637 headerHeight = header->isVisible() ? header->height() : .0;
638 header->setWidth(width);
639 header->setPosition(QPointF(pageX, .0));
640 header->setZ(2);
641 }
642 if (QQuickItem *footer = attached->globalFooter()) {
643 footerHeight = footer->isVisible() ? footer->height() : .0;
644 footer->setWidth(width);
645 footer->setPosition(QPointF(pageX, height() - footerHeight));
646 footer->setZ(2);
647 }
648
649 child->setSize(QSizeF(width, height() - headerHeight - footerHeight));
650 child->setPosition(QPointF(pageX, headerHeight));
651 child->setZ(1);
652
653 if (partialWidth <= -x()) {
654 m_leftPinnedSpace = qMax(a: m_leftPinnedSpace, b: width);
655 } else if (partialWidth > -x() + m_view->width() - child->width()) {
656 m_rightPinnedSpace = qMax(a: m_rightPinnedSpace, b: child->width());
657 }
658
659 partialWidth += width;
660
661 } else {
662 const qreal width = childWidth(child);
663 qreal headerHeight = .0;
664 qreal footerHeight = .0;
665 if (m_view->separatorVisible()) {
666 ensureSeparator(previousColumn: previousChild, column: child, nextColumn: nextChild);
667 }
668 if (QQuickItem *header = attached->globalHeader(); header && qmlEngine(header)) {
669 headerHeight = header->isVisible() ? header->height() : .0;
670 header->setWidth(width);
671 header->setPosition(QPointF(partialWidth, .0));
672 header->setZ(1);
673 auto it = m_separators.find(key: header);
674 if (it != m_separators.end()) {
675 it.value()->deleteLater();
676 m_separators.erase(it);
677 }
678 }
679 if (QQuickItem *footer = attached->globalFooter(); footer && qmlEngine(footer)) {
680 footerHeight = footer->isVisible() ? footer->height() : .0;
681 footer->setWidth(width);
682 footer->setPosition(QPointF(partialWidth, height() - footerHeight));
683 footer->setZ(1);
684 // TODO: remove
685 auto it = m_separators.find(key: footer);
686 if (it != m_separators.end()) {
687 it.value()->deleteLater();
688 m_separators.erase(it);
689 }
690 }
691
692 child->setSize(QSizeF(width, height() - headerHeight - footerHeight));
693 child->setPosition(QPointF(partialWidth, headerHeight));
694 child->setZ(0);
695
696 partialWidth += child->width();
697 }
698 }
699
700 if (reverse) {
701 attached->setIndex(m_items.count() - (++i));
702 } else {
703 attached->setIndex(i++);
704 }
705
706 implicitWidth += attached->preferredWidth();
707
708 implicitHeight = qMax(a: implicitHeight, b: child->implicitHeight());
709
710 previousChild = child;
711 }
712
713 setWidth(partialWidth);
714
715 setImplicitWidth(implicitWidth);
716 setImplicitHeight(implicitHeight);
717
718 m_view->setImplicitWidth(implicitWidth);
719 m_view->setImplicitHeight(implicitHeight + m_view->topPadding() + m_view->bottomPadding());
720
721 const qreal newContentX = (m_viewAnchorItem ? -m_viewAnchorItem->x() : 0.0) + m_leftPinnedSpace;
722 if (m_shouldAnimate) {
723 animateX(newX: newContentX);
724 } else {
725 setBoundedX(newContentX);
726 }
727
728 updateVisibleItems();
729}
730
731void ContentItem::layoutPinnedItems()
732{
733 if (m_view->columnResizeMode() == ColumnView::SingleColumn) {
734 return;
735 }
736
737 qreal partialWidth = 0;
738 m_leftPinnedSpace = 0;
739 m_rightPinnedSpace = 0;
740
741 QQuickItem *previousChild = nullptr;
742
743 for (auto it = m_items.constBegin(); it != m_items.constEnd(); it++) {
744 // for (QQuickItem *child : std::as_const(m_items)) {
745 QQuickItem *child = *it;
746 QQuickItem *nextChild = *(it + 1);
747 // for (QQuickItem *child : std::as_const(m_items)) {
748 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: child, create: true));
749
750 if (child->isVisible()) {
751 if (attached->isPinned()) {
752 const qreal pageX = qMin(a: qMax(a: -x(), b: partialWidth), b: -x() + m_view->width() - child->width());
753 qreal headerHeight = .0;
754 qreal footerHeight = .0;
755 if (QQuickItem *header = attached->globalHeader()) {
756 headerHeight = header->isVisible() ? header->height() : .0;
757 header->setPosition(QPointF(pageX, .0));
758 }
759 if (QQuickItem *footer = attached->globalFooter()) {
760 footerHeight = footer->isVisible() ? footer->height() : .0;
761 footer->setPosition(QPointF(pageX, height() - footerHeight));
762 }
763 child->setPosition(QPointF(pageX, headerHeight));
764
765 if (partialWidth <= -x()) {
766 m_leftPinnedSpace = qMax(a: m_leftPinnedSpace, b: child->width());
767 } else if (partialWidth > -x() + m_view->width() - child->width()) {
768 m_rightPinnedSpace = qMax(a: m_rightPinnedSpace, b: child->width());
769 }
770 }
771
772 partialWidth += child->width();
773 }
774 }
775}
776
777void ContentItem::updateVisibleItems()
778{
779 QList<QQuickItem *> newItems;
780
781 for (auto *item : std::as_const(t&: m_items)) {
782 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
783
784 if (item->isVisible() && item->x() + x() < m_view->width() && item->x() + item->width() + x() > 0) {
785 newItems << item;
786 connect(sender: item, signal: &QObject::destroyed, context: this, slot: [this, item] {
787 m_visibleItems.removeAll(t: item);
788 });
789 attached->setInViewport(true);
790 item->setEnabled(true);
791 if (attached->globalHeader()) {
792 attached->globalHeader()->setEnabled(true);
793 }
794 if (attached->globalFooter()) {
795 attached->globalFooter()->setEnabled(true);
796 }
797 } else {
798 attached->setInViewport(false);
799 item->setEnabled(false);
800 if (attached->globalHeader()) {
801 attached->globalHeader()->setEnabled(false);
802 }
803 if (attached->globalFooter()) {
804 attached->globalFooter()->setEnabled(false);
805 }
806 }
807 }
808
809 for (auto *item : std::as_const(t&: m_visibleItems)) {
810 disconnect(sender: item, signal: &QObject::destroyed, receiver: this, zero: nullptr);
811 }
812
813 const QQuickItem *oldLeadingVisibleItem = m_view->leadingVisibleItem();
814 const QQuickItem *oldTrailingVisibleItem = m_view->trailingVisibleItem();
815
816 if (newItems != m_visibleItems) {
817 m_visibleItems = newItems;
818 Q_EMIT m_view->visibleItemsChanged();
819 if (!m_visibleItems.isEmpty() && m_visibleItems.first() != oldLeadingVisibleItem) {
820 Q_EMIT m_view->leadingVisibleItemChanged();
821 }
822 if (!m_visibleItems.isEmpty() && m_visibleItems.last() != oldTrailingVisibleItem) {
823 Q_EMIT m_view->trailingVisibleItemChanged();
824 }
825 }
826}
827
828void ContentItem::forgetItem(QQuickItem *item)
829{
830 if (!m_items.contains(t: item) && !m_disappearingItems.contains(t: item)) {
831 return;
832 }
833
834 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
835 attached->setView(nullptr);
836 attached->setIndex(-1);
837
838 disconnect(sender: attached, signal: nullptr, receiver: this, member: nullptr);
839 disconnect(sender: item, signal: nullptr, receiver: this, member: nullptr);
840 disconnect(sender: item, signal: nullptr, receiver: m_view, member: nullptr);
841
842 QQuickItem *separatorItem = m_separators.take(key: item);
843 if (separatorItem) {
844 separatorItem->deleteLater();
845 }
846
847 if (QQuickItem *header = attached->globalHeader()) {
848 // disconnecting before hiding avoids one extra layoutItems,
849 // which would try to recreate separatorItem.
850 // Which might crash if we are doing this during item destruction
851 disconnect(sender: header, signal: nullptr, receiver: this, member: nullptr);
852 header->setVisible(false);
853 header->setParentItem(item);
854 separatorItem = m_separators.take(key: header);
855 if (separatorItem) {
856 separatorItem->deleteLater();
857 }
858 }
859 if (QQuickItem *footer = attached->globalFooter()) {
860 disconnect(sender: footer, signal: nullptr, receiver: this, member: nullptr);
861 footer->setVisible(false);
862 footer->setParentItem(item);
863 separatorItem = m_separators.take(key: footer);
864 if (separatorItem) {
865 separatorItem->deleteLater();
866 }
867 }
868
869 const int index = m_items.indexOf(t: item);
870 m_items.removeAll(t: item);
871 m_disappearingItems.removeAll(t: item);
872 // We are connected not only to destroyed but also to lambdas
873 disconnect(sender: item, signal: nullptr, receiver: this, member: nullptr);
874 updateVisibleItems();
875 m_shouldAnimate = true;
876 m_view->polish();
877
878 if (index >= 0) {
879 if (index <= m_view->currentIndex()) {
880 m_view->setCurrentIndex(m_items.isEmpty() ? 0 : qBound(min: 0, val: index - 1, max: m_items.count() - 1));
881 }
882
883 Q_EMIT m_view->countChanged();
884 }
885}
886
887QQuickItem *ContentItem::ensureSeparator(QQuickItem *previousColumn, QQuickItem *column, QQuickItem *nextColumn)
888{
889 QQuickItem *separatorItem = m_separators.value(key: column);
890
891 if (!separatorItem) {
892 separatorItem = qobject_cast<QQuickItem *>(
893 o: QmlComponentsPoolSingleton::instance(engine: qmlEngine(column))->m_separatorComponent.beginCreate(QQmlEngine::contextForObject(column)));
894 if (separatorItem) {
895 // Do NOT parent the separator to the column
896 // we are managing the lifetime of the separator on this side,
897 // therefore if is column itself deleting it, we will have a double dellete
898 separatorItem->setParent(this);
899 separatorItem->setParentItem(column);
900 separatorItem->setZ(9999);
901 separatorItem->setProperty(name: "column", value: QVariant::fromValue(value: column));
902 QmlComponentsPoolSingleton::instance(engine: qmlEngine(column))->m_separatorComponent.completeCreate();
903 m_separators[column] = separatorItem;
904 }
905 }
906
907 if (separatorItem) {
908 separatorItem->setProperty(name: "previousColumn", value: QVariant::fromValue(value: previousColumn));
909 separatorItem->setProperty(name: "nextColumn", value: QVariant::fromValue(value: nextColumn));
910 }
911
912 return separatorItem;
913}
914
915void ContentItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
916{
917 if (m_creationInProgress) {
918 QQuickItem::itemChange(change, value);
919 return;
920 }
921 switch (change) {
922 case QQuickItem::ItemChildAddedChange: {
923 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: value.item, create: true));
924 attached->setView(m_view);
925
926 // connect(attached, &ColumnViewAttached::fillWidthChanged, m_view, &ColumnView::polish);
927 connect(sender: attached, signal: &ColumnViewAttached::fillWidthChanged, context: this, slot: [this] {
928 m_view->polish();
929 });
930 connect(sender: attached, signal: &ColumnViewAttached::reservedSpaceChanged, context: m_view, slot: &ColumnView::polish);
931
932 value.item->setVisible(true);
933
934 if (!m_items.contains(t: value.item)) {
935 connect(sender: value.item, signal: &QQuickItem::widthChanged, context: m_view, slot: &ColumnView::polish);
936 QQuickItem *item = value.item;
937 m_items << item;
938 connect(sender: item, signal: &QObject::destroyed, context: this, slot: [this, item]() {
939 m_view->removeItem(item);
940 });
941 }
942
943 m_shouldAnimate = true;
944 m_view->polish();
945 Q_EMIT m_view->countChanged();
946 break;
947 }
948 case QQuickItem::ItemChildRemovedChange: {
949 forgetItem(item: value.item);
950 break;
951 }
952 case QQuickItem::ItemVisibleHasChanged:
953 updateVisibleItems();
954 if (value.boolValue) {
955 m_view->polish();
956 }
957 break;
958 default:
959 break;
960 }
961 QQuickItem::itemChange(change, value);
962}
963
964void ContentItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
965{
966 updateVisibleItems();
967 QQuickItem::geometryChange(newGeometry, oldGeometry);
968}
969
970void ContentItem::syncItemsOrder()
971{
972 if (m_items == childItems()) {
973 return;
974 }
975
976 m_items = childItems();
977 // NOTE: polish() here sometimes gets indefinitely delayed and items changing order isn't seen
978 layoutItems();
979}
980
981void ContentItem::updateRepeaterModel()
982{
983 if (!sender()) {
984 return;
985 }
986
987 QObject *modelObj = sender()->property(name: "model").value<QObject *>();
988
989 if (!modelObj) {
990 m_models.remove(key: sender());
991 return;
992 }
993
994 if (m_models[sender()]) {
995 disconnect(sender: m_models[sender()], signal: nullptr, receiver: this, member: nullptr);
996 }
997
998 m_models[sender()] = modelObj;
999
1000 QAbstractItemModel *qaim = qobject_cast<QAbstractItemModel *>(object: modelObj);
1001
1002 if (qaim) {
1003 connect(sender: qaim, signal: &QAbstractItemModel::rowsMoved, context: this, slot: &ContentItem::syncItemsOrder);
1004
1005 } else {
1006 connect(sender: modelObj, SIGNAL(childrenChanged()), receiver: this, SLOT(syncItemsOrder()));
1007 }
1008}
1009
1010void ContentItem::connectHeader(QQuickItem *oldHeader, QQuickItem *newHeader)
1011{
1012 if (oldHeader) {
1013 disconnect(sender: oldHeader, signal: nullptr, receiver: this, member: nullptr);
1014 oldHeader->setParentItem(nullptr);
1015 }
1016 if (newHeader) {
1017 connect(sender: newHeader, signal: &QQuickItem::heightChanged, context: this, slot: &ContentItem::layoutItems);
1018 connect(sender: newHeader, signal: &QQuickItem::visibleChanged, context: this, slot: &ContentItem::layoutItems);
1019 newHeader->setParentItem(m_globalHeaderParent);
1020 }
1021}
1022
1023void ContentItem::connectFooter(QQuickItem *oldFooter, QQuickItem *newFooter)
1024{
1025 if (oldFooter) {
1026 disconnect(sender: oldFooter, signal: nullptr, receiver: this, member: nullptr);
1027 oldFooter->setParentItem(nullptr);
1028 }
1029 if (newFooter) {
1030 connect(sender: newFooter, signal: &QQuickItem::heightChanged, context: this, slot: &ContentItem::layoutItems);
1031 connect(sender: newFooter, signal: &QQuickItem::visibleChanged, context: this, slot: &ContentItem::layoutItems);
1032 newFooter->setParentItem(m_globalFooterParent);
1033 }
1034}
1035
1036ColumnView::ColumnView(QQuickItem *parent)
1037 : QQuickItem(parent)
1038 , m_contentItem(nullptr)
1039{
1040 // NOTE: this is to *not* trigger itemChange
1041 m_contentItem = new ContentItem(this);
1042 // Prevent interactions outside of ColumnView bounds, and let it act as a viewport.
1043 setClip(true);
1044 setAcceptedMouseButtons(Qt::BackButton | Qt::ForwardButton);
1045 setAcceptHoverEvents(false);
1046 setAcceptTouchEvents(true);
1047 setFiltersChildMouseEvents(true);
1048
1049 connect(sender: m_contentItem->m_slideAnim,
1050 signal: &QPropertyAnimation::stateChanged,
1051 context: this,
1052 slot: [this](QAbstractAnimation::State newState, QAbstractAnimation::State oldState) {
1053 if ((newState == QAbstractAnimation::Running) == (oldState == QAbstractAnimation::Running)) {
1054 return;
1055 }
1056 m_moving = newState == QAbstractAnimation::Running;
1057 Q_EMIT movingChanged();
1058 });
1059 connect(sender: m_contentItem, signal: &ContentItem::widthChanged, context: this, slot: &ColumnView::contentWidthChanged);
1060 connect(sender: m_contentItem, signal: &ContentItem::xChanged, context: this, slot: &ColumnView::contentXChanged);
1061
1062 connect(sender: this, signal: &ColumnView::activeFocusChanged, context: this, slot: [this]() {
1063 if (hasActiveFocus() && m_currentItem) {
1064 m_currentItem->forceActiveFocus();
1065 }
1066 });
1067 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: this, create: true));
1068 attached->setView(this);
1069 attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: m_contentItem, create: true));
1070 attached->setView(this);
1071}
1072
1073ColumnView::~ColumnView()
1074{
1075}
1076
1077ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const
1078{
1079 return m_contentItem->m_columnResizeMode;
1080}
1081
1082void ColumnView::setColumnResizeMode(ColumnResizeMode mode)
1083{
1084 if (m_contentItem->m_columnResizeMode == mode) {
1085 return;
1086 }
1087
1088 m_contentItem->m_columnResizeMode = mode;
1089 if (mode == SingleColumn && m_currentItem) {
1090 m_contentItem->m_viewAnchorItem = m_currentItem;
1091 }
1092 m_contentItem->m_shouldAnimate = false;
1093 polish();
1094 Q_EMIT columnResizeModeChanged();
1095}
1096
1097qreal ColumnView::columnWidth() const
1098{
1099 return m_contentItem->m_columnWidth;
1100}
1101
1102void ColumnView::setColumnWidth(qreal width)
1103{
1104 // Always forget the internal binding when the user sets anything, even the same value
1105 disconnect(sender: QmlComponentsPoolSingleton::instance(engine: qmlEngine(this)), signal: &QmlComponentsPool::gridUnitChanged, receiver: this, zero: nullptr);
1106
1107 if (m_contentItem->m_columnWidth == width) {
1108 return;
1109 }
1110
1111 m_contentItem->m_columnWidth = width;
1112 m_contentItem->m_shouldAnimate = false;
1113 polish();
1114 Q_EMIT columnWidthChanged();
1115}
1116
1117int ColumnView::currentIndex() const
1118{
1119 return m_currentIndex;
1120}
1121
1122void ColumnView::setCurrentIndex(int index)
1123{
1124 if (m_currentIndex == index || index < -1 || index >= m_contentItem->m_items.count()) {
1125 return;
1126 }
1127
1128 m_currentIndex = index;
1129
1130 if (index == -1) {
1131 m_currentItem.clear();
1132
1133 } else {
1134 m_currentItem = m_contentItem->m_items[index];
1135 Q_ASSERT(m_currentItem);
1136 m_currentItem->forceActiveFocus();
1137
1138 // If the current item is not on view, scroll
1139 QRectF mappedCurrent = m_currentItem->mapRectToItem(item: this, rect: QRectF(QPointF(0, 0), m_currentItem->size()));
1140
1141 if (m_contentItem->m_slideAnim->state() == QAbstractAnimation::Running) {
1142 mappedCurrent.moveLeft(pos: mappedCurrent.left() + m_contentItem->x() + m_contentItem->m_slideAnim->endValue().toInt());
1143 }
1144
1145 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: m_currentItem, create: true));
1146
1147 QRectF contentsRect(attached->isPinned() ? 0 : m_contentItem->m_leftPinnedSpace,
1148 0,
1149 width() - m_contentItem->m_rightPinnedSpace - m_contentItem->m_leftPinnedSpace,
1150 height());
1151
1152 if (!m_dragging) {
1153 if (!contentsRect.contains(r: mappedCurrent)) {
1154 m_contentItem->m_viewAnchorItem = m_currentItem;
1155 if (qApp->layoutDirection() == Qt::RightToLeft) {
1156 m_contentItem->animateX(newX: -m_currentItem->x() - m_currentItem->width() + width());
1157 } else {
1158 m_contentItem->animateX(newX: -m_currentItem->x() + m_contentItem->m_leftPinnedSpace);
1159 }
1160 } else {
1161 m_contentItem->snapToItem();
1162 m_dragging = false;
1163 }
1164 }
1165 }
1166
1167 Q_EMIT currentIndexChanged();
1168 Q_EMIT currentItemChanged();
1169}
1170
1171QQuickItem *ColumnView::currentItem()
1172{
1173 return m_currentItem;
1174}
1175
1176QList<QQuickItem *> ColumnView::visibleItems() const
1177{
1178 return m_contentItem->m_visibleItems;
1179}
1180
1181QQuickItem *ColumnView::leadingVisibleItem() const
1182{
1183 if (m_contentItem->m_visibleItems.isEmpty()) {
1184 return nullptr;
1185 }
1186
1187 return m_contentItem->m_visibleItems.first();
1188}
1189
1190QQuickItem *ColumnView::trailingVisibleItem() const
1191{
1192 if (m_contentItem->m_visibleItems.isEmpty()) {
1193 return nullptr;
1194 }
1195
1196 return qobject_cast<QQuickItem *>(o: m_contentItem->m_visibleItems.last());
1197}
1198
1199int ColumnView::count() const
1200{
1201 return m_contentItem->m_items.count();
1202}
1203
1204qreal ColumnView::topPadding() const
1205{
1206 return m_topPadding;
1207}
1208
1209void ColumnView::setTopPadding(qreal padding)
1210{
1211 if (padding == m_topPadding) {
1212 return;
1213 }
1214
1215 m_topPadding = padding;
1216 polish();
1217 Q_EMIT topPaddingChanged();
1218}
1219
1220qreal ColumnView::bottomPadding() const
1221{
1222 return m_bottomPadding;
1223}
1224
1225void ColumnView::setBottomPadding(qreal padding)
1226{
1227 if (padding == m_bottomPadding) {
1228 return;
1229 }
1230
1231 m_bottomPadding = padding;
1232 polish();
1233 Q_EMIT bottomPaddingChanged();
1234}
1235
1236QQuickItem *ColumnView::contentItem() const
1237{
1238 return m_contentItem;
1239}
1240
1241int ColumnView::scrollDuration() const
1242{
1243 return m_contentItem->m_slideAnim->duration();
1244}
1245
1246void ColumnView::setScrollDuration(int duration)
1247{
1248 disconnect(sender: QmlComponentsPoolSingleton::instance(engine: qmlEngine(this)), signal: &QmlComponentsPool::longDurationChanged, receiver: this, zero: nullptr);
1249
1250 if (m_contentItem->m_slideAnim->duration() == duration) {
1251 return;
1252 }
1253
1254 m_contentItem->m_slideAnim->setDuration(duration);
1255 Q_EMIT scrollDurationChanged();
1256}
1257
1258bool ColumnView::separatorVisible() const
1259{
1260 return m_separatorVisible;
1261}
1262
1263void ColumnView::setSeparatorVisible(bool visible)
1264{
1265 if (visible == m_separatorVisible) {
1266 return;
1267 }
1268
1269 m_separatorVisible = visible;
1270
1271 Q_EMIT separatorVisibleChanged();
1272}
1273
1274bool ColumnView::dragging() const
1275{
1276 return m_dragging;
1277}
1278
1279bool ColumnView::moving() const
1280{
1281 return m_moving;
1282}
1283
1284qreal ColumnView::contentWidth() const
1285{
1286 return m_contentItem->width();
1287}
1288
1289qreal ColumnView::contentX() const
1290{
1291 return -m_contentItem->x();
1292}
1293
1294void ColumnView::setContentX(qreal x) const
1295{
1296 m_contentItem->setX(qRound(d: -x));
1297}
1298
1299bool ColumnView::interactive() const
1300{
1301 return m_interactive;
1302}
1303
1304void ColumnView::setInteractive(bool interactive)
1305{
1306 if (m_interactive == interactive) {
1307 return;
1308 }
1309
1310 m_interactive = interactive;
1311
1312 if (!m_interactive) {
1313 m_contentItem->snapToItem();
1314
1315 if (m_dragging) {
1316 m_dragging = false;
1317 Q_EMIT draggingChanged();
1318 }
1319
1320 setKeepMouseGrab(false);
1321 }
1322
1323 Q_EMIT interactiveChanged();
1324}
1325
1326bool ColumnView::acceptsMouse() const
1327{
1328 return m_acceptsMouse;
1329}
1330
1331void ColumnView::setAcceptsMouse(bool accepts)
1332{
1333 if (m_acceptsMouse == accepts) {
1334 return;
1335 }
1336
1337 m_acceptsMouse = accepts;
1338
1339 Q_EMIT acceptsMouseChanged();
1340}
1341
1342void ColumnView::addItem(QQuickItem *item)
1343{
1344 insertItem(pos: m_contentItem->m_items.length(), item);
1345}
1346
1347void ColumnView::insertItem(int pos, QQuickItem *item)
1348{
1349 if (!item || m_contentItem->m_items.contains(t: item)) {
1350 return;
1351 }
1352
1353 // If there was a pop animation in progress of this item
1354 // and the item is back in, remove it from m_disappearingItems
1355 if (m_contentItem->m_disappearingItems.contains(t: item)) {
1356 m_contentItem->m_disappearingItems.removeAll(t: item);
1357 }
1358
1359 item->setVisible(true);
1360 m_contentItem->m_items.insert(i: qBound(min: 0, val: pos, max: m_contentItem->m_items.length()), t: item);
1361
1362 connect(sender: item, signal: &QObject::destroyed, context: m_contentItem, slot: [this, item]() {
1363 removeItem(item);
1364 });
1365 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
1366 attached->setOriginalParent(item->parentItem());
1367 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1368 item->setParentItem(m_contentItem);
1369 connect(sender: item, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &ColumnView::polish);
1370
1371 item->forceActiveFocus();
1372
1373 if (attached->globalHeader()) {
1374 m_contentItem->connectHeader(oldHeader: nullptr, newHeader: attached->globalHeader());
1375 }
1376 if (attached->globalFooter()) {
1377 m_contentItem->connectFooter(oldFooter: nullptr, newFooter: attached->globalFooter());
1378 }
1379 connect(sender: attached, signal: &ColumnViewAttached::globalHeaderChanged, context: m_contentItem, slot: &ContentItem::connectHeader);
1380 connect(sender: attached, signal: &ColumnViewAttached::globalFooterChanged, context: m_contentItem, slot: &ContentItem::connectFooter);
1381
1382 if (attached->interactiveResizeEnabled()) {
1383 const qreal preferredWidth = m_state.value(key: pos);
1384 if (preferredWidth > 0) {
1385 attached->setPreferredWidth(preferredWidth);
1386 }
1387 }
1388 // Animate shift to new item.
1389 m_contentItem->m_shouldAnimate = true;
1390 m_contentItem->layoutItems();
1391 Q_EMIT contentChildrenChanged();
1392
1393 // In order to keep the same current item we need to increase the current index if displaced
1394 // NOTE: just updating m_currentIndex does *not* update currentItem (which is what we need atm) while setCurrentIndex will update also currentItem
1395 if (m_currentIndex >= pos) {
1396 ++m_currentIndex;
1397 Q_EMIT currentIndexChanged();
1398 }
1399
1400 Q_EMIT itemInserted(position: pos, item);
1401}
1402
1403void ColumnView::replaceItem(int pos, QQuickItem *item)
1404{
1405 if (pos < 0 || pos >= m_contentItem->m_items.length()) {
1406 qCWarning(KirigamiLayoutsLog) << "Position" << pos << "passed to ColumnView::replaceItem is out of range.";
1407 return;
1408 }
1409
1410 if (!item) {
1411 qCWarning(KirigamiLayoutsLog) << "Null item passed to ColumnView::replaceItem.";
1412 return;
1413 }
1414
1415 QQuickItem *oldItem = m_contentItem->m_items[pos];
1416
1417 // In order to keep the same current item we need to increase the current index if displaced
1418 if (m_currentIndex >= pos) {
1419 setCurrentIndex(m_currentIndex - 1);
1420 }
1421
1422 m_contentItem->forgetItem(item: oldItem);
1423 oldItem->setVisible(false);
1424
1425 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: oldItem, create: false));
1426
1427 if (attached && attached->shouldDeleteOnRemove()) {
1428 oldItem->deleteLater();
1429 } else {
1430 oldItem->setParentItem(attached ? attached->originalParent() : nullptr);
1431 }
1432
1433 Q_EMIT itemRemoved(item: oldItem);
1434
1435 if (!m_contentItem->m_items.contains(t: item)) {
1436 m_contentItem->m_items.insert(i: qBound(min: 0, val: pos, max: m_contentItem->m_items.length()), t: item);
1437
1438 connect(sender: item, signal: &QObject::destroyed, context: m_contentItem, slot: [this, item]() {
1439 removeItem(item);
1440 });
1441 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
1442 attached->setOriginalParent(item->parentItem());
1443 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1444 item->setParentItem(m_contentItem);
1445 connect(sender: item, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &ColumnView::polish);
1446
1447 if (attached->globalHeader()) {
1448 m_contentItem->connectHeader(oldHeader: nullptr, newHeader: attached->globalHeader());
1449 }
1450 if (attached->globalFooter()) {
1451 m_contentItem->connectFooter(oldFooter: nullptr, newFooter: attached->globalFooter());
1452 }
1453 connect(sender: attached, signal: &ColumnViewAttached::globalHeaderChanged, context: m_contentItem, slot: &ContentItem::connectHeader);
1454 connect(sender: attached, signal: &ColumnViewAttached::globalFooterChanged, context: m_contentItem, slot: &ContentItem::connectFooter);
1455
1456 if (m_currentIndex >= pos) {
1457 ++m_currentIndex;
1458 Q_EMIT currentIndexChanged();
1459 }
1460
1461 Q_EMIT itemInserted(position: pos, item);
1462 }
1463
1464 // Disable animation so replacement happens immediately.
1465 m_contentItem->m_shouldAnimate = false;
1466 m_contentItem->layoutItems();
1467 Q_EMIT contentChildrenChanged();
1468}
1469
1470void ColumnView::moveItem(int from, int to)
1471{
1472 if (m_contentItem->m_items.isEmpty() //
1473 || from < 0 || from >= m_contentItem->m_items.length() //
1474 || to < 0 || to >= m_contentItem->m_items.length()) {
1475 return;
1476 }
1477
1478 m_contentItem->m_items.move(from, to);
1479 m_contentItem->m_shouldAnimate = true;
1480
1481 if (from == m_currentIndex) {
1482 m_currentIndex = to;
1483 Q_EMIT currentIndexChanged();
1484 } else if (from < m_currentIndex && to > m_currentIndex) {
1485 --m_currentIndex;
1486 Q_EMIT currentIndexChanged();
1487 } else if (from > m_currentIndex && to <= m_currentIndex) {
1488 ++m_currentIndex;
1489 Q_EMIT currentIndexChanged();
1490 }
1491
1492 polish();
1493}
1494
1495QQuickItem *ColumnView::removeItem(QQuickItem *item)
1496{
1497 if (!m_contentItem->m_items.contains(t: item) && !m_contentItem->m_disappearingItems.contains(t: item)) {
1498 return nullptr;
1499 }
1500
1501 const int index = m_contentItem->m_items.indexOf(t: item);
1502
1503 // In order to keep the same current item we need to increase the current index if displaced
1504 if (index >= 0 && m_currentIndex >= index) {
1505 setCurrentIndex(m_currentIndex - 1);
1506 }
1507
1508 m_contentItem->forgetItem(item);
1509 item->setVisible(false);
1510
1511 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: false));
1512
1513 if (attached && attached->shouldDeleteOnRemove()) {
1514 item->deleteLater();
1515 } else {
1516 item->setParentItem(attached ? attached->originalParent() : nullptr);
1517 }
1518
1519 // Don't notify if we removed from m_disappearingItems
1520 if (index >= 0) {
1521 Q_EMIT contentChildrenChanged();
1522 Q_EMIT itemRemoved(item);
1523 }
1524
1525 return item;
1526}
1527
1528QString ColumnView::savedState()
1529{
1530 int i = 0;
1531 QJsonObject obj;
1532 for (auto it = m_state.constBegin(); it != m_state.constEnd(); it++) {
1533 obj.insert(key: QString::number(it.key()), value: it.value());
1534 }
1535 for (QQuickItem *item : std::as_const(t&: m_contentItem->m_items)) {
1536 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
1537 if (!attached->interactiveResizeEnabled()) {
1538 ++i;
1539 continue;
1540 }
1541
1542 obj.insert(key: QString::number(i), value: attached->preferredWidth());
1543 m_state[i] = attached->preferredWidth();
1544 ++i;
1545 }
1546
1547 QJsonDocument doc(obj);
1548
1549 return QString::fromUtf8(ba: doc.toJson(format: QJsonDocument::Compact));
1550}
1551
1552void ColumnView::setSavedState(const QString &state)
1553{
1554 QJsonParseError parseError;
1555 QJsonDocument doc = QJsonDocument::fromJson(json: state.toUtf8(), error: &parseError);
1556 if (parseError.error != QJsonParseError::NoError) {
1557 qCWarning(KirigamiLayoutsLog) << "Error reading state:" << parseError.errorString();
1558 return;
1559 }
1560
1561 if (!doc.isObject()) {
1562 qCWarning(KirigamiLayoutsLog) << "Error reading state: Not a JSon Object";
1563 return;
1564 }
1565
1566 QJsonObject obj = doc.object();
1567
1568 if (obj.size() == 0) {
1569 qCWarning(KirigamiLayoutsLog) << "Error reading state: Empty JSon Object";
1570 return;
1571 }
1572
1573 for (auto it = obj.constBegin(); it != obj.constEnd(); it++) {
1574 bool ok;
1575 int index = it.key().toInt(ok: &ok);
1576 if (!ok || index < 0) {
1577 qCWarning(KirigamiLayoutsLog) << "Key" << it.key() << "is not a positive integer";
1578 continue;
1579 }
1580 qreal value = it.value().toDouble();
1581 if (!it.value().isDouble() || value <= 0) {
1582 qCWarning(KirigamiLayoutsLog) << "Value for key" << it.key() << "is not a positive number:" << it.value();
1583 continue;
1584 }
1585
1586 m_state[index] = value;
1587
1588 if (index >= m_contentItem->m_items.count()) {
1589 continue;
1590 }
1591
1592 QQuickItem *item = m_contentItem->m_items[index];
1593 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
1594 if (!attached->interactiveResizeEnabled()) {
1595 continue;
1596 }
1597
1598 attached->setPreferredWidth(value);
1599 }
1600
1601 polish();
1602
1603 Q_EMIT savedStateChanged();
1604}
1605
1606QQuickItem *ColumnView::removeItem(const int index)
1607{
1608 if (m_contentItem->m_items.isEmpty() || index < 0 || index >= count()) {
1609 return nullptr;
1610 } else {
1611 return removeItem(item: m_contentItem->m_items[index]);
1612 }
1613}
1614
1615QQuickItem *ColumnView::removeItem(const QVariant &item)
1616{
1617 if (item.canConvert<QQuickItem *>()) {
1618 return removeItem(item: item.value<QQuickItem *>());
1619 } else if (item.canConvert<int>()) {
1620 return removeItem(index: item.toInt());
1621 } else {
1622 return nullptr;
1623 }
1624}
1625
1626QQuickItem *ColumnView::get(int index)
1627{
1628 if (index < 0 || index > m_contentItem->m_items.count() - 1) {
1629 return nullptr;
1630 }
1631
1632 return m_contentItem->m_items.value(i: index);
1633}
1634
1635QQuickItem *ColumnView::pop(const QVariant &item)
1636{
1637 if (item.canConvert<QQuickItem *>()) {
1638 return pop(item: item.value<QQuickItem *>());
1639 } else if (item.canConvert<int>()) {
1640 return pop(index: item.toInt());
1641 } else if (item.isNull()) {
1642 return pop();
1643 }
1644 return nullptr;
1645}
1646QQuickItem *ColumnView::pop(QQuickItem *item)
1647{
1648 QQuickItem *removed = nullptr;
1649 if (count() == 0) {
1650 return nullptr;
1651 }
1652
1653 QList<QQuickItem *> items;
1654 // Take all the items that are in the list after the item parameter
1655 for (auto it = m_contentItem->m_items.crbegin(); it != m_contentItem->m_items.crend(); ++it) {
1656 if ((*it) == item) {
1657 break;
1658 }
1659 items << *it;
1660 }
1661 // Remove immediately all but one
1662 while (items.count() > 1) {
1663 removed = removeItem(item: items.last());
1664 items.pop_back();
1665 }
1666
1667 // The last one will do a pop animation
1668 if (!removed) {
1669 removed = m_contentItem->m_items.last();
1670 }
1671
1672 Q_ASSERT(items.count() <= 1);
1673 if (items.count() == 1) {
1674 // call pop() which will do the transition if needed
1675 pop();
1676 }
1677 return removed;
1678}
1679
1680QQuickItem *ColumnView::pop(const int index)
1681{
1682 if (index >= 0 && index < count() - 1) {
1683 return pop(item: m_contentItem->m_items.at(i: index));
1684 } else if (index == -1) {
1685 return pop(item: nullptr);
1686 }
1687 return nullptr;
1688}
1689
1690QQuickItem *ColumnView::pop()
1691{
1692 if (count() == 0) {
1693 return nullptr;
1694 }
1695
1696 if (m_contentItem->m_columnResizeMode != ColumnView::SingleColumn || currentIndex() < count() - 1) {
1697 return removeItem(index: count() - 1);
1698 }
1699
1700 QQuickItem *item = get(index: count() - 1);
1701 m_contentItem->m_disappearingItems.append(t: item);
1702 m_contentItem->m_items.removeAll(t: item);
1703 // Count - 1 now is the previous item as we already removed this from m_items
1704 // The item in m_disappearingItems will be definitely removed/deleted as soon the animation stops
1705 setCurrentIndex(count() - 1);
1706 Q_EMIT countChanged();
1707 Q_EMIT contentChildrenChanged();
1708 Q_EMIT itemRemoved(item);
1709 return item;
1710}
1711
1712void ColumnView::clear()
1713{
1714 // Don't do an iterator on a list that gets progressively destroyed, treat it as a stack
1715 while (!m_contentItem->m_items.isEmpty()) {
1716 QQuickItem *item = m_contentItem->m_items.first();
1717 removeItem(item);
1718 }
1719
1720 m_contentItem->m_items.clear();
1721 Q_EMIT contentChildrenChanged();
1722}
1723
1724bool ColumnView::containsItem(QQuickItem *item)
1725{
1726 return m_contentItem->m_items.contains(t: item);
1727}
1728
1729QQuickItem *ColumnView::itemAt(qreal x, qreal y)
1730{
1731 return m_contentItem->childAt(x, y);
1732}
1733
1734ColumnViewAttached *ColumnView::qmlAttachedProperties(QObject *object)
1735{
1736 return new ColumnViewAttached(object);
1737}
1738
1739void ColumnView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1740{
1741 m_contentItem->setY(m_topPadding);
1742 m_contentItem->setHeight(newGeometry.height() - m_topPadding - m_bottomPadding);
1743 m_contentItem->m_shouldAnimate = false;
1744 polish();
1745
1746 m_contentItem->updateVisibleItems();
1747 QQuickItem::geometryChange(newGeometry, oldGeometry);
1748}
1749
1750bool ColumnView::childMouseEventFilter(QQuickItem *item, QEvent *event)
1751{
1752 if (!m_interactive || item == m_contentItem) {
1753 return QQuickItem::childMouseEventFilter(item, event);
1754 }
1755
1756 switch (event->type()) {
1757 case QEvent::MouseButtonPress: {
1758 // On press, we set the current index of the view to the root item,
1759 // this needs to be done on both mouse and touch input
1760 QQuickItem *candidateItem = item;
1761 while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1762 candidateItem = candidateItem->parentItem();
1763 }
1764 if (int idx = m_contentItem->m_items.indexOf(t: candidateItem); idx >= 0 && candidateItem->parentItem() == m_contentItem) {
1765 setCurrentIndex(idx);
1766 }
1767 event->setAccepted(false);
1768 break;
1769 }
1770 case QEvent::TouchBegin: {
1771 QTouchEvent *te = static_cast<QTouchEvent *>(event);
1772
1773 if (te->pointCount() != 1) {
1774 return false;
1775 }
1776
1777 m_verticalScrollIntercepted = false;
1778 // On press, we set the current index of the view to the root item
1779 QQuickItem *candidateItem = item;
1780 while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1781 candidateItem = candidateItem->parentItem();
1782 }
1783 if (int idx = m_contentItem->m_items.indexOf(t: candidateItem); idx >= 0 && candidateItem->parentItem() == m_contentItem) {
1784 setCurrentIndex(idx);
1785 }
1786
1787 // m_acceptsMouse is a noop now
1788
1789 m_contentItem->m_slideAnim->stop();
1790 if (item->property(name: "preventStealing").toBool()) {
1791 m_contentItem->snapToItem();
1792 return false;
1793 }
1794
1795 te->setAccepted(false);
1796
1797 break;
1798 }
1799 case QEvent::TouchUpdate: {
1800 QTouchEvent *te = static_cast<QTouchEvent *>(event);
1801
1802 if (te->pointCount() != 1) {
1803 return false;
1804 }
1805
1806 const QPointF pressPos = mapFromItem(item, point: te->points().first().pressPosition());
1807 const QPointF lastPos = mapFromItem(item, point: te->points().first().lastPosition());
1808 const QPointF pos = mapFromItem(item, point: te->points().first().position());
1809
1810 m_verticalScrollIntercepted = m_verticalScrollIntercepted || std::abs(x: pos.y() - pressPos.y()) > qApp->styleHints()->startDragDistance() * 3;
1811
1812 if (m_verticalScrollIntercepted) {
1813 m_contentItem->snapToItem();
1814 m_dragging = false;
1815 return false;
1816 }
1817
1818 QQuickItem *candidateItem = item;
1819 while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1820 candidateItem = candidateItem->parentItem();
1821 }
1822 if (candidateItem->parentItem() == m_contentItem) {
1823 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: candidateItem, create: true));
1824 if (attached->preventStealing()) {
1825 return false;
1826 }
1827 }
1828
1829 {
1830 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: candidateItem, create: true));
1831
1832 ScrollIntentionEvent scrollIntentionEvent;
1833 scrollIntentionEvent.delta = pos - lastPos;
1834
1835 Q_EMIT attached->scrollIntention(event: &scrollIntentionEvent);
1836
1837 if (scrollIntentionEvent.accepted) {
1838 m_verticalScrollIntercepted = true;
1839 event->setAccepted(true);
1840 }
1841 }
1842
1843 if (item->keepTouchGrab() || item->property(name: "preventStealing").toBool()) {
1844 m_contentItem->snapToItem();
1845 m_dragging = false;
1846 return false;
1847 }
1848
1849 const bool wasDragging = m_dragging;
1850 // If a drag happened, start to steal all events, use startDragDistance * 2 to give time to widgets to take the mouse grab by themselves
1851 m_dragging = !m_verticalScrollIntercepted && std::abs(x: pos.x() - pressPos.x()) > qApp->styleHints()->startDragDistance() * 3;
1852
1853 if (m_dragging != wasDragging) {
1854 m_moving = true;
1855 Q_EMIT movingChanged();
1856 Q_EMIT draggingChanged();
1857 }
1858
1859 m_contentItem->m_lastDragDelta = pos.x() - lastPos.x();
1860
1861 if (m_dragging) {
1862 m_contentItem->setBoundedX(m_contentItem->x() + m_contentItem->m_lastDragDelta);
1863 }
1864
1865 te->setAccepted(m_dragging);
1866
1867 return m_dragging;
1868 }
1869 case QEvent::TouchEnd:
1870 case QEvent::TouchCancel: {
1871 QTouchEvent *te = static_cast<QTouchEvent *>(event);
1872 if (item->property(name: "preventStealing").toBool()) {
1873 return false;
1874 }
1875
1876 m_verticalScrollIntercepted = false;
1877 // if a drag happened, don't pass the event
1878 const bool block = m_dragging;
1879
1880 m_contentItem->snapToItem();
1881
1882 if (m_dragging) {
1883 m_contentItem->m_lastDragDelta = 0;
1884 m_dragging = false;
1885 Q_EMIT draggingChanged();
1886 }
1887
1888 event->accept();
1889
1890 te->setAccepted(block);
1891 return block;
1892 }
1893 case QEvent::MouseButtonRelease: {
1894 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1895 if (me->button() == Qt::BackButton && m_currentIndex > 0) {
1896 setCurrentIndex(m_currentIndex - 1);
1897 me->accept();
1898 return true;
1899 } else if (me->button() == Qt::ForwardButton) {
1900 setCurrentIndex(m_currentIndex + 1);
1901 me->accept();
1902 return true;
1903 }
1904 break;
1905 }
1906 default:
1907 break;
1908 }
1909
1910 return QQuickItem::childMouseEventFilter(item, event);
1911}
1912
1913bool ColumnView::event(QEvent *event)
1914{
1915 if (!m_interactive) {
1916 return QQuickItem::event(event);
1917 }
1918 switch (event->type()) {
1919 case QEvent::TouchBegin: {
1920 QTouchEvent *te = static_cast<QTouchEvent *>(event);
1921
1922 if (te->pointCount() != 1) {
1923 m_contentItem->snapToItem();
1924 m_dragging = false;
1925 }
1926 break;
1927 }
1928 case QEvent::TouchUpdate: {
1929 QTouchEvent *te = static_cast<QTouchEvent *>(event);
1930
1931 if (te->pointCount() != 1) {
1932 m_contentItem->snapToItem();
1933 m_dragging = false;
1934 break;
1935 }
1936
1937 m_dragging = true;
1938
1939 const QEventPoint point = te->points().first();
1940
1941 m_contentItem->m_lastDragDelta = point.position().x() - point.lastPosition().x();
1942 m_contentItem->setBoundedX(m_contentItem->x() + m_contentItem->m_lastDragDelta);
1943 break;
1944 }
1945 case QEvent::TouchEnd:
1946 case QEvent::TouchCancel:
1947 m_contentItem->snapToItem();
1948 m_dragging = false;
1949 break;
1950 default:
1951 break;
1952 }
1953 return QQuickItem::event(event);
1954}
1955
1956void ColumnView::mousePressEvent(QMouseEvent *event)
1957{
1958 if (!m_acceptsMouse && event->source() == Qt::MouseEventNotSynthesized) {
1959 event->setAccepted(false);
1960 return;
1961 }
1962
1963 if (event->button() == Qt::BackButton || event->button() == Qt::ForwardButton) {
1964 event->accept();
1965 }
1966}
1967
1968void ColumnView::mouseReleaseEvent(QMouseEvent *event)
1969{
1970 if (event->button() == Qt::BackButton && m_currentIndex > 0) {
1971 setCurrentIndex(m_currentIndex - 1);
1972 event->accept();
1973 return;
1974 } else if (event->button() == Qt::ForwardButton) {
1975 setCurrentIndex(m_currentIndex + 1);
1976 event->accept();
1977 return;
1978 }
1979}
1980
1981void ColumnView::classBegin()
1982{
1983 auto syncColumnWidth = [this]() {
1984 m_contentItem->m_columnWidth = privateQmlComponentsPoolSelf->instance(engine: qmlEngine(this))->m_units->gridUnit() * 20;
1985 Q_EMIT columnWidthChanged();
1986 };
1987
1988 connect(sender: QmlComponentsPoolSingleton::instance(engine: qmlEngine(this)), signal: &QmlComponentsPool::gridUnitChanged, context: this, slot&: syncColumnWidth);
1989 syncColumnWidth();
1990
1991 auto syncDuration = [this]() {
1992 m_contentItem->m_slideAnim->setDuration(QmlComponentsPoolSingleton::instance(engine: qmlEngine(this))->m_units->veryLongDuration());
1993 Q_EMIT scrollDurationChanged();
1994 };
1995
1996 connect(sender: QmlComponentsPoolSingleton::instance(engine: qmlEngine(this)), signal: &QmlComponentsPool::longDurationChanged, context: this, slot&: syncDuration);
1997 syncDuration();
1998
1999 QQuickItem::classBegin();
2000}
2001
2002void ColumnView::componentComplete()
2003{
2004 m_complete = true;
2005 QQuickItem::componentComplete();
2006}
2007
2008void ColumnView::updatePolish()
2009{
2010 m_contentItem->layoutItems();
2011}
2012
2013void ColumnView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
2014{
2015 switch (change) {
2016 case QQuickItem::ItemChildAddedChange:
2017 if (m_contentItem && value.item != m_contentItem && !value.item->inherits(classname: "QQuickRepeater")) {
2018 addItem(item: value.item);
2019 }
2020 break;
2021 default:
2022 break;
2023 }
2024 QQuickItem::itemChange(change, value);
2025}
2026
2027void ColumnView::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item)
2028{
2029 // This can only be called from QML
2030 ColumnView *view = static_cast<ColumnView *>(prop->object);
2031 if (!view) {
2032 return;
2033 }
2034
2035 view->m_contentItem->m_items.append(t: item);
2036 connect(sender: item, signal: &QObject::destroyed, context: view->m_contentItem, slot: [view, item]() {
2037 view->removeItem(item);
2038 });
2039
2040 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
2041 attached->setOriginalParent(item->parentItem());
2042 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
2043
2044 item->setParentItem(view->m_contentItem);
2045}
2046
2047qsizetype ColumnView::contentChildren_count(QQmlListProperty<QQuickItem> *prop)
2048{
2049 ColumnView *view = static_cast<ColumnView *>(prop->object);
2050 if (!view) {
2051 return 0;
2052 }
2053
2054 return view->m_contentItem->m_items.count();
2055}
2056
2057QQuickItem *ColumnView::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index)
2058{
2059 ColumnView *view = static_cast<ColumnView *>(prop->object);
2060 if (!view) {
2061 return nullptr;
2062 }
2063
2064 if (index < 0 || index >= view->m_contentItem->m_items.count()) {
2065 return nullptr;
2066 }
2067 return view->m_contentItem->m_items.value(i: index);
2068}
2069
2070void ColumnView::contentChildren_clear(QQmlListProperty<QQuickItem> *prop)
2071{
2072 ColumnView *view = static_cast<ColumnView *>(prop->object);
2073 if (!view) {
2074 return;
2075 }
2076
2077 return view->m_contentItem->m_items.clear();
2078}
2079
2080QQmlListProperty<QQuickItem> ColumnView::contentChildren()
2081{
2082 return QQmlListProperty<QQuickItem>(this, //
2083 nullptr,
2084 contentChildren_append,
2085 contentChildren_count,
2086 contentChildren_at,
2087 contentChildren_clear);
2088}
2089
2090void ColumnView::contentData_append(QQmlListProperty<QObject> *prop, QObject *object)
2091{
2092 ColumnView *view = static_cast<ColumnView *>(prop->object);
2093 if (!view) {
2094 return;
2095 }
2096
2097 view->m_contentData.append(t: object);
2098 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
2099 // exclude repeaters from layout
2100 if (item && item->inherits(classname: "QQuickRepeater")) {
2101 item->setParentItem(view);
2102
2103 connect(sender: item, SIGNAL(modelChanged()), receiver: view->m_contentItem, SLOT(updateRepeaterModel()));
2104
2105 } else if (item) {
2106 view->m_contentItem->m_items.append(t: item);
2107 connect(sender: item, signal: &QObject::destroyed, context: view->m_contentItem, slot: [view, item]() {
2108 view->removeItem(item);
2109 });
2110
2111 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(object: qmlAttachedPropertiesObject<ColumnView>(obj: item, create: true));
2112 attached->setOriginalParent(item->parentItem());
2113 attached->setShouldDeleteOnRemove(view->m_complete && !item->parentItem() && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
2114
2115 item->setParentItem(view->m_contentItem);
2116
2117 } else {
2118 object->setParent(view);
2119 }
2120}
2121
2122qsizetype ColumnView::contentData_count(QQmlListProperty<QObject> *prop)
2123{
2124 ColumnView *view = static_cast<ColumnView *>(prop->object);
2125 if (!view) {
2126 return 0;
2127 }
2128
2129 return view->m_contentData.count();
2130}
2131
2132QObject *ColumnView::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
2133{
2134 ColumnView *view = static_cast<ColumnView *>(prop->object);
2135 if (!view) {
2136 return nullptr;
2137 }
2138
2139 if (index < 0 || index >= view->m_contentData.count()) {
2140 return nullptr;
2141 }
2142 return view->m_contentData.value(i: index);
2143}
2144
2145void ColumnView::contentData_clear(QQmlListProperty<QObject> *prop)
2146{
2147 ColumnView *view = static_cast<ColumnView *>(prop->object);
2148 if (!view) {
2149 return;
2150 }
2151
2152 return view->m_contentData.clear();
2153}
2154
2155QQmlListProperty<QObject> ColumnView::contentData()
2156{
2157 return QQmlListProperty<QObject>(this, //
2158 nullptr,
2159 contentData_append,
2160 contentData_count,
2161 contentData_at,
2162 contentData_clear);
2163}
2164
2165#include "moc_columnview.cpp"
2166#include "moc_columnview_p.cpp"
2167

source code of kirigami/src/layouts/columnview.cpp