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 <QPropertyAnimation>
14 #include <QQmlComponent>
15 #include <QQmlContext>
16 #include <QQmlEngine>
17 #include <QStyleHints>
18 
19 #include "units.h"
20 
21 QHash<QObject *, ColumnViewAttached *> ColumnView::m_attachedObjects = QHash<QObject *, ColumnViewAttached *>();
22 
23 class QmlComponentsPoolSingleton
24 {
25 public:
QmlComponentsPoolSingleton()26     QmlComponentsPoolSingleton()
27     {
28     }
29     static QmlComponentsPool *instance(QQmlEngine *engine);
30 
31 private:
32     QHash<QQmlEngine *, QmlComponentsPool *> m_instances;
33 };
34 
Q_GLOBAL_STATIC(QmlComponentsPoolSingleton,privateQmlComponentsPoolSelf)35 Q_GLOBAL_STATIC(QmlComponentsPoolSingleton, privateQmlComponentsPoolSelf)
36 
37 QmlComponentsPool *QmlComponentsPoolSingleton::instance(QQmlEngine *engine)
38 {
39     Q_ASSERT(engine);
40     auto componentPool = privateQmlComponentsPoolSelf->m_instances.value(engine);
41 
42     if (componentPool) {
43         return componentPool;
44     }
45 
46     componentPool = new QmlComponentsPool(engine);
47 
48     const auto removePool = [engine]() {
49         // NB: do not derefence engine. it may be dangling already!
50         if (privateQmlComponentsPoolSelf) {
51             privateQmlComponentsPoolSelf->m_instances.remove(engine);
52         }
53     };
54     QObject::connect(engine, &QObject::destroyed, engine, removePool);
55     QObject::connect(componentPool, &QObject::destroyed, componentPool, removePool);
56 
57     privateQmlComponentsPoolSelf->m_instances[engine] = componentPool;
58     return componentPool;
59 }
60 
QmlComponentsPool(QQmlEngine * engine)61 QmlComponentsPool::QmlComponentsPool(QQmlEngine *engine)
62     : QObject(engine)
63 {
64     QQmlComponent *component = new QQmlComponent(engine, this);
65 
66     /* clang-format off */
67     component->setData(QByteArrayLiteral(R"(
68 import QtQuick 2.7
69 import org.kde.kirigami 2.7 as Kirigami
70 
71 QtObject {
72     id: root
73     readonly property Kirigami.Units units: Kirigami.Units
74 
75     readonly property Component separator: Kirigami.Separator {
76         property Item column
77 
78         visible: column.Kirigami.ColumnView.view && column.Kirigami.ColumnView.view.contentX < column.x
79         anchors.top: column.top
80         anchors.bottom: column.bottom
81     }
82 
83     readonly property Component rightSeparator: Kirigami.Separator {
84         property Item column
85 
86         anchors.top: column.top
87         anchors.right: column.right
88         anchors.bottom: column.bottom
89     }
90 }
91 )"), QUrl(QStringLiteral("columnview.cpp")));
92     /* clang-format on */
93 
94     m_instance = component->create();
95     // qCWarning(KirigamiLog)<<component->errors();
96     Q_ASSERT(m_instance);
97 
98     m_separatorComponent = m_instance->property("separator").value<QQmlComponent *>();
99     Q_ASSERT(m_separatorComponent);
100 
101     m_rightSeparatorComponent = m_instance->property("rightSeparator").value<QQmlComponent *>();
102     Q_ASSERT(m_rightSeparatorComponent);
103 
104     m_units = engine->singletonInstance<Kirigami::Units *>(qmlTypeId("org.kde.kirigami", 2, 0, "Units"));
105     Q_ASSERT(m_units);
106 
107     connect(m_units, &Kirigami::Units::gridUnitChanged, this, &QmlComponentsPool::gridUnitChanged);
108     connect(m_units, &Kirigami::Units::longDurationChanged, this, &QmlComponentsPool::longDurationChanged);
109 }
110 
~QmlComponentsPool()111 QmlComponentsPool::~QmlComponentsPool()
112 {
113 }
114 
115 /////////
116 
ColumnViewAttached(QObject * parent)117 ColumnViewAttached::ColumnViewAttached(QObject *parent)
118     : QObject(parent)
119 {
120 }
121 
~ColumnViewAttached()122 ColumnViewAttached::~ColumnViewAttached()
123 {
124 }
125 
setIndex(int index)126 void ColumnViewAttached::setIndex(int index)
127 {
128     if (!m_customFillWidth && m_view) {
129         const bool oldFillWidth = m_fillWidth;
130         m_fillWidth = index == m_view->count() - 1;
131         if (oldFillWidth != m_fillWidth) {
132             Q_EMIT fillWidthChanged();
133         }
134     }
135 
136     if (index == m_index) {
137         return;
138     }
139 
140     m_index = index;
141     Q_EMIT indexChanged();
142 }
143 
index() const144 int ColumnViewAttached::index() const
145 {
146     return m_index;
147 }
148 
setFillWidth(bool fill)149 void ColumnViewAttached::setFillWidth(bool fill)
150 {
151     if (m_view) {
152         disconnect(m_view.data(), &ColumnView::countChanged, this, nullptr);
153     }
154     m_customFillWidth = true;
155 
156     if (fill == m_fillWidth) {
157         return;
158     }
159 
160     m_fillWidth = fill;
161     Q_EMIT fillWidthChanged();
162 
163     if (m_view) {
164         m_view->polish();
165     }
166 }
167 
fillWidth() const168 bool ColumnViewAttached::fillWidth() const
169 {
170     return m_fillWidth;
171 }
172 
reservedSpace() const173 qreal ColumnViewAttached::reservedSpace() const
174 {
175     return m_reservedSpace;
176 }
177 
setReservedSpace(qreal space)178 void ColumnViewAttached::setReservedSpace(qreal space)
179 {
180     if (m_view) {
181         disconnect(m_view.data(), &ColumnView::columnWidthChanged, this, nullptr);
182     }
183     m_customReservedSpace = true;
184 
185     if (qFuzzyCompare(space, m_reservedSpace)) {
186         return;
187     }
188 
189     m_reservedSpace = space;
190     Q_EMIT reservedSpaceChanged();
191 
192     if (m_view) {
193         m_view->polish();
194     }
195 }
196 
view()197 ColumnView *ColumnViewAttached::view()
198 {
199     return m_view;
200 }
201 
setView(ColumnView * view)202 void ColumnViewAttached::setView(ColumnView *view)
203 {
204     if (view == m_view) {
205         return;
206     }
207 
208     if (m_view) {
209         disconnect(m_view.data(), nullptr, this, nullptr);
210     }
211     m_view = view;
212 
213     if (!m_customFillWidth && m_view) {
214         m_fillWidth = m_index == m_view->count() - 1;
215         connect(m_view.data(), &ColumnView::countChanged, this, [this]() {
216             m_fillWidth = m_index == m_view->count() - 1;
217             Q_EMIT fillWidthChanged();
218         });
219     }
220     if (!m_customReservedSpace && m_view) {
221         m_reservedSpace = m_view->columnWidth();
222         connect(m_view.data(), &ColumnView::columnWidthChanged, this, [this]() {
223             m_reservedSpace = m_view->columnWidth();
224             Q_EMIT reservedSpaceChanged();
225         });
226     }
227 
228     Q_EMIT viewChanged();
229 }
230 
originalParent() const231 QQuickItem *ColumnViewAttached::originalParent() const
232 {
233     return m_originalParent;
234 }
235 
setOriginalParent(QQuickItem * parent)236 void ColumnViewAttached::setOriginalParent(QQuickItem *parent)
237 {
238     m_originalParent = parent;
239 }
240 
shouldDeleteOnRemove() const241 bool ColumnViewAttached::shouldDeleteOnRemove() const
242 {
243     return m_shouldDeleteOnRemove;
244 }
245 
setShouldDeleteOnRemove(bool del)246 void ColumnViewAttached::setShouldDeleteOnRemove(bool del)
247 {
248     m_shouldDeleteOnRemove = del;
249 }
250 
preventStealing() const251 bool ColumnViewAttached::preventStealing() const
252 {
253     return m_preventStealing;
254 }
255 
setPreventStealing(bool prevent)256 void ColumnViewAttached::setPreventStealing(bool prevent)
257 {
258     if (prevent == m_preventStealing) {
259         return;
260     }
261 
262     m_preventStealing = prevent;
263     Q_EMIT preventStealingChanged();
264 }
265 
isPinned() const266 bool ColumnViewAttached::isPinned() const
267 {
268     return m_pinned;
269 }
270 
setPinned(bool pinned)271 void ColumnViewAttached::setPinned(bool pinned)
272 {
273     if (pinned == m_pinned) {
274         return;
275     }
276 
277     m_pinned = pinned;
278 
279     Q_EMIT pinnedChanged();
280 
281     if (m_view) {
282         m_view->polish();
283     }
284 }
285 
inViewport() const286 bool ColumnViewAttached::inViewport() const
287 {
288     return m_inViewport;
289 }
290 
setInViewport(bool inViewport)291 void ColumnViewAttached::setInViewport(bool inViewport)
292 {
293     if (m_inViewport == inViewport) {
294         return;
295     }
296 
297     m_inViewport = inViewport;
298 
299     Q_EMIT inViewportChanged();
300 }
301 
302 /////////
303 
ContentItem(ColumnView * parent)304 ContentItem::ContentItem(ColumnView *parent)
305     : QQuickItem(parent)
306     , m_view(parent)
307 {
308     setFlags(flags() | ItemIsFocusScope);
309     m_slideAnim = new QPropertyAnimation(this);
310     m_slideAnim->setTargetObject(this);
311     m_slideAnim->setPropertyName("x");
312     // NOTE: the duration will be taken from kirigami units upon classBegin
313     m_slideAnim->setDuration(0);
314     m_slideAnim->setEasingCurve(QEasingCurve(QEasingCurve::InOutQuad));
315     connect(m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
316         if (!m_view->currentItem()) {
317             m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
318         } else {
319             QRectF mapped = m_view->currentItem()->mapRectToItem(m_view, QRectF(QPointF(0, 0), m_view->currentItem()->size()));
320             if (!QRectF(QPointF(0, 0), m_view->size()).intersects(mapped)) {
321                 m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
322             }
323         }
324     });
325 
326     connect(this, &QQuickItem::xChanged, this, &ContentItem::layoutPinnedItems);
327 }
328 
~ContentItem()329 ContentItem::~ContentItem()
330 {
331 }
332 
setBoundedX(qreal x)333 void ContentItem::setBoundedX(qreal x)
334 {
335     if (!parentItem()) {
336         return;
337     }
338     m_slideAnim->stop();
339     setX(qRound(qBound(qMin(0.0, -width() + parentItem()->width()), x, 0.0)));
340 }
341 
animateX(qreal newX)342 void ContentItem::animateX(qreal newX)
343 {
344     if (!parentItem()) {
345         return;
346     }
347 
348     const qreal to = qRound(qBound(qMin(0.0, -width() + parentItem()->width()), newX, 0.0));
349 
350     m_slideAnim->stop();
351     m_slideAnim->setStartValue(x());
352     m_slideAnim->setEndValue(to);
353     m_slideAnim->start();
354 }
355 
snapToItem()356 void ContentItem::snapToItem()
357 {
358     QQuickItem *firstItem = childAt(viewportLeft(), 0);
359     if (!firstItem) {
360         return;
361     }
362     QQuickItem *nextItem = childAt(firstItem->x() + firstItem->width() + 1, 0);
363 
364     // need to make the last item visible?
365     if (nextItem && //
366         ((m_view->dragging() && m_lastDragDelta < 0) //
367          || (!m_view->dragging() //
368              && (width() - viewportRight()) < (viewportLeft() - firstItem->x())))) {
369         m_viewAnchorItem = nextItem;
370         animateX(-nextItem->x() + m_leftPinnedSpace);
371 
372         // The first one found?
373     } else if ((m_view->dragging() && m_lastDragDelta >= 0) //
374                || (!m_view->dragging() && (viewportLeft() <= (firstItem->x() + (firstItem->width() / 2)))) //
375                || !nextItem) {
376         m_viewAnchorItem = firstItem;
377         animateX(-firstItem->x() + m_leftPinnedSpace);
378 
379         // the second?
380     } else {
381         m_viewAnchorItem = nextItem;
382         animateX(-nextItem->x() + m_leftPinnedSpace);
383     }
384 }
385 
viewportLeft() const386 qreal ContentItem::viewportLeft() const
387 {
388     return -x() + m_leftPinnedSpace;
389 }
390 
viewportRight() const391 qreal ContentItem::viewportRight() const
392 {
393     return -x() + m_view->width() - m_rightPinnedSpace;
394 }
395 
childWidth(QQuickItem * child)396 qreal ContentItem::childWidth(QQuickItem *child)
397 {
398     if (!parentItem()) {
399         return 0.0;
400     }
401 
402     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
403 
404     if (m_columnResizeMode == ColumnView::SingleColumn) {
405         return qRound(parentItem()->width());
406 
407     } else if (attached->fillWidth()) {
408         return qRound(qBound(m_columnWidth, (parentItem()->width() - attached->reservedSpace()), parentItem()->width()));
409 
410     } else if (m_columnResizeMode == ColumnView::FixedColumns) {
411         return qRound(qMin(parentItem()->width(), m_columnWidth));
412 
413         // DynamicColumns
414     } else {
415         // TODO:look for Layout size hints
416         qreal width = child->implicitWidth();
417 
418         if (width < 1.0) {
419             width = m_columnWidth;
420         }
421 
422         return qRound(qMin(m_view->width(), width));
423     }
424 }
425 
layoutItems()426 void ContentItem::layoutItems()
427 {
428     setY(m_view->topPadding());
429     setHeight(m_view->height() - m_view->topPadding() - m_view->bottomPadding());
430 
431     qreal implicitWidth = 0;
432     qreal implicitHeight = 0;
433     qreal partialWidth = 0;
434     int i = 0;
435     m_leftPinnedSpace = 0;
436     m_rightPinnedSpace = 0;
437 
438     bool reverse = qApp->layoutDirection() == Qt::RightToLeft;
439     auto it = !reverse ? m_items.begin() : m_items.end();
440     int increment = reverse ? -1 : +1;
441     auto lastPos = reverse ? m_items.begin() : m_items.end();
442 
443     for (; it != lastPos; it += increment) {
444         // for (QQuickItem *child : std::as_const(m_items)) {
445         QQuickItem *child = reverse ? *(it - 1) : *it;
446         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
447 
448         if (child->isVisible()) {
449             if (attached->isPinned() && m_view->columnResizeMode() != ColumnView::SingleColumn) {
450                 QQuickItem *sep = nullptr;
451                 int sepWidth = 0;
452                 if (m_view->separatorVisible()) {
453                     sep = ensureRightSeparator(child);
454                     sepWidth = (sep ? sep->width() : 0);
455                 }
456                 const qreal width = childWidth(child);
457                 child->setSize(QSizeF(width + sepWidth, height()));
458 
459                 child->setPosition(QPointF(qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth), 0.0));
460                 child->setZ(1);
461 
462                 if (partialWidth <= -x()) {
463                     m_leftPinnedSpace = qMax(m_leftPinnedSpace, width);
464                 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
465                     m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
466                 }
467 
468                 partialWidth += width;
469 
470             } else {
471                 child->setSize(QSizeF(childWidth(child), height()));
472 
473                 auto it = m_rightSeparators.find(child);
474                 if (it != m_rightSeparators.end()) {
475                     it.value()->deleteLater();
476                     m_rightSeparators.erase(it);
477                 }
478                 child->setPosition(QPointF(partialWidth, 0.0));
479                 child->setZ(0);
480 
481                 partialWidth += child->width();
482             }
483         }
484 
485         if (reverse) {
486             attached->setIndex(m_items.count() - (++i));
487         } else {
488             attached->setIndex(i++);
489         }
490 
491         implicitWidth += child->implicitWidth();
492 
493         implicitHeight = qMax(implicitHeight, child->implicitHeight());
494     }
495 
496     setWidth(partialWidth);
497 
498     setImplicitWidth(implicitWidth);
499     setImplicitHeight(implicitHeight);
500 
501     m_view->setImplicitWidth(implicitWidth);
502     m_view->setImplicitHeight(implicitHeight + m_view->topPadding() + m_view->bottomPadding());
503 
504     const qreal newContentX = m_viewAnchorItem ? -m_viewAnchorItem->x() : 0.0;
505     if (m_shouldAnimate) {
506         animateX(newContentX);
507     } else {
508         setBoundedX(newContentX);
509     }
510 
511     updateVisibleItems();
512 }
513 
layoutPinnedItems()514 void ContentItem::layoutPinnedItems()
515 {
516     if (m_view->columnResizeMode() == ColumnView::SingleColumn) {
517         return;
518     }
519 
520     qreal partialWidth = 0;
521     m_leftPinnedSpace = 0;
522     m_rightPinnedSpace = 0;
523 
524     for (QQuickItem *child : std::as_const(m_items)) {
525         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
526 
527         if (child->isVisible()) {
528             if (attached->isPinned()) {
529                 QQuickItem *sep = nullptr;
530                 int sepWidth = 0;
531                 if (m_view->separatorVisible()) {
532                     sep = ensureRightSeparator(child);
533                     sepWidth = (sep ? sep->width() : 0);
534                 }
535 
536                 child->setPosition(QPointF(qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth), 0.0));
537 
538                 if (partialWidth <= -x()) {
539                     m_leftPinnedSpace = qMax(m_leftPinnedSpace, child->width() - sepWidth);
540                 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
541                     m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
542                 }
543             }
544 
545             partialWidth += child->width();
546         }
547     }
548 }
549 
updateVisibleItems()550 void ContentItem::updateVisibleItems()
551 {
552     QList<QObject *> newItems;
553 
554     for (auto *item : std::as_const(m_items)) {
555         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
556 
557         if (item->isVisible() && item->x() + x() < m_view->width() && item->x() + item->width() + x() > 0) {
558             newItems << item;
559             connect(item, &QObject::destroyed, this, [this, item] {
560                 m_visibleItems.removeAll(item);
561             });
562             attached->setInViewport(true);
563         } else {
564             attached->setInViewport(false);
565         }
566     }
567 
568     for (auto *item : std::as_const(m_visibleItems)) {
569         disconnect(item, &QObject::destroyed, this, nullptr);
570     }
571     const QQuickItem *oldFirstVisibleItem = m_visibleItems.isEmpty() ? nullptr : qobject_cast<QQuickItem *>(m_visibleItems.first());
572     const QQuickItem *oldLastVisibleItem = m_visibleItems.isEmpty() ? nullptr : qobject_cast<QQuickItem *>(m_visibleItems.last());
573 
574     if (newItems != m_visibleItems) {
575         m_visibleItems = newItems;
576         Q_EMIT m_view->visibleItemsChanged();
577         if (!newItems.isEmpty() && m_visibleItems.first() != oldFirstVisibleItem) {
578             Q_EMIT m_view->firstVisibleItemChanged();
579         }
580         if (!newItems.isEmpty() && m_visibleItems.last() != oldLastVisibleItem) {
581             Q_EMIT m_view->lastVisibleItemChanged();
582         }
583     }
584 }
585 
forgetItem(QQuickItem * item)586 void ContentItem::forgetItem(QQuickItem *item)
587 {
588     if (!m_items.contains(item)) {
589         return;
590     }
591 
592     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
593     attached->setView(nullptr);
594     attached->setIndex(-1);
595 
596     disconnect(attached, nullptr, this, nullptr);
597     disconnect(item, nullptr, this, nullptr);
598     disconnect(item, nullptr, m_view, nullptr);
599 
600     QQuickItem *separatorItem = m_separators.take(item);
601     if (separatorItem) {
602         separatorItem->deleteLater();
603     }
604     separatorItem = m_rightSeparators.take(item);
605     if (separatorItem) {
606         separatorItem->deleteLater();
607     }
608 
609     const int index = m_items.indexOf(item);
610     m_items.removeAll(item);
611     disconnect(item, &QObject::destroyed, this, nullptr);
612     updateVisibleItems();
613     m_shouldAnimate = true;
614     m_view->polish();
615 
616     if (index <= m_view->currentIndex()) {
617         m_view->setCurrentIndex(qBound(0, index - 1, m_items.count() - 1));
618     }
619     Q_EMIT m_view->countChanged();
620 }
621 
ensureSeparator(QQuickItem * item)622 QQuickItem *ContentItem::ensureSeparator(QQuickItem *item)
623 {
624     QQuickItem *separatorItem = m_separators.value(item);
625 
626     if (!separatorItem) {
627         separatorItem = qobject_cast<QQuickItem *>(
628             privateQmlComponentsPoolSelf->instance(qmlEngine(item))->m_separatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
629         if (separatorItem) {
630             separatorItem->setParentItem(item);
631             separatorItem->setZ(9999);
632             separatorItem->setProperty("column", QVariant::fromValue(item));
633             QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_separatorComponent->completeCreate();
634             m_separators[item] = separatorItem;
635         }
636     }
637 
638     return separatorItem;
639 }
640 
ensureRightSeparator(QQuickItem * item)641 QQuickItem *ContentItem::ensureRightSeparator(QQuickItem *item)
642 {
643     QQuickItem *separatorItem = m_rightSeparators.value(item);
644 
645     if (!separatorItem) {
646         separatorItem = qobject_cast<QQuickItem *>(
647             QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_rightSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
648         if (separatorItem) {
649             separatorItem->setParentItem(item);
650             separatorItem->setZ(9999);
651             separatorItem->setProperty("column", QVariant::fromValue(item));
652             QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_rightSeparatorComponent->completeCreate();
653             m_rightSeparators[item] = separatorItem;
654         }
655     }
656 
657     return separatorItem;
658 }
659 
itemChange(QQuickItem::ItemChange change,const QQuickItem::ItemChangeData & value)660 void ContentItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
661 {
662     switch (change) {
663     case QQuickItem::ItemChildAddedChange: {
664         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(value.item, true));
665         attached->setView(m_view);
666 
667         // connect(attached, &ColumnViewAttached::fillWidthChanged, m_view, &ColumnView::polish);
668         connect(attached, &ColumnViewAttached::fillWidthChanged, this, [this] {
669             m_view->polish();
670         });
671         connect(attached, &ColumnViewAttached::reservedSpaceChanged, m_view, &ColumnView::polish);
672 
673         value.item->setVisible(true);
674 
675         if (!m_items.contains(value.item)) {
676             connect(value.item, &QQuickItem::widthChanged, m_view, &ColumnView::polish);
677             QQuickItem *item = value.item;
678             m_items << item;
679             connect(item, &QObject::destroyed, this, [this, item]() {
680                 m_view->removeItem(item);
681             });
682         }
683 
684         if (m_view->separatorVisible()) {
685             ensureSeparator(value.item);
686         }
687 
688         m_shouldAnimate = true;
689         m_view->polish();
690         Q_EMIT m_view->countChanged();
691         break;
692     }
693     case QQuickItem::ItemChildRemovedChange: {
694         forgetItem(value.item);
695         break;
696     }
697     case QQuickItem::ItemVisibleHasChanged:
698         updateVisibleItems();
699         if (value.boolValue) {
700             m_view->polish();
701         }
702         break;
703     default:
704         break;
705     }
706     QQuickItem::itemChange(change, value);
707 }
708 
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)709 void ContentItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
710 {
711     updateVisibleItems();
712     QQuickItem::geometryChanged(newGeometry, oldGeometry);
713 }
714 
syncItemsOrder()715 void ContentItem::syncItemsOrder()
716 {
717     if (m_items == childItems()) {
718         return;
719     }
720 
721     m_items = childItems();
722     // NOTE: polish() here sometimes gets indefinitely delayed and items changing order isn't seen
723     layoutItems();
724 }
725 
updateRepeaterModel()726 void ContentItem::updateRepeaterModel()
727 {
728     if (!sender()) {
729         return;
730     }
731 
732     QObject *modelObj = sender()->property("model").value<QObject *>();
733 
734     if (!modelObj) {
735         m_models.remove(sender());
736         return;
737     }
738 
739     if (m_models[sender()]) {
740         disconnect(m_models[sender()], nullptr, this, nullptr);
741     }
742 
743     m_models[sender()] = modelObj;
744 
745     QAbstractItemModel *qaim = qobject_cast<QAbstractItemModel *>(modelObj);
746 
747     if (qaim) {
748         connect(qaim, &QAbstractItemModel::rowsMoved, this, &ContentItem::syncItemsOrder);
749 
750     } else {
751         connect(modelObj, SIGNAL(childrenChanged()), this, SLOT(syncItemsOrder()));
752     }
753 }
754 
ColumnView(QQuickItem * parent)755 ColumnView::ColumnView(QQuickItem *parent)
756     : QQuickItem(parent)
757     , m_contentItem(nullptr)
758 {
759     // NOTE: this is to *not* trigger itemChange
760     m_contentItem = new ContentItem(this);
761     setAcceptedMouseButtons(Qt::LeftButton | Qt::BackButton | Qt::ForwardButton);
762     setAcceptTouchEvents(false); // Relies on synthetized mouse events
763     setFiltersChildMouseEvents(true);
764 
765     connect(m_contentItem->m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
766         m_moving = false;
767         Q_EMIT movingChanged();
768     });
769     connect(m_contentItem, &ContentItem::widthChanged, this, &ColumnView::contentWidthChanged);
770     connect(m_contentItem, &ContentItem::xChanged, this, &ColumnView::contentXChanged);
771 
772     connect(this, &ColumnView::activeFocusChanged, this, [this]() {
773         if (hasActiveFocus() && m_currentItem) {
774             m_currentItem->forceActiveFocus();
775         }
776     });
777     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(this, true));
778     attached->setView(this);
779     attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(m_contentItem, true));
780     attached->setView(this);
781 }
782 
~ColumnView()783 ColumnView::~ColumnView()
784 {
785 }
786 
columnResizeMode() const787 ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const
788 {
789     return m_contentItem->m_columnResizeMode;
790 }
791 
setColumnResizeMode(ColumnResizeMode mode)792 void ColumnView::setColumnResizeMode(ColumnResizeMode mode)
793 {
794     if (m_contentItem->m_columnResizeMode == mode) {
795         return;
796     }
797 
798     m_contentItem->m_columnResizeMode = mode;
799     if (mode == SingleColumn && m_currentItem) {
800         m_contentItem->m_viewAnchorItem = m_currentItem;
801     }
802     m_contentItem->m_shouldAnimate = false;
803     polish();
804     Q_EMIT columnResizeModeChanged();
805 }
806 
columnWidth() const807 qreal ColumnView::columnWidth() const
808 {
809     return m_contentItem->m_columnWidth;
810 }
811 
setColumnWidth(qreal width)812 void ColumnView::setColumnWidth(qreal width)
813 {
814     // Always forget the internal binding when the user sets anything, even the same value
815     disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, nullptr);
816 
817     if (m_contentItem->m_columnWidth == width) {
818         return;
819     }
820 
821     m_contentItem->m_columnWidth = width;
822     m_contentItem->m_shouldAnimate = false;
823     polish();
824     Q_EMIT columnWidthChanged();
825 }
826 
currentIndex() const827 int ColumnView::currentIndex() const
828 {
829     return m_currentIndex;
830 }
831 
setCurrentIndex(int index)832 void ColumnView::setCurrentIndex(int index)
833 {
834     if (m_currentIndex == index || index < -1 || index >= m_contentItem->m_items.count()) {
835         return;
836     }
837 
838     m_currentIndex = index;
839 
840     if (index == -1) {
841         m_currentItem.clear();
842 
843     } else {
844         m_currentItem = m_contentItem->m_items[index];
845         Q_ASSERT(m_currentItem);
846         m_currentItem->forceActiveFocus();
847 
848         // If the current item is not on view, scroll
849         QRectF mappedCurrent = m_currentItem->mapRectToItem(this, QRectF(QPointF(0, 0), m_currentItem->size()));
850 
851         if (m_contentItem->m_slideAnim->state() == QAbstractAnimation::Running) {
852             mappedCurrent.moveLeft(mappedCurrent.left() + m_contentItem->x() + m_contentItem->m_slideAnim->endValue().toInt());
853         }
854 
855         // m_contentItem->m_slideAnim->stop();
856 
857         QRectF contentsRect(m_contentItem->m_leftPinnedSpace, //
858                             0,
859                             width() - m_contentItem->m_rightPinnedSpace - m_contentItem->m_leftPinnedSpace,
860                             height());
861 
862         if (!m_mouseDown) {
863             if (!contentsRect.contains(mappedCurrent)) {
864                 m_contentItem->m_viewAnchorItem = m_currentItem;
865                 m_contentItem->animateX(-m_currentItem->x() + m_contentItem->m_leftPinnedSpace);
866             } else {
867                 m_contentItem->snapToItem();
868             }
869         }
870     }
871 
872     Q_EMIT currentIndexChanged();
873     Q_EMIT currentItemChanged();
874 }
875 
currentItem()876 QQuickItem *ColumnView::currentItem()
877 {
878     return m_currentItem;
879 }
880 
visibleItems() const881 QList<QObject *> ColumnView::visibleItems() const
882 {
883     return m_contentItem->m_visibleItems;
884 }
885 
firstVisibleItem() const886 QQuickItem *ColumnView::firstVisibleItem() const
887 {
888     if (m_contentItem->m_visibleItems.isEmpty()) {
889         return nullptr;
890     }
891 
892     return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.first());
893 }
894 
lastVisibleItem() const895 QQuickItem *ColumnView::lastVisibleItem() const
896 {
897     if (m_contentItem->m_visibleItems.isEmpty()) {
898         return nullptr;
899     }
900 
901     return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.last());
902 }
903 
count() const904 int ColumnView::count() const
905 {
906     return m_contentItem->m_items.count();
907 }
908 
topPadding() const909 qreal ColumnView::topPadding() const
910 {
911     return m_topPadding;
912 }
913 
setTopPadding(qreal padding)914 void ColumnView::setTopPadding(qreal padding)
915 {
916     if (padding == m_topPadding) {
917         return;
918     }
919 
920     m_topPadding = padding;
921     polish();
922     Q_EMIT topPaddingChanged();
923 }
924 
bottomPadding() const925 qreal ColumnView::bottomPadding() const
926 {
927     return m_bottomPadding;
928 }
929 
setBottomPadding(qreal padding)930 void ColumnView::setBottomPadding(qreal padding)
931 {
932     if (padding == m_bottomPadding) {
933         return;
934     }
935 
936     m_bottomPadding = padding;
937     polish();
938     Q_EMIT bottomPaddingChanged();
939 }
940 
contentItem() const941 QQuickItem *ColumnView::contentItem() const
942 {
943     return m_contentItem;
944 }
945 
scrollDuration() const946 int ColumnView::scrollDuration() const
947 {
948     return m_contentItem->m_slideAnim->duration();
949 }
950 
setScrollDuration(int duration)951 void ColumnView::setScrollDuration(int duration)
952 {
953     disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, nullptr);
954 
955     if (m_contentItem->m_slideAnim->duration() == duration) {
956         return;
957     }
958 
959     m_contentItem->m_slideAnim->setDuration(duration);
960     Q_EMIT scrollDurationChanged();
961 }
962 
separatorVisible() const963 bool ColumnView::separatorVisible() const
964 {
965     return m_separatorVisible;
966 }
967 
setSeparatorVisible(bool visible)968 void ColumnView::setSeparatorVisible(bool visible)
969 {
970     if (visible == m_separatorVisible) {
971         return;
972     }
973 
974     m_separatorVisible = visible;
975 
976     if (visible) {
977         for (QQuickItem *item : std::as_const(m_contentItem->m_items)) {
978             QQuickItem *sep = m_contentItem->ensureSeparator(item);
979             if (sep) {
980                 sep->setVisible(true);
981             }
982 
983             ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
984             if (attached->isPinned()) {
985                 QQuickItem *sep = m_contentItem->ensureRightSeparator(item);
986                 if (sep) {
987                     sep->setVisible(true);
988                 }
989             }
990         }
991 
992     } else {
993         for (QQuickItem *sep : std::as_const(m_contentItem->m_separators)) {
994             sep->setVisible(false);
995         }
996         for (QQuickItem *sep : std::as_const(m_contentItem->m_rightSeparators)) {
997             sep->setVisible(false);
998         }
999     }
1000 
1001     Q_EMIT separatorVisibleChanged();
1002 }
1003 
dragging() const1004 bool ColumnView::dragging() const
1005 {
1006     return m_dragging;
1007 }
1008 
moving() const1009 bool ColumnView::moving() const
1010 {
1011     return m_moving;
1012 }
1013 
contentWidth() const1014 qreal ColumnView::contentWidth() const
1015 {
1016     return m_contentItem->width();
1017 }
1018 
contentX() const1019 qreal ColumnView::contentX() const
1020 {
1021     return -m_contentItem->x();
1022 }
1023 
setContentX(qreal x) const1024 void ColumnView::setContentX(qreal x) const
1025 {
1026     m_contentItem->setX(qRound(-x));
1027 }
1028 
interactive() const1029 bool ColumnView::interactive() const
1030 {
1031     return m_interactive;
1032 }
1033 
setInteractive(bool interactive)1034 void ColumnView::setInteractive(bool interactive)
1035 {
1036     if (m_interactive == interactive) {
1037         return;
1038     }
1039 
1040     m_interactive = interactive;
1041 
1042     if (!m_interactive) {
1043         if (m_dragging) {
1044             m_dragging = false;
1045             Q_EMIT draggingChanged();
1046         }
1047 
1048         m_contentItem->snapToItem();
1049         setKeepMouseGrab(false);
1050     }
1051 
1052     Q_EMIT interactiveChanged();
1053 }
1054 
acceptsMouse() const1055 bool ColumnView::acceptsMouse() const
1056 {
1057     return m_acceptsMouse;
1058 }
1059 
setAcceptsMouse(bool accepts)1060 void ColumnView::setAcceptsMouse(bool accepts)
1061 {
1062     if (m_acceptsMouse == accepts) {
1063         return;
1064     }
1065 
1066     m_acceptsMouse = accepts;
1067 
1068     if (!m_acceptsMouse) {
1069         if (m_dragging) {
1070             m_dragging = false;
1071             Q_EMIT draggingChanged();
1072         }
1073 
1074         m_contentItem->snapToItem();
1075         setKeepMouseGrab(false);
1076     }
1077 
1078     Q_EMIT acceptsMouseChanged();
1079 }
1080 
addItem(QQuickItem * item)1081 void ColumnView::addItem(QQuickItem *item)
1082 {
1083     insertItem(m_contentItem->m_items.length(), item);
1084 }
1085 
insertItem(int pos,QQuickItem * item)1086 void ColumnView::insertItem(int pos, QQuickItem *item)
1087 {
1088     if (!item || m_contentItem->m_items.contains(item)) {
1089         return;
1090     }
1091 
1092     m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1093 
1094     connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1095         removeItem(item);
1096     });
1097     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1098     attached->setOriginalParent(item->parentItem());
1099     attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1100     item->setParentItem(m_contentItem);
1101 
1102     item->forceActiveFocus();
1103 
1104     // Animate shift to new item.
1105     m_contentItem->m_shouldAnimate = true;
1106     m_contentItem->layoutItems();
1107     Q_EMIT contentChildrenChanged();
1108 
1109     // In order to keep the same current item we need to increase the current index if displaced
1110     // NOTE: just updating m_currentIndex does *not* update currentItem (which is what we need atm) while setCurrentIndex will update also currentItem
1111     if (m_currentIndex >= pos) {
1112         ++m_currentIndex;
1113         Q_EMIT currentIndexChanged();
1114     }
1115 
1116     Q_EMIT itemInserted(pos, item);
1117 }
1118 
replaceItem(int pos,QQuickItem * item)1119 void ColumnView::replaceItem(int pos, QQuickItem *item)
1120 {
1121     if (pos < 0 || pos >= m_contentItem->m_items.length()) {
1122         qCWarning(KirigamiLog) << "Position" << pos << "passed to ColumnView::replaceItem is out of range.";
1123         return;
1124     }
1125 
1126     if (!item) {
1127         qCWarning(KirigamiLog) << "Null item passed to ColumnView::replaceItem.";
1128         return;
1129     }
1130 
1131     QQuickItem *oldItem = m_contentItem->m_items[pos];
1132 
1133     // In order to keep the same current item we need to increase the current index if displaced
1134     if (m_currentIndex >= pos) {
1135         setCurrentIndex(m_currentIndex - 1);
1136     }
1137 
1138     m_contentItem->forgetItem(oldItem);
1139     oldItem->setVisible(false);
1140 
1141     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(oldItem, false));
1142 
1143     if (attached && attached->shouldDeleteOnRemove()) {
1144         oldItem->deleteLater();
1145     } else {
1146         oldItem->setParentItem(attached ? attached->originalParent() : nullptr);
1147     }
1148 
1149     Q_EMIT itemRemoved(oldItem);
1150 
1151     if (!m_contentItem->m_items.contains(item)) {
1152         m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1153 
1154         connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1155             removeItem(item);
1156         });
1157         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1158         attached->setOriginalParent(item->parentItem());
1159         attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1160         item->setParentItem(m_contentItem);
1161 
1162         if (m_currentIndex >= pos) {
1163             ++m_currentIndex;
1164             Q_EMIT currentIndexChanged();
1165         }
1166 
1167         Q_EMIT itemInserted(pos, item);
1168     }
1169 
1170     // Disable animation so replacement happens immediately.
1171     m_contentItem->m_shouldAnimate = false;
1172     m_contentItem->layoutItems();
1173     Q_EMIT contentChildrenChanged();
1174 }
1175 
moveItem(int from,int to)1176 void ColumnView::moveItem(int from, int to)
1177 {
1178     if (m_contentItem->m_items.isEmpty() //
1179         || from < 0 || from >= m_contentItem->m_items.length() //
1180         || to < 0 || to >= m_contentItem->m_items.length()) {
1181         return;
1182     }
1183 
1184     m_contentItem->m_items.move(from, to);
1185     m_contentItem->m_shouldAnimate = true;
1186 
1187     if (from == m_currentIndex) {
1188         m_currentIndex = to;
1189         Q_EMIT currentIndexChanged();
1190     } else if (from < m_currentIndex && to > m_currentIndex) {
1191         --m_currentIndex;
1192         Q_EMIT currentIndexChanged();
1193     } else if (from > m_currentIndex && to <= m_currentIndex) {
1194         ++m_currentIndex;
1195         Q_EMIT currentIndexChanged();
1196     }
1197 
1198     polish();
1199 }
1200 
removeItem(const QVariant & item)1201 QQuickItem *ColumnView::removeItem(const QVariant &item)
1202 {
1203     if (item.canConvert<QQuickItem *>()) {
1204         return removeItem(item.value<QQuickItem *>());
1205     } else if (item.canConvert<int>()) {
1206         return removeItem(item.toInt());
1207     } else {
1208         return nullptr;
1209     }
1210 }
1211 
removeItem(QQuickItem * item)1212 QQuickItem *ColumnView::removeItem(QQuickItem *item)
1213 {
1214     if (m_contentItem->m_items.isEmpty() || !m_contentItem->m_items.contains(item)) {
1215         return nullptr;
1216     }
1217 
1218     const int index = m_contentItem->m_items.indexOf(item);
1219 
1220     // In order to keep the same current item we need to increase the current index if displaced
1221     if (m_currentIndex >= index) {
1222         setCurrentIndex(m_currentIndex - 1);
1223     }
1224 
1225     m_contentItem->forgetItem(item);
1226     item->setVisible(false);
1227 
1228     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, false));
1229 
1230     if (attached && attached->shouldDeleteOnRemove()) {
1231         item->deleteLater();
1232     } else {
1233         item->setParentItem(attached ? attached->originalParent() : nullptr);
1234     }
1235 
1236     Q_EMIT itemRemoved(item);
1237 
1238     return item;
1239 }
1240 
removeItem(int pos)1241 QQuickItem *ColumnView::removeItem(int pos)
1242 {
1243     if (m_contentItem->m_items.isEmpty() //
1244         || pos < 0 || pos >= m_contentItem->m_items.length()) {
1245         return nullptr;
1246     }
1247 
1248     return removeItem(m_contentItem->m_items[pos]);
1249 }
1250 
pop(QQuickItem * item)1251 QQuickItem *ColumnView::pop(QQuickItem *item)
1252 {
1253     QQuickItem *removed = nullptr;
1254 
1255     while (!m_contentItem->m_items.isEmpty() && m_contentItem->m_items.last() != item) {
1256         removed = removeItem(m_contentItem->m_items.last());
1257         // if no item has been passed, just pop one
1258         if (!item) {
1259             break;
1260         }
1261     }
1262     return removed;
1263 }
1264 
clear()1265 void ColumnView::clear()
1266 {
1267     for (QQuickItem *item : std::as_const(m_contentItem->m_items)) {
1268         removeItem(item);
1269     }
1270     m_contentItem->m_items.clear();
1271     Q_EMIT contentChildrenChanged();
1272 }
1273 
containsItem(QQuickItem * item)1274 bool ColumnView::containsItem(QQuickItem *item)
1275 {
1276     return m_contentItem->m_items.contains(item);
1277 }
1278 
itemAt(qreal x,qreal y)1279 QQuickItem *ColumnView::itemAt(qreal x, qreal y)
1280 {
1281     return m_contentItem->childAt(x, y);
1282 }
1283 
qmlAttachedProperties(QObject * object)1284 ColumnViewAttached *ColumnView::qmlAttachedProperties(QObject *object)
1285 {
1286     return new ColumnViewAttached(object);
1287 }
1288 
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)1289 void ColumnView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
1290 {
1291     m_contentItem->setY(m_topPadding);
1292     m_contentItem->setHeight(newGeometry.height() - m_topPadding - m_bottomPadding);
1293     m_contentItem->m_shouldAnimate = false;
1294     polish();
1295 
1296     m_contentItem->updateVisibleItems();
1297     QQuickItem::geometryChanged(newGeometry, oldGeometry);
1298 }
1299 
childMouseEventFilter(QQuickItem * item,QEvent * event)1300 bool ColumnView::childMouseEventFilter(QQuickItem *item, QEvent *event)
1301 {
1302     if (!m_interactive || item == m_contentItem) {
1303         return QQuickItem::childMouseEventFilter(item, event);
1304     }
1305 
1306     switch (event->type()) {
1307     case QEvent::MouseButtonPress: {
1308         QMouseEvent *me = static_cast<QMouseEvent *>(event);
1309 
1310         if (me->button() != Qt::LeftButton) {
1311             return false;
1312         }
1313 
1314         // On press, we set the current index of the view to the root item
1315         QQuickItem *candidateItem = item;
1316         while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1317             candidateItem = candidateItem->parentItem();
1318         }
1319         if (candidateItem->parentItem() == m_contentItem) {
1320             setCurrentIndex(m_contentItem->m_items.indexOf(candidateItem));
1321         }
1322 
1323         // if !m_acceptsMouse we don't drag with mouse
1324         if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1325             event->setAccepted(false);
1326             return false;
1327         }
1328 
1329         m_contentItem->m_slideAnim->stop();
1330         if (item->property("preventStealing").toBool()) {
1331             m_contentItem->snapToItem();
1332             return false;
1333         }
1334         m_oldMouseX = m_startMouseX = mapFromItem(item, me->localPos()).x();
1335         m_oldMouseY = m_startMouseY = mapFromItem(item, me->localPos()).y();
1336 
1337         m_mouseDown = true;
1338         me->setAccepted(false);
1339         setKeepMouseGrab(false);
1340 
1341         break;
1342     }
1343     case QEvent::MouseMove: {
1344         QMouseEvent *me = static_cast<QMouseEvent *>(event);
1345 
1346         if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1347             return false;
1348         }
1349 
1350         if (!(me->buttons() & Qt::LeftButton)) {
1351             return false;
1352         }
1353 
1354         const QPointF pos = mapFromItem(item, me->localPos());
1355 
1356         bool verticalScrollIntercepted = false;
1357 
1358         QQuickItem *candidateItem = item;
1359         while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1360             candidateItem = candidateItem->parentItem();
1361         }
1362         if (candidateItem->parentItem() == m_contentItem) {
1363             ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1364             if (attached->preventStealing()) {
1365                 return false;
1366             }
1367         }
1368 
1369         {
1370             ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1371 
1372             ScrollIntentionEvent scrollIntentionEvent;
1373             scrollIntentionEvent.delta = QPointF(pos.x() - m_oldMouseX, pos.y() - m_oldMouseY);
1374 
1375             Q_EMIT attached->scrollIntention(&scrollIntentionEvent);
1376 
1377             if (scrollIntentionEvent.accepted) {
1378                 verticalScrollIntercepted = true;
1379                 event->setAccepted(true);
1380             }
1381         }
1382 
1383         if ((!keepMouseGrab() && item->keepMouseGrab()) || item->property("preventStealing").toBool()) {
1384             m_contentItem->snapToItem();
1385             m_oldMouseX = pos.x();
1386             m_oldMouseY = pos.y();
1387             return false;
1388         }
1389 
1390         const bool wasDragging = m_dragging;
1391         // If a drag happened, start to steal all events, use startDragDistance * 2 to give time to widgets to take the mouse grab by themselves
1392         m_dragging = keepMouseGrab() || qAbs(mapFromItem(item, me->localPos()).x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 3;
1393 
1394         if (m_dragging != wasDragging) {
1395             m_moving = true;
1396             Q_EMIT movingChanged();
1397             Q_EMIT draggingChanged();
1398         }
1399 
1400         if (m_dragging) {
1401             m_contentItem->setBoundedX(m_contentItem->x() + pos.x() - m_oldMouseX);
1402         }
1403 
1404         m_contentItem->m_lastDragDelta = pos.x() - m_oldMouseX;
1405         m_oldMouseX = pos.x();
1406         m_oldMouseY = pos.y();
1407 
1408         setKeepMouseGrab(m_dragging);
1409         me->setAccepted(m_dragging);
1410 
1411         return m_dragging && !verticalScrollIntercepted;
1412         break;
1413     }
1414     case QEvent::MouseButtonRelease: {
1415         QMouseEvent *me = static_cast<QMouseEvent *>(event);
1416 
1417         if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1418             return false;
1419         }
1420 
1421         if (me->button() == Qt::BackButton && m_currentIndex > 0) {
1422             setCurrentIndex(m_currentIndex - 1);
1423             me->accept();
1424             return true;
1425         } else if (me->button() == Qt::ForwardButton) {
1426             setCurrentIndex(m_currentIndex + 1);
1427             me->accept();
1428             return true;
1429         }
1430 
1431         if (me->button() != Qt::LeftButton) {
1432             return false;
1433         }
1434 
1435         m_mouseDown = false;
1436 
1437         if (m_dragging) {
1438             m_contentItem->snapToItem();
1439             m_contentItem->m_lastDragDelta = 0;
1440             m_dragging = false;
1441             Q_EMIT draggingChanged();
1442         }
1443 
1444         if (item->property("preventStealing").toBool()) {
1445             return false;
1446         }
1447 
1448         event->accept();
1449 
1450         // if a drag happened, don't pass the event
1451         const bool block = keepMouseGrab();
1452         setKeepMouseGrab(false);
1453 
1454         me->setAccepted(block);
1455         return block;
1456         break;
1457     }
1458     default:
1459         break;
1460     }
1461 
1462     return QQuickItem::childMouseEventFilter(item, event);
1463 }
1464 
mousePressEvent(QMouseEvent * event)1465 void ColumnView::mousePressEvent(QMouseEvent *event)
1466 {
1467     if (!m_acceptsMouse && event->source() == Qt::MouseEventNotSynthesized) {
1468         event->setAccepted(false);
1469         return;
1470     }
1471 
1472     if (event->button() == Qt::BackButton || event->button() == Qt::ForwardButton) {
1473         event->accept();
1474         return;
1475     }
1476 
1477     if (!m_interactive) {
1478         return;
1479     }
1480 
1481     m_contentItem->snapToItem();
1482     m_oldMouseX = event->localPos().x();
1483     m_startMouseX = event->localPos().x();
1484     m_mouseDown = true;
1485     setKeepMouseGrab(false);
1486     event->accept();
1487 }
1488 
mouseMoveEvent(QMouseEvent * event)1489 void ColumnView::mouseMoveEvent(QMouseEvent *event)
1490 {
1491     if (event->buttons() & Qt::BackButton || event->buttons() & Qt::ForwardButton) {
1492         event->accept();
1493         return;
1494     }
1495 
1496     if (!m_interactive) {
1497         return;
1498     }
1499 
1500     const bool wasDragging = m_dragging;
1501     // Same startDragDistance * 2 as the event filter
1502     m_dragging = keepMouseGrab() || qAbs(event->localPos().x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 2;
1503     if (m_dragging != wasDragging) {
1504         m_moving = true;
1505         Q_EMIT movingChanged();
1506         Q_EMIT draggingChanged();
1507     }
1508 
1509     setKeepMouseGrab(m_dragging);
1510 
1511     if (m_dragging) {
1512         m_contentItem->setBoundedX(m_contentItem->x() + event->pos().x() - m_oldMouseX);
1513     }
1514 
1515     m_contentItem->m_lastDragDelta = event->pos().x() - m_oldMouseX;
1516     m_oldMouseX = event->pos().x();
1517     event->accept();
1518 }
1519 
mouseReleaseEvent(QMouseEvent * event)1520 void ColumnView::mouseReleaseEvent(QMouseEvent *event)
1521 {
1522     if (event->button() == Qt::BackButton && m_currentIndex > 0) {
1523         setCurrentIndex(m_currentIndex - 1);
1524         event->accept();
1525         return;
1526     } else if (event->button() == Qt::ForwardButton) {
1527         setCurrentIndex(m_currentIndex + 1);
1528         event->accept();
1529         return;
1530     }
1531 
1532     m_mouseDown = false;
1533 
1534     if (!m_interactive) {
1535         return;
1536     }
1537 
1538     m_contentItem->snapToItem();
1539     m_contentItem->m_lastDragDelta = 0;
1540 
1541     if (m_dragging) {
1542         m_dragging = false;
1543         Q_EMIT draggingChanged();
1544     }
1545 
1546     setKeepMouseGrab(false);
1547     event->accept();
1548 }
1549 
mouseUngrabEvent()1550 void ColumnView::mouseUngrabEvent()
1551 {
1552     m_mouseDown = false;
1553 
1554     if (m_contentItem->m_slideAnim->state() != QAbstractAnimation::Running) {
1555         m_contentItem->snapToItem();
1556     }
1557     m_contentItem->m_lastDragDelta = 0;
1558 
1559     if (m_dragging) {
1560         m_dragging = false;
1561         Q_EMIT draggingChanged();
1562     }
1563 
1564     setKeepMouseGrab(false);
1565 }
1566 
classBegin()1567 void ColumnView::classBegin()
1568 {
1569     auto syncColumnWidth = [this]() {
1570         m_contentItem->m_columnWidth = privateQmlComponentsPoolSelf->instance(qmlEngine(this))->m_units->gridUnit() * 20;
1571         Q_EMIT columnWidthChanged();
1572     };
1573 
1574     connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, syncColumnWidth);
1575     syncColumnWidth();
1576 
1577     auto syncDuration = [this]() {
1578         m_contentItem->m_slideAnim->setDuration(QmlComponentsPoolSingleton::instance(qmlEngine(this))->m_units->longDuration());
1579         Q_EMIT scrollDurationChanged();
1580     };
1581 
1582     connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, syncDuration);
1583     syncDuration();
1584 
1585     QQuickItem::classBegin();
1586 }
1587 
componentComplete()1588 void ColumnView::componentComplete()
1589 {
1590     m_complete = true;
1591     QQuickItem::componentComplete();
1592 }
1593 
updatePolish()1594 void ColumnView::updatePolish()
1595 {
1596     m_contentItem->layoutItems();
1597 }
1598 
itemChange(QQuickItem::ItemChange change,const QQuickItem::ItemChangeData & value)1599 void ColumnView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1600 {
1601     switch (change) {
1602     case QQuickItem::ItemChildAddedChange:
1603         if (m_contentItem && value.item != m_contentItem && !value.item->inherits("QQuickRepeater")) {
1604             addItem(value.item);
1605         }
1606         break;
1607     default:
1608         break;
1609     }
1610     QQuickItem::itemChange(change, value);
1611 }
1612 
contentChildren_append(QQmlListProperty<QQuickItem> * prop,QQuickItem * item)1613 void ColumnView::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item)
1614 {
1615     // This can only be called from QML
1616     ColumnView *view = static_cast<ColumnView *>(prop->object);
1617     if (!view) {
1618         return;
1619     }
1620 
1621     view->m_contentItem->m_items.append(item);
1622     connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1623         view->removeItem(item);
1624     });
1625 
1626     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1627     attached->setOriginalParent(item->parentItem());
1628     attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1629 
1630     item->setParentItem(view->m_contentItem);
1631 }
1632 
contentChildren_count(QQmlListProperty<QQuickItem> * prop)1633 int ColumnView::contentChildren_count(QQmlListProperty<QQuickItem> *prop)
1634 {
1635     ColumnView *view = static_cast<ColumnView *>(prop->object);
1636     if (!view) {
1637         return 0;
1638     }
1639 
1640     return view->m_contentItem->m_items.count();
1641 }
1642 
contentChildren_at(QQmlListProperty<QQuickItem> * prop,int index)1643 QQuickItem *ColumnView::contentChildren_at(QQmlListProperty<QQuickItem> *prop, int index)
1644 {
1645     ColumnView *view = static_cast<ColumnView *>(prop->object);
1646     if (!view) {
1647         return nullptr;
1648     }
1649 
1650     if (index < 0 || index >= view->m_contentItem->m_items.count()) {
1651         return nullptr;
1652     }
1653     return view->m_contentItem->m_items.value(index);
1654 }
1655 
contentChildren_clear(QQmlListProperty<QQuickItem> * prop)1656 void ColumnView::contentChildren_clear(QQmlListProperty<QQuickItem> *prop)
1657 {
1658     ColumnView *view = static_cast<ColumnView *>(prop->object);
1659     if (!view) {
1660         return;
1661     }
1662 
1663     return view->m_contentItem->m_items.clear();
1664 }
1665 
contentChildren()1666 QQmlListProperty<QQuickItem> ColumnView::contentChildren()
1667 {
1668     return QQmlListProperty<QQuickItem>(this, //
1669                                         nullptr,
1670                                         contentChildren_append,
1671                                         contentChildren_count,
1672                                         contentChildren_at,
1673                                         contentChildren_clear);
1674 }
1675 
contentData_append(QQmlListProperty<QObject> * prop,QObject * object)1676 void ColumnView::contentData_append(QQmlListProperty<QObject> *prop, QObject *object)
1677 {
1678     ColumnView *view = static_cast<ColumnView *>(prop->object);
1679     if (!view) {
1680         return;
1681     }
1682 
1683     view->m_contentData.append(object);
1684     QQuickItem *item = qobject_cast<QQuickItem *>(object);
1685     // exclude repeaters from layout
1686     if (item && item->inherits("QQuickRepeater")) {
1687         item->setParentItem(view);
1688 
1689         connect(item, SIGNAL(modelChanged()), view->m_contentItem, SLOT(updateRepeaterModel()));
1690 
1691     } else if (item) {
1692         view->m_contentItem->m_items.append(item);
1693         connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1694             view->removeItem(item);
1695         });
1696 
1697         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1698         attached->setOriginalParent(item->parentItem());
1699         attached->setShouldDeleteOnRemove(view->m_complete && !item->parentItem() && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1700 
1701         item->setParentItem(view->m_contentItem);
1702 
1703     } else {
1704         object->setParent(view);
1705     }
1706 }
1707 
contentData_count(QQmlListProperty<QObject> * prop)1708 int ColumnView::contentData_count(QQmlListProperty<QObject> *prop)
1709 {
1710     ColumnView *view = static_cast<ColumnView *>(prop->object);
1711     if (!view) {
1712         return 0;
1713     }
1714 
1715     return view->m_contentData.count();
1716 }
1717 
contentData_at(QQmlListProperty<QObject> * prop,int index)1718 QObject *ColumnView::contentData_at(QQmlListProperty<QObject> *prop, int index)
1719 {
1720     ColumnView *view = static_cast<ColumnView *>(prop->object);
1721     if (!view) {
1722         return nullptr;
1723     }
1724 
1725     if (index < 0 || index >= view->m_contentData.count()) {
1726         return nullptr;
1727     }
1728     return view->m_contentData.value(index);
1729 }
1730 
contentData_clear(QQmlListProperty<QObject> * prop)1731 void ColumnView::contentData_clear(QQmlListProperty<QObject> *prop)
1732 {
1733     ColumnView *view = static_cast<ColumnView *>(prop->object);
1734     if (!view) {
1735         return;
1736     }
1737 
1738     return view->m_contentData.clear();
1739 }
1740 
contentData()1741 QQmlListProperty<QObject> ColumnView::contentData()
1742 {
1743     return QQmlListProperty<QObject>(this, //
1744                                      nullptr,
1745                                      contentData_append,
1746                                      contentData_count,
1747                                      contentData_at,
1748                                      contentData_clear);
1749 }
1750 
1751 #include "moc_columnview.cpp"
1752