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

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