File indexing completed on 2024-04-21 03:55:58

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "columnview.h"
0008 #include "columnview_p.h"
0009 
0010 #include "loggingcategory.h"
0011 #include <QAbstractItemModel>
0012 #include <QGuiApplication>
0013 #include <QPropertyAnimation>
0014 #include <QQmlComponent>
0015 #include <QQmlContext>
0016 #include <QQmlEngine>
0017 #include <QStyleHints>
0018 
0019 #include "platform/units.h"
0020 
0021 class QmlComponentsPoolSingleton
0022 {
0023 public:
0024     QmlComponentsPoolSingleton()
0025     {
0026     }
0027     static QmlComponentsPool *instance(QQmlEngine *engine);
0028 
0029 private:
0030     QHash<QQmlEngine *, QmlComponentsPool *> m_instances;
0031 };
0032 
0033 Q_GLOBAL_STATIC(QmlComponentsPoolSingleton, privateQmlComponentsPoolSelf)
0034 
0035 QmlComponentsPool *QmlComponentsPoolSingleton::instance(QQmlEngine *engine)
0036 {
0037     Q_ASSERT(engine);
0038     auto componentPool = privateQmlComponentsPoolSelf->m_instances.value(engine);
0039 
0040     if (componentPool) {
0041         return componentPool;
0042     }
0043 
0044     componentPool = new QmlComponentsPool(engine);
0045 
0046     const auto removePool = [engine]() {
0047         // NB: do not derefence engine. it may be dangling already!
0048         if (privateQmlComponentsPoolSelf) {
0049             privateQmlComponentsPoolSelf->m_instances.remove(engine);
0050         }
0051     };
0052     QObject::connect(engine, &QObject::destroyed, engine, removePool);
0053     QObject::connect(componentPool, &QObject::destroyed, componentPool, removePool);
0054 
0055     privateQmlComponentsPoolSelf->m_instances[engine] = componentPool;
0056     return componentPool;
0057 }
0058 
0059 QmlComponentsPool::QmlComponentsPool(QQmlEngine *engine)
0060     : QObject(engine)
0061 {
0062     QQmlComponent component(engine);
0063 
0064     /* clang-format off */
0065     component.setData(QByteArrayLiteral(R"(
0066 import QtQuick
0067 import org.kde.kirigami as Kirigami
0068 
0069 QtObject {
0070     readonly property Component leadingSeparator: Kirigami.Separator {
0071         property Item column
0072         property bool inToolBar
0073         property Kirigami.ColumnView view
0074 
0075         // positioning trick to hide the very first separator
0076         visible: {
0077             if (!view || !view.separatorVisible) {
0078                 return false;
0079             }
0080 
0081             return view && (LayoutMirroring.enabled
0082                 ? view.contentX + view.width > column.x + column.width
0083                 : view.contentX < column.x);
0084         }
0085 
0086         anchors.top: column.top
0087         anchors.left: column.left
0088         anchors.bottom: column.bottom
0089         anchors.topMargin: inToolBar ? Kirigami.Units.largeSpacing : 0
0090         anchors.bottomMargin: inToolBar ? Kirigami.Units.largeSpacing : 0
0091         Kirigami.Theme.colorSet: Kirigami.Theme.Header
0092         Kirigami.Theme.inherit: false
0093     }
0094 
0095     readonly property Component trailingSeparator: Kirigami.Separator {
0096         property Item column
0097 
0098         anchors.top: column.top
0099         anchors.right: column.right
0100         anchors.bottom: column.bottom
0101         Kirigami.Theme.colorSet: Kirigami.Theme.Header
0102         Kirigami.Theme.inherit: false
0103     }
0104 }
0105 )"), QUrl(QStringLiteral("columnview.cpp")));
0106     /* clang-format on */
0107 
0108     m_instance = component.create();
0109     // qCWarning(KirigamiLog)<<component.errors();
0110     Q_ASSERT(m_instance);
0111     m_instance->setParent(this);
0112 
0113     m_leadingSeparatorComponent = m_instance->property("leadingSeparator").value<QQmlComponent *>();
0114     Q_ASSERT(m_leadingSeparatorComponent);
0115 
0116     m_trailingSeparatorComponent = m_instance->property("trailingSeparator").value<QQmlComponent *>();
0117     Q_ASSERT(m_trailingSeparatorComponent);
0118 
0119     m_units = engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
0120     Q_ASSERT(m_units);
0121 
0122     connect(m_units, &Kirigami::Platform::Units::gridUnitChanged, this, &QmlComponentsPool::gridUnitChanged);
0123     connect(m_units, &Kirigami::Platform::Units::longDurationChanged, this, &QmlComponentsPool::longDurationChanged);
0124 }
0125 
0126 QmlComponentsPool::~QmlComponentsPool()
0127 {
0128 }
0129 
0130 /////////
0131 
0132 ColumnViewAttached::ColumnViewAttached(QObject *parent)
0133     : QObject(parent)
0134 {
0135 }
0136 
0137 ColumnViewAttached::~ColumnViewAttached()
0138 {
0139 }
0140 
0141 void ColumnViewAttached::setIndex(int index)
0142 {
0143     if (!m_customFillWidth && m_view) {
0144         const bool oldFillWidth = m_fillWidth;
0145         m_fillWidth = index == m_view->count() - 1;
0146         if (oldFillWidth != m_fillWidth) {
0147             Q_EMIT fillWidthChanged();
0148         }
0149     }
0150 
0151     if (index == m_index) {
0152         return;
0153     }
0154 
0155     m_index = index;
0156     Q_EMIT indexChanged();
0157 }
0158 
0159 int ColumnViewAttached::index() const
0160 {
0161     return m_index;
0162 }
0163 
0164 void ColumnViewAttached::setFillWidth(bool fill)
0165 {
0166     if (m_view) {
0167         disconnect(m_view.data(), &ColumnView::countChanged, this, nullptr);
0168     }
0169     m_customFillWidth = true;
0170 
0171     if (fill == m_fillWidth) {
0172         return;
0173     }
0174 
0175     m_fillWidth = fill;
0176     Q_EMIT fillWidthChanged();
0177 
0178     if (m_view) {
0179         m_view->polish();
0180     }
0181 }
0182 
0183 bool ColumnViewAttached::fillWidth() const
0184 {
0185     return m_fillWidth;
0186 }
0187 
0188 qreal ColumnViewAttached::reservedSpace() const
0189 {
0190     return m_reservedSpace;
0191 }
0192 
0193 void ColumnViewAttached::setReservedSpace(qreal space)
0194 {
0195     if (m_view) {
0196         disconnect(m_view.data(), &ColumnView::columnWidthChanged, this, nullptr);
0197     }
0198     m_customReservedSpace = true;
0199 
0200     if (qFuzzyCompare(space, m_reservedSpace)) {
0201         return;
0202     }
0203 
0204     m_reservedSpace = space;
0205     Q_EMIT reservedSpaceChanged();
0206 
0207     if (m_view) {
0208         m_view->polish();
0209     }
0210 }
0211 
0212 ColumnView *ColumnViewAttached::view()
0213 {
0214     return m_view;
0215 }
0216 
0217 void ColumnViewAttached::setView(ColumnView *view)
0218 {
0219     if (view == m_view) {
0220         return;
0221     }
0222 
0223     if (m_view) {
0224         disconnect(m_view.data(), nullptr, this, nullptr);
0225     }
0226     m_view = view;
0227 
0228     if (!m_customFillWidth && m_view) {
0229         m_fillWidth = m_index == m_view->count() - 1;
0230         connect(m_view.data(), &ColumnView::countChanged, this, [this]() {
0231             m_fillWidth = m_index == m_view->count() - 1;
0232             Q_EMIT fillWidthChanged();
0233         });
0234     }
0235     if (!m_customReservedSpace && m_view) {
0236         m_reservedSpace = m_view->columnWidth();
0237         connect(m_view.data(), &ColumnView::columnWidthChanged, this, [this]() {
0238             m_reservedSpace = m_view->columnWidth();
0239             Q_EMIT reservedSpaceChanged();
0240         });
0241     }
0242 
0243     Q_EMIT viewChanged();
0244 }
0245 
0246 QQuickItem *ColumnViewAttached::originalParent() const
0247 {
0248     return m_originalParent;
0249 }
0250 
0251 void ColumnViewAttached::setOriginalParent(QQuickItem *parent)
0252 {
0253     m_originalParent = parent;
0254 }
0255 
0256 bool ColumnViewAttached::shouldDeleteOnRemove() const
0257 {
0258     return m_shouldDeleteOnRemove;
0259 }
0260 
0261 void ColumnViewAttached::setShouldDeleteOnRemove(bool del)
0262 {
0263     m_shouldDeleteOnRemove = del;
0264 }
0265 
0266 bool ColumnViewAttached::preventStealing() const
0267 {
0268     return m_preventStealing;
0269 }
0270 
0271 void ColumnViewAttached::setPreventStealing(bool prevent)
0272 {
0273     if (prevent == m_preventStealing) {
0274         return;
0275     }
0276 
0277     m_preventStealing = prevent;
0278     Q_EMIT preventStealingChanged();
0279 }
0280 
0281 bool ColumnViewAttached::isPinned() const
0282 {
0283     return m_pinned;
0284 }
0285 
0286 void ColumnViewAttached::setPinned(bool pinned)
0287 {
0288     if (pinned == m_pinned) {
0289         return;
0290     }
0291 
0292     m_pinned = pinned;
0293 
0294     Q_EMIT pinnedChanged();
0295 
0296     if (m_view) {
0297         m_view->polish();
0298     }
0299 }
0300 
0301 bool ColumnViewAttached::inViewport() const
0302 {
0303     return m_inViewport;
0304 }
0305 
0306 void ColumnViewAttached::setInViewport(bool inViewport)
0307 {
0308     if (m_inViewport == inViewport) {
0309         return;
0310     }
0311 
0312     m_inViewport = inViewport;
0313 
0314     Q_EMIT inViewportChanged();
0315 }
0316 
0317 QQuickItem *ColumnViewAttached::globalHeader() const
0318 {
0319     return m_globalHeader;
0320 }
0321 
0322 void ColumnViewAttached::setGlobalHeader(QQuickItem *header)
0323 {
0324     if (header == m_globalHeader) {
0325         return;
0326     }
0327 
0328     QQuickItem *oldHeader = m_globalHeader;
0329     if (m_globalHeader) {
0330         disconnect(m_globalHeader, nullptr, this, nullptr);
0331     }
0332 
0333     m_globalHeader = header;
0334 
0335     connect(header, &QObject::destroyed, this, [this, header]() {
0336         globalHeaderChanged(header, nullptr);
0337     });
0338 
0339     Q_EMIT globalHeaderChanged(oldHeader, header);
0340 }
0341 
0342 QQuickItem *ColumnViewAttached::globalFooter() const
0343 {
0344     return m_globalFooter;
0345 }
0346 
0347 void ColumnViewAttached::setGlobalFooter(QQuickItem *footer)
0348 {
0349     if (footer == m_globalFooter) {
0350         return;
0351     }
0352 
0353     QQuickItem *oldFooter = m_globalFooter;
0354     if (m_globalFooter) {
0355         disconnect(m_globalFooter, nullptr, this, nullptr);
0356     }
0357 
0358     m_globalFooter = footer;
0359 
0360     connect(footer, &QObject::destroyed, this, [this, footer]() {
0361         globalFooterChanged(footer, nullptr);
0362     });
0363 
0364     Q_EMIT globalFooterChanged(oldFooter, footer);
0365 }
0366 
0367 /////////
0368 
0369 ContentItem::ContentItem(ColumnView *parent)
0370     : QQuickItem(parent)
0371     , m_view(parent)
0372 {
0373     m_globalHeaderParent = new QQuickItem(this);
0374     m_globalFooterParent = new QQuickItem(this);
0375 
0376     setFlags(flags() | ItemIsFocusScope);
0377     m_slideAnim = new QPropertyAnimation(this);
0378     m_slideAnim->setTargetObject(this);
0379     m_slideAnim->setPropertyName("x");
0380     // NOTE: the duration will be taken from kirigami units upon classBegin
0381     m_slideAnim->setDuration(0);
0382     m_slideAnim->setEasingCurve(QEasingCurve(QEasingCurve::InOutQuad));
0383     connect(m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
0384         if (!m_view->currentItem()) {
0385             m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
0386         } else {
0387             QRectF mapped = m_view->currentItem()->mapRectToItem(m_view, QRectF(QPointF(0, 0), m_view->currentItem()->size()));
0388             if (!QRectF(QPointF(0, 0), m_view->size()).intersects(mapped)) {
0389                 m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
0390             }
0391         }
0392     });
0393 
0394     connect(this, &QQuickItem::xChanged, this, &ContentItem::layoutPinnedItems);
0395     m_creationInProgress = false;
0396 }
0397 
0398 ContentItem::~ContentItem()
0399 {
0400 }
0401 
0402 void ContentItem::setBoundedX(qreal x)
0403 {
0404     if (!parentItem()) {
0405         return;
0406     }
0407     m_slideAnim->stop();
0408     setX(qRound(qBound(qMin(0.0, -width() + parentItem()->width()), x, 0.0)));
0409 }
0410 
0411 void ContentItem::animateX(qreal newX)
0412 {
0413     if (!parentItem()) {
0414         return;
0415     }
0416 
0417     const qreal to = qRound(qBound(qMin(0.0, -width() + parentItem()->width()), newX, 0.0));
0418 
0419     m_slideAnim->stop();
0420     m_slideAnim->setStartValue(x());
0421     m_slideAnim->setEndValue(to);
0422     m_slideAnim->start();
0423 }
0424 
0425 void ContentItem::snapToItem()
0426 {
0427     QQuickItem *firstItem = childAt(viewportLeft(), height() / 2);
0428     if (!firstItem) {
0429         return;
0430     }
0431     QQuickItem *nextItem = childAt(firstItem->x() + firstItem->width() + 1, height() / 2);
0432 
0433     // need to make the last item visible?
0434     if (nextItem && //
0435         ((m_view->dragging() && m_lastDragDelta < 0) //
0436          || (!m_view->dragging() //
0437              && (width() - viewportRight()) < (viewportLeft() - firstItem->x())))) {
0438         m_viewAnchorItem = nextItem;
0439         animateX(-nextItem->x() + m_leftPinnedSpace);
0440 
0441         // The first one found?
0442     } else if ((m_view->dragging() && m_lastDragDelta >= 0) //
0443                || (!m_view->dragging() && (viewportLeft() <= (firstItem->x() + (firstItem->width() / 2)))) //
0444                || !nextItem) {
0445         m_viewAnchorItem = firstItem;
0446         animateX(-firstItem->x() + m_leftPinnedSpace);
0447 
0448         // the second?
0449     } else {
0450         m_viewAnchorItem = nextItem;
0451         animateX(-nextItem->x() + m_leftPinnedSpace);
0452     }
0453 }
0454 
0455 qreal ContentItem::viewportLeft() const
0456 {
0457     return -x() + m_leftPinnedSpace;
0458 }
0459 
0460 qreal ContentItem::viewportRight() const
0461 {
0462     return -x() + m_view->width() - m_rightPinnedSpace;
0463 }
0464 
0465 qreal ContentItem::childWidth(QQuickItem *child)
0466 {
0467     if (!parentItem()) {
0468         return 0.0;
0469     }
0470 
0471     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
0472 
0473     if (m_columnResizeMode == ColumnView::SingleColumn) {
0474         return qRound(parentItem()->width());
0475 
0476     } else if (attached->fillWidth()) {
0477         return qRound(qBound(m_columnWidth, (parentItem()->width() - attached->reservedSpace()), std::max(m_columnWidth, parentItem()->width())));
0478 
0479     } else if (m_columnResizeMode == ColumnView::FixedColumns) {
0480         return qRound(qMin(parentItem()->width(), m_columnWidth));
0481 
0482         // DynamicColumns
0483     } else {
0484         // TODO:look for Layout size hints
0485         qreal width = child->implicitWidth();
0486 
0487         if (width < 1.0) {
0488             width = m_columnWidth;
0489         }
0490 
0491         return qRound(qMin(m_view->width(), width));
0492     }
0493 }
0494 
0495 void ContentItem::layoutItems()
0496 {
0497     setY(m_view->topPadding());
0498     setHeight(m_view->height() - m_view->topPadding() - m_view->bottomPadding());
0499 
0500     qreal implicitWidth = 0;
0501     qreal implicitHeight = 0;
0502     qreal partialWidth = 0;
0503     int i = 0;
0504     m_leftPinnedSpace = 0;
0505     m_rightPinnedSpace = 0;
0506 
0507     bool reverse = qApp->layoutDirection() == Qt::RightToLeft;
0508     auto it = !reverse ? m_items.begin() : m_items.end();
0509     int increment = reverse ? -1 : +1;
0510     auto lastPos = reverse ? m_items.begin() : m_items.end();
0511 
0512     for (; it != lastPos; it += increment) {
0513         // for (QQuickItem *child : std::as_const(m_items)) {
0514         QQuickItem *child = reverse ? *(it - 1) : *it;
0515         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
0516         if (child == m_globalHeaderParent || child == m_globalFooterParent) {
0517             continue;
0518         }
0519 
0520         if (child->isVisible()) {
0521             if (attached->isPinned() && m_view->columnResizeMode() != ColumnView::SingleColumn) {
0522                 QQuickItem *sep = nullptr;
0523                 int sepWidth = 0;
0524                 if (m_view->separatorVisible()) {
0525                     sep = ensureTrailingSeparator(child);
0526                     sepWidth = (sep ? sep->width() : 0);
0527                 }
0528                 const qreal width = childWidth(child);
0529                 const qreal pageX = std::clamp(partialWidth, -x(), -x() + m_view->width() - child->width());
0530                 qreal headerHeight = .0;
0531                 qreal footerHeight = .0;
0532                 if (QQuickItem *header = attached->globalHeader()) {
0533                     headerHeight = header->isVisible() ? header->height() : .0;
0534                     header->setWidth(width + sepWidth);
0535                     header->setPosition(QPointF(pageX, .0));
0536                     header->setZ(2);
0537                     if (m_view->separatorVisible()) {
0538                         QQuickItem *sep = ensureTrailingSeparator(header);
0539                         sep->setProperty("inToolBar", true);
0540                     }
0541                 }
0542                 if (QQuickItem *footer = attached->globalFooter()) {
0543                     footerHeight = footer->isVisible() ? footer->height() : .0;
0544                     footer->setWidth(width + sepWidth);
0545                     footer->setPosition(QPointF(pageX, height() - footerHeight));
0546                     footer->setZ(2);
0547                     if (m_view->separatorVisible()) {
0548                         QQuickItem *sep = ensureTrailingSeparator(footer);
0549                         sep->setProperty("inToolBar", true);
0550                     }
0551                 }
0552 
0553                 child->setSize(QSizeF(width + sepWidth, height() - headerHeight - footerHeight));
0554                 child->setPosition(QPointF(pageX, headerHeight));
0555                 child->setZ(1);
0556 
0557                 if (partialWidth <= -x()) {
0558                     m_leftPinnedSpace = qMax(m_leftPinnedSpace, width);
0559                 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
0560                     m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
0561                 }
0562 
0563                 partialWidth += width;
0564 
0565             } else {
0566                 const qreal width = childWidth(child);
0567                 qreal headerHeight = .0;
0568                 qreal footerHeight = .0;
0569                 if (QQuickItem *header = attached->globalHeader(); header && qmlEngine(header)) {
0570                     if (m_view->separatorVisible()) {
0571                         QQuickItem *sep = ensureLeadingSeparator(header);
0572                         sep->setProperty("inToolBar", true);
0573                     }
0574                     headerHeight = header->isVisible() ? header->height() : .0;
0575                     header->setWidth(width);
0576                     header->setPosition(QPointF(partialWidth, .0));
0577                     header->setZ(1);
0578                     auto it = m_trailingSeparators.find(header);
0579                     if (it != m_trailingSeparators.end()) {
0580                         it.value()->deleteLater();
0581                         m_trailingSeparators.erase(it);
0582                     }
0583                 }
0584                 if (QQuickItem *footer = attached->globalFooter(); footer && qmlEngine(footer)) {
0585                     if (m_view->separatorVisible()) {
0586                         QQuickItem *sep = ensureLeadingSeparator(footer);
0587                         sep->setProperty("inToolBar", true);
0588                     }
0589                     footerHeight = footer->isVisible() ? footer->height() : .0;
0590                     footer->setWidth(width);
0591                     footer->setPosition(QPointF(partialWidth, height() - footerHeight));
0592                     footer->setZ(1);
0593                     auto it = m_trailingSeparators.find(footer);
0594                     if (it != m_trailingSeparators.end()) {
0595                         it.value()->deleteLater();
0596                         m_trailingSeparators.erase(it);
0597                     }
0598                 }
0599 
0600                 child->setSize(QSizeF(width, height() - headerHeight - footerHeight));
0601 
0602                 auto it = m_trailingSeparators.find(child);
0603                 if (it != m_trailingSeparators.end()) {
0604                     it.value()->deleteLater();
0605                     m_trailingSeparators.erase(it);
0606                 }
0607                 child->setPosition(QPointF(partialWidth, headerHeight));
0608                 child->setZ(0);
0609 
0610                 partialWidth += child->width();
0611             }
0612         }
0613 
0614         if (reverse) {
0615             attached->setIndex(m_items.count() - (++i));
0616         } else {
0617             attached->setIndex(i++);
0618         }
0619 
0620         implicitWidth += child->implicitWidth();
0621 
0622         implicitHeight = qMax(implicitHeight, child->implicitHeight());
0623     }
0624 
0625     setWidth(partialWidth);
0626 
0627     setImplicitWidth(implicitWidth);
0628     setImplicitHeight(implicitHeight);
0629 
0630     m_view->setImplicitWidth(implicitWidth);
0631     m_view->setImplicitHeight(implicitHeight + m_view->topPadding() + m_view->bottomPadding());
0632 
0633     const qreal newContentX = m_viewAnchorItem ? -m_viewAnchorItem->x() : 0.0;
0634     if (m_shouldAnimate) {
0635         animateX(newContentX);
0636     } else {
0637         setBoundedX(newContentX);
0638     }
0639 
0640     updateVisibleItems();
0641 }
0642 
0643 void ContentItem::layoutPinnedItems()
0644 {
0645     if (m_view->columnResizeMode() == ColumnView::SingleColumn) {
0646         return;
0647     }
0648 
0649     qreal partialWidth = 0;
0650     m_leftPinnedSpace = 0;
0651     m_rightPinnedSpace = 0;
0652 
0653     for (QQuickItem *child : std::as_const(m_items)) {
0654         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
0655 
0656         if (child->isVisible()) {
0657             if (attached->isPinned()) {
0658                 QQuickItem *sep = nullptr;
0659                 int sepWidth = 0;
0660                 if (m_view->separatorVisible()) {
0661                     sep = ensureTrailingSeparator(child);
0662                     sepWidth = (sep ? sep->width() : 0);
0663                 }
0664 
0665                 const qreal pageX = qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth);
0666                 qreal headerHeight = .0;
0667                 qreal footerHeight = .0;
0668                 if (QQuickItem *header = attached->globalHeader()) {
0669                     headerHeight = header->isVisible() ? header->height() : .0;
0670                     header->setPosition(QPointF(pageX, .0));
0671                     if (m_view->separatorVisible()) {
0672                         QQuickItem *sep = ensureTrailingSeparator(header);
0673                         sep->setProperty("inToolBar", true);
0674                     }
0675                 }
0676                 if (QQuickItem *footer = attached->globalFooter()) {
0677                     footerHeight = footer->isVisible() ? footer->height() : .0;
0678                     footer->setPosition(QPointF(pageX, height() - footerHeight));
0679                     if (m_view->separatorVisible()) {
0680                         QQuickItem *sep = ensureTrailingSeparator(footer);
0681                         sep->setProperty("inToolBar", true);
0682                     }
0683                 }
0684                 child->setPosition(QPointF(pageX, headerHeight));
0685 
0686                 if (partialWidth <= -x()) {
0687                     m_leftPinnedSpace = qMax(m_leftPinnedSpace, child->width() - sepWidth);
0688                 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
0689                     m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
0690                 }
0691             }
0692 
0693             partialWidth += child->width();
0694         }
0695     }
0696 }
0697 
0698 void ContentItem::updateVisibleItems()
0699 {
0700     QList<QObject *> newItems;
0701 
0702     for (auto *item : std::as_const(m_items)) {
0703         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
0704 
0705         if (item->isVisible() && item->x() + x() < m_view->width() && item->x() + item->width() + x() > 0) {
0706             newItems << item;
0707             connect(item, &QObject::destroyed, this, [this, item] {
0708                 m_visibleItems.removeAll(item);
0709             });
0710             attached->setInViewport(true);
0711         } else {
0712             attached->setInViewport(false);
0713         }
0714     }
0715 
0716     for (auto *item : std::as_const(m_visibleItems)) {
0717         disconnect(item, &QObject::destroyed, this, nullptr);
0718     }
0719 
0720     const QQuickItem *oldLeadingVisibleItem = m_view->leadingVisibleItem();
0721     const QQuickItem *oldTrailingVisibleItem = m_view->trailingVisibleItem();
0722 
0723     if (newItems != m_visibleItems) {
0724         m_visibleItems = newItems;
0725         Q_EMIT m_view->visibleItemsChanged();
0726         if (!m_visibleItems.isEmpty() && m_visibleItems.first() != oldLeadingVisibleItem) {
0727             Q_EMIT m_view->leadingVisibleItemChanged();
0728         }
0729         if (!m_visibleItems.isEmpty() && m_visibleItems.last() != oldTrailingVisibleItem) {
0730             Q_EMIT m_view->trailingVisibleItemChanged();
0731         }
0732     }
0733 }
0734 
0735 void ContentItem::forgetItem(QQuickItem *item)
0736 {
0737     if (!m_items.contains(item)) {
0738         return;
0739     }
0740 
0741     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
0742     attached->setView(nullptr);
0743     attached->setIndex(-1);
0744 
0745     disconnect(attached, nullptr, this, nullptr);
0746     disconnect(item, nullptr, this, nullptr);
0747     disconnect(item, nullptr, m_view, nullptr);
0748 
0749     QQuickItem *separatorItem = m_leadingSeparators.take(item);
0750     if (separatorItem) {
0751         separatorItem->deleteLater();
0752     }
0753     separatorItem = m_trailingSeparators.take(item);
0754     if (separatorItem) {
0755         separatorItem->deleteLater();
0756     }
0757 
0758     if (QQuickItem *header = attached->globalHeader()) {
0759         header->setVisible(false);
0760         header->setParentItem(item);
0761         separatorItem = m_leadingSeparators.take(header);
0762         if (separatorItem) {
0763             separatorItem->deleteLater();
0764         }
0765         separatorItem = m_trailingSeparators.take(header);
0766         if (separatorItem) {
0767             separatorItem->deleteLater();
0768         }
0769     }
0770     if (QQuickItem *footer = attached->globalFooter()) {
0771         footer->setVisible(false);
0772         footer->setParentItem(item);
0773         separatorItem = m_leadingSeparators.take(footer);
0774         if (separatorItem) {
0775             separatorItem->deleteLater();
0776         }
0777         separatorItem = m_trailingSeparators.take(footer);
0778         if (separatorItem) {
0779             separatorItem->deleteLater();
0780         }
0781     }
0782 
0783     const int index = m_items.indexOf(item);
0784     m_items.removeAll(item);
0785     // We are connected not only to destroyed but also to lambdas
0786     disconnect(item, nullptr, this, nullptr);
0787     updateVisibleItems();
0788     m_shouldAnimate = true;
0789     m_view->polish();
0790 
0791     if (index <= m_view->currentIndex()) {
0792         m_view->setCurrentIndex(m_items.isEmpty() ? 0 : qBound(0, index - 1, m_items.count() - 1));
0793     }
0794     Q_EMIT m_view->countChanged();
0795 }
0796 
0797 QQuickItem *ContentItem::ensureLeadingSeparator(QQuickItem *item)
0798 {
0799     QQuickItem *separatorItem = m_leadingSeparators.value(item);
0800 
0801     if (!separatorItem) {
0802         separatorItem = qobject_cast<QQuickItem *>(
0803             QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
0804         if (separatorItem) {
0805             separatorItem->setParent(this);
0806             separatorItem->setParentItem(item);
0807             separatorItem->setZ(9999);
0808             separatorItem->setProperty("column", QVariant::fromValue(item));
0809             separatorItem->setProperty("view", QVariant::fromValue(m_view));
0810             QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->completeCreate();
0811             m_leadingSeparators[item] = separatorItem;
0812         }
0813     }
0814 
0815     return separatorItem;
0816 }
0817 
0818 QQuickItem *ContentItem::ensureTrailingSeparator(QQuickItem *item)
0819 {
0820     QQuickItem *separatorItem = m_trailingSeparators.value(item);
0821 
0822     if (!separatorItem) {
0823         separatorItem = qobject_cast<QQuickItem *>(
0824             QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
0825         if (separatorItem) {
0826             separatorItem->setParent(this);
0827             separatorItem->setParentItem(item);
0828             separatorItem->setZ(9999);
0829             separatorItem->setProperty("column", QVariant::fromValue(item));
0830             QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->completeCreate();
0831             m_trailingSeparators[item] = separatorItem;
0832         }
0833     }
0834 
0835     return separatorItem;
0836 }
0837 
0838 void ContentItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
0839 {
0840     if (m_creationInProgress) {
0841         QQuickItem::itemChange(change, value);
0842         return;
0843     }
0844     switch (change) {
0845     case QQuickItem::ItemChildAddedChange: {
0846         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(value.item, true));
0847         attached->setView(m_view);
0848 
0849         // connect(attached, &ColumnViewAttached::fillWidthChanged, m_view, &ColumnView::polish);
0850         connect(attached, &ColumnViewAttached::fillWidthChanged, this, [this] {
0851             m_view->polish();
0852         });
0853         connect(attached, &ColumnViewAttached::reservedSpaceChanged, m_view, &ColumnView::polish);
0854 
0855         value.item->setVisible(true);
0856 
0857         if (!m_items.contains(value.item)) {
0858             connect(value.item, &QQuickItem::widthChanged, m_view, &ColumnView::polish);
0859             QQuickItem *item = value.item;
0860             m_items << item;
0861             connect(item, &QObject::destroyed, this, [this, item]() {
0862                 m_view->removeItem(item);
0863             });
0864         }
0865 
0866         if (m_view->separatorVisible()) {
0867             ensureLeadingSeparator(value.item);
0868         }
0869 
0870         m_shouldAnimate = true;
0871         m_view->polish();
0872         Q_EMIT m_view->countChanged();
0873         break;
0874     }
0875     case QQuickItem::ItemChildRemovedChange: {
0876         forgetItem(value.item);
0877         break;
0878     }
0879     case QQuickItem::ItemVisibleHasChanged:
0880         updateVisibleItems();
0881         if (value.boolValue) {
0882             m_view->polish();
0883         }
0884         break;
0885     default:
0886         break;
0887     }
0888     QQuickItem::itemChange(change, value);
0889 }
0890 
0891 void ContentItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
0892 {
0893     updateVisibleItems();
0894     QQuickItem::geometryChange(newGeometry, oldGeometry);
0895 }
0896 
0897 void ContentItem::syncItemsOrder()
0898 {
0899     if (m_items == childItems()) {
0900         return;
0901     }
0902 
0903     m_items = childItems();
0904     // NOTE: polish() here sometimes gets indefinitely delayed and items changing order isn't seen
0905     layoutItems();
0906 }
0907 
0908 void ContentItem::updateRepeaterModel()
0909 {
0910     if (!sender()) {
0911         return;
0912     }
0913 
0914     QObject *modelObj = sender()->property("model").value<QObject *>();
0915 
0916     if (!modelObj) {
0917         m_models.remove(sender());
0918         return;
0919     }
0920 
0921     if (m_models[sender()]) {
0922         disconnect(m_models[sender()], nullptr, this, nullptr);
0923     }
0924 
0925     m_models[sender()] = modelObj;
0926 
0927     QAbstractItemModel *qaim = qobject_cast<QAbstractItemModel *>(modelObj);
0928 
0929     if (qaim) {
0930         connect(qaim, &QAbstractItemModel::rowsMoved, this, &ContentItem::syncItemsOrder);
0931 
0932     } else {
0933         connect(modelObj, SIGNAL(childrenChanged()), this, SLOT(syncItemsOrder()));
0934     }
0935 }
0936 
0937 void ContentItem::connectHeader(QQuickItem *oldHeader, QQuickItem *newHeader)
0938 {
0939     if (oldHeader) {
0940         disconnect(oldHeader, nullptr, this, nullptr);
0941         oldHeader->setParentItem(nullptr);
0942     }
0943     if (newHeader) {
0944         connect(newHeader, &QQuickItem::heightChanged, this, &ContentItem::layoutItems);
0945         connect(newHeader, &QQuickItem::visibleChanged, this, &ContentItem::layoutItems);
0946         newHeader->setParentItem(m_globalHeaderParent);
0947     }
0948 }
0949 
0950 void ContentItem::connectFooter(QQuickItem *oldFooter, QQuickItem *newFooter)
0951 {
0952     if (oldFooter) {
0953         disconnect(oldFooter, nullptr, this, nullptr);
0954         oldFooter->setParentItem(nullptr);
0955     }
0956     if (newFooter) {
0957         connect(newFooter, &QQuickItem::heightChanged, this, &ContentItem::layoutItems);
0958         connect(newFooter, &QQuickItem::visibleChanged, this, &ContentItem::layoutItems);
0959         newFooter->setParentItem(m_globalFooterParent);
0960     }
0961 }
0962 
0963 ColumnView::ColumnView(QQuickItem *parent)
0964     : QQuickItem(parent)
0965     , m_contentItem(nullptr)
0966 {
0967     // NOTE: this is to *not* trigger itemChange
0968     m_contentItem = new ContentItem(this);
0969     // Prevent interactions outside of ColumnView bounds, and let it act as a viewport.
0970     setClip(true);
0971     setAcceptedMouseButtons(Qt::LeftButton | Qt::BackButton | Qt::ForwardButton);
0972     setAcceptTouchEvents(false); // Relies on synthetized mouse events
0973     setFiltersChildMouseEvents(true);
0974 
0975     connect(m_contentItem->m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
0976         m_moving = false;
0977         Q_EMIT movingChanged();
0978     });
0979     connect(m_contentItem, &ContentItem::widthChanged, this, &ColumnView::contentWidthChanged);
0980     connect(m_contentItem, &ContentItem::xChanged, this, &ColumnView::contentXChanged);
0981 
0982     connect(this, &ColumnView::activeFocusChanged, this, [this]() {
0983         if (hasActiveFocus() && m_currentItem) {
0984             m_currentItem->forceActiveFocus();
0985         }
0986     });
0987     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(this, true));
0988     attached->setView(this);
0989     attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(m_contentItem, true));
0990     attached->setView(this);
0991 }
0992 
0993 ColumnView::~ColumnView()
0994 {
0995 }
0996 
0997 ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const
0998 {
0999     return m_contentItem->m_columnResizeMode;
1000 }
1001 
1002 void ColumnView::setColumnResizeMode(ColumnResizeMode mode)
1003 {
1004     if (m_contentItem->m_columnResizeMode == mode) {
1005         return;
1006     }
1007 
1008     m_contentItem->m_columnResizeMode = mode;
1009     if (mode == SingleColumn && m_currentItem) {
1010         m_contentItem->m_viewAnchorItem = m_currentItem;
1011     }
1012     m_contentItem->m_shouldAnimate = false;
1013     polish();
1014     Q_EMIT columnResizeModeChanged();
1015 }
1016 
1017 qreal ColumnView::columnWidth() const
1018 {
1019     return m_contentItem->m_columnWidth;
1020 }
1021 
1022 void ColumnView::setColumnWidth(qreal width)
1023 {
1024     // Always forget the internal binding when the user sets anything, even the same value
1025     disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, nullptr);
1026 
1027     if (m_contentItem->m_columnWidth == width) {
1028         return;
1029     }
1030 
1031     m_contentItem->m_columnWidth = width;
1032     m_contentItem->m_shouldAnimate = false;
1033     polish();
1034     Q_EMIT columnWidthChanged();
1035 }
1036 
1037 int ColumnView::currentIndex() const
1038 {
1039     return m_currentIndex;
1040 }
1041 
1042 void ColumnView::setCurrentIndex(int index)
1043 {
1044     if (m_currentIndex == index || index < -1 || index >= m_contentItem->m_items.count()) {
1045         return;
1046     }
1047 
1048     m_currentIndex = index;
1049 
1050     if (index == -1) {
1051         m_currentItem.clear();
1052 
1053     } else {
1054         m_currentItem = m_contentItem->m_items[index];
1055         Q_ASSERT(m_currentItem);
1056         m_currentItem->forceActiveFocus();
1057 
1058         // If the current item is not on view, scroll
1059         QRectF mappedCurrent = m_currentItem->mapRectToItem(this, QRectF(QPointF(0, 0), m_currentItem->size()));
1060 
1061         if (m_contentItem->m_slideAnim->state() == QAbstractAnimation::Running) {
1062             mappedCurrent.moveLeft(mappedCurrent.left() + m_contentItem->x() + m_contentItem->m_slideAnim->endValue().toInt());
1063         }
1064 
1065         // m_contentItem->m_slideAnim->stop();
1066 
1067         QRectF contentsRect(m_contentItem->m_leftPinnedSpace, //
1068                             0,
1069                             width() - m_contentItem->m_rightPinnedSpace - m_contentItem->m_leftPinnedSpace,
1070                             height());
1071 
1072         if (!m_mouseDown) {
1073             if (!contentsRect.contains(mappedCurrent)) {
1074                 m_contentItem->m_viewAnchorItem = m_currentItem;
1075                 if (qApp->layoutDirection() == Qt::RightToLeft) {
1076                     m_contentItem->animateX(-m_currentItem->x() - m_currentItem->width() + width());
1077                 } else {
1078                     m_contentItem->animateX(-m_currentItem->x() + m_contentItem->m_leftPinnedSpace);
1079                 }
1080             } else {
1081                 m_contentItem->snapToItem();
1082             }
1083         }
1084     }
1085 
1086     Q_EMIT currentIndexChanged();
1087     Q_EMIT currentItemChanged();
1088 }
1089 
1090 QQuickItem *ColumnView::currentItem()
1091 {
1092     return m_currentItem;
1093 }
1094 
1095 QList<QObject *> ColumnView::visibleItems() const
1096 {
1097     return m_contentItem->m_visibleItems;
1098 }
1099 
1100 QQuickItem *ColumnView::leadingVisibleItem() const
1101 {
1102     if (m_contentItem->m_visibleItems.isEmpty()) {
1103         return nullptr;
1104     }
1105 
1106     return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.first());
1107 }
1108 
1109 QQuickItem *ColumnView::trailingVisibleItem() const
1110 {
1111     if (m_contentItem->m_visibleItems.isEmpty()) {
1112         return nullptr;
1113     }
1114 
1115     return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.last());
1116 }
1117 
1118 int ColumnView::count() const
1119 {
1120     return m_contentItem->m_items.count();
1121 }
1122 
1123 qreal ColumnView::topPadding() const
1124 {
1125     return m_topPadding;
1126 }
1127 
1128 void ColumnView::setTopPadding(qreal padding)
1129 {
1130     if (padding == m_topPadding) {
1131         return;
1132     }
1133 
1134     m_topPadding = padding;
1135     polish();
1136     Q_EMIT topPaddingChanged();
1137 }
1138 
1139 qreal ColumnView::bottomPadding() const
1140 {
1141     return m_bottomPadding;
1142 }
1143 
1144 void ColumnView::setBottomPadding(qreal padding)
1145 {
1146     if (padding == m_bottomPadding) {
1147         return;
1148     }
1149 
1150     m_bottomPadding = padding;
1151     polish();
1152     Q_EMIT bottomPaddingChanged();
1153 }
1154 
1155 QQuickItem *ColumnView::contentItem() const
1156 {
1157     return m_contentItem;
1158 }
1159 
1160 int ColumnView::scrollDuration() const
1161 {
1162     return m_contentItem->m_slideAnim->duration();
1163 }
1164 
1165 void ColumnView::setScrollDuration(int duration)
1166 {
1167     disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, nullptr);
1168 
1169     if (m_contentItem->m_slideAnim->duration() == duration) {
1170         return;
1171     }
1172 
1173     m_contentItem->m_slideAnim->setDuration(duration);
1174     Q_EMIT scrollDurationChanged();
1175 }
1176 
1177 bool ColumnView::separatorVisible() const
1178 {
1179     return m_separatorVisible;
1180 }
1181 
1182 void ColumnView::setSeparatorVisible(bool visible)
1183 {
1184     if (visible == m_separatorVisible) {
1185         return;
1186     }
1187 
1188     m_separatorVisible = visible;
1189 
1190     Q_EMIT separatorVisibleChanged();
1191 }
1192 
1193 bool ColumnView::dragging() const
1194 {
1195     return m_dragging;
1196 }
1197 
1198 bool ColumnView::moving() const
1199 {
1200     return m_moving;
1201 }
1202 
1203 qreal ColumnView::contentWidth() const
1204 {
1205     return m_contentItem->width();
1206 }
1207 
1208 qreal ColumnView::contentX() const
1209 {
1210     return -m_contentItem->x();
1211 }
1212 
1213 void ColumnView::setContentX(qreal x) const
1214 {
1215     m_contentItem->setX(qRound(-x));
1216 }
1217 
1218 bool ColumnView::interactive() const
1219 {
1220     return m_interactive;
1221 }
1222 
1223 void ColumnView::setInteractive(bool interactive)
1224 {
1225     if (m_interactive == interactive) {
1226         return;
1227     }
1228 
1229     m_interactive = interactive;
1230 
1231     if (!m_interactive) {
1232         if (m_dragging) {
1233             m_dragging = false;
1234             Q_EMIT draggingChanged();
1235         }
1236 
1237         m_contentItem->snapToItem();
1238         setKeepMouseGrab(false);
1239     }
1240 
1241     Q_EMIT interactiveChanged();
1242 }
1243 
1244 bool ColumnView::acceptsMouse() const
1245 {
1246     return m_acceptsMouse;
1247 }
1248 
1249 void ColumnView::setAcceptsMouse(bool accepts)
1250 {
1251     if (m_acceptsMouse == accepts) {
1252         return;
1253     }
1254 
1255     m_acceptsMouse = accepts;
1256 
1257     if (!m_acceptsMouse) {
1258         if (m_dragging) {
1259             m_dragging = false;
1260             Q_EMIT draggingChanged();
1261         }
1262 
1263         m_contentItem->snapToItem();
1264         setKeepMouseGrab(false);
1265     }
1266 
1267     Q_EMIT acceptsMouseChanged();
1268 }
1269 
1270 void ColumnView::addItem(QQuickItem *item)
1271 {
1272     insertItem(m_contentItem->m_items.length(), item);
1273 }
1274 
1275 void ColumnView::insertItem(int pos, QQuickItem *item)
1276 {
1277     if (!item || m_contentItem->m_items.contains(item)) {
1278         return;
1279     }
1280 
1281     m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1282 
1283     connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1284         removeItem(item);
1285     });
1286     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1287     attached->setOriginalParent(item->parentItem());
1288     attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1289     item->setParentItem(m_contentItem);
1290 
1291     item->forceActiveFocus();
1292 
1293     if (attached->globalHeader()) {
1294         m_contentItem->connectHeader(nullptr, attached->globalHeader());
1295     }
1296     if (attached->globalFooter()) {
1297         m_contentItem->connectFooter(nullptr, attached->globalFooter());
1298     }
1299     connect(attached, &ColumnViewAttached::globalHeaderChanged, m_contentItem, &ContentItem::connectHeader);
1300     connect(attached, &ColumnViewAttached::globalFooterChanged, m_contentItem, &ContentItem::connectFooter);
1301 
1302     // Animate shift to new item.
1303     m_contentItem->m_shouldAnimate = true;
1304     m_contentItem->layoutItems();
1305     Q_EMIT contentChildrenChanged();
1306 
1307     // In order to keep the same current item we need to increase the current index if displaced
1308     // NOTE: just updating m_currentIndex does *not* update currentItem (which is what we need atm) while setCurrentIndex will update also currentItem
1309     if (m_currentIndex >= pos) {
1310         ++m_currentIndex;
1311         Q_EMIT currentIndexChanged();
1312     }
1313 
1314     Q_EMIT itemInserted(pos, item);
1315 }
1316 
1317 void ColumnView::replaceItem(int pos, QQuickItem *item)
1318 {
1319     if (pos < 0 || pos >= m_contentItem->m_items.length()) {
1320         qCWarning(KirigamiLog) << "Position" << pos << "passed to ColumnView::replaceItem is out of range.";
1321         return;
1322     }
1323 
1324     if (!item) {
1325         qCWarning(KirigamiLog) << "Null item passed to ColumnView::replaceItem.";
1326         return;
1327     }
1328 
1329     QQuickItem *oldItem = m_contentItem->m_items[pos];
1330 
1331     // In order to keep the same current item we need to increase the current index if displaced
1332     if (m_currentIndex >= pos) {
1333         setCurrentIndex(m_currentIndex - 1);
1334     }
1335 
1336     m_contentItem->forgetItem(oldItem);
1337     oldItem->setVisible(false);
1338 
1339     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(oldItem, false));
1340 
1341     if (attached && attached->shouldDeleteOnRemove()) {
1342         oldItem->deleteLater();
1343     } else {
1344         oldItem->setParentItem(attached ? attached->originalParent() : nullptr);
1345     }
1346 
1347     Q_EMIT itemRemoved(oldItem);
1348 
1349     if (!m_contentItem->m_items.contains(item)) {
1350         m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1351 
1352         connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1353             removeItem(item);
1354         });
1355         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1356         attached->setOriginalParent(item->parentItem());
1357         attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1358         item->setParentItem(m_contentItem);
1359 
1360         if (attached->globalHeader()) {
1361             m_contentItem->connectHeader(nullptr, attached->globalHeader());
1362         }
1363         if (attached->globalFooter()) {
1364             m_contentItem->connectFooter(nullptr, attached->globalFooter());
1365         }
1366         connect(attached, &ColumnViewAttached::globalHeaderChanged, m_contentItem, &ContentItem::connectHeader);
1367         connect(attached, &ColumnViewAttached::globalFooterChanged, m_contentItem, &ContentItem::connectFooter);
1368 
1369         if (m_currentIndex >= pos) {
1370             ++m_currentIndex;
1371             Q_EMIT currentIndexChanged();
1372         }
1373 
1374         Q_EMIT itemInserted(pos, item);
1375     }
1376 
1377     // Disable animation so replacement happens immediately.
1378     m_contentItem->m_shouldAnimate = false;
1379     m_contentItem->layoutItems();
1380     Q_EMIT contentChildrenChanged();
1381 }
1382 
1383 void ColumnView::moveItem(int from, int to)
1384 {
1385     if (m_contentItem->m_items.isEmpty() //
1386         || from < 0 || from >= m_contentItem->m_items.length() //
1387         || to < 0 || to >= m_contentItem->m_items.length()) {
1388         return;
1389     }
1390 
1391     m_contentItem->m_items.move(from, to);
1392     m_contentItem->m_shouldAnimate = true;
1393 
1394     if (from == m_currentIndex) {
1395         m_currentIndex = to;
1396         Q_EMIT currentIndexChanged();
1397     } else if (from < m_currentIndex && to > m_currentIndex) {
1398         --m_currentIndex;
1399         Q_EMIT currentIndexChanged();
1400     } else if (from > m_currentIndex && to <= m_currentIndex) {
1401         ++m_currentIndex;
1402         Q_EMIT currentIndexChanged();
1403     }
1404 
1405     polish();
1406 }
1407 
1408 QQuickItem *ColumnView::removeItem(QQuickItem *item)
1409 {
1410     if (m_contentItem->m_items.isEmpty() || !m_contentItem->m_items.contains(item)) {
1411         return nullptr;
1412     }
1413 
1414     const int index = m_contentItem->m_items.indexOf(item);
1415 
1416     // In order to keep the same current item we need to increase the current index if displaced
1417     if (m_currentIndex >= index) {
1418         setCurrentIndex(m_currentIndex - 1);
1419     }
1420 
1421     m_contentItem->forgetItem(item);
1422     item->setVisible(false);
1423 
1424     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, false));
1425 
1426     if (attached && attached->shouldDeleteOnRemove()) {
1427         item->deleteLater();
1428     } else {
1429         item->setParentItem(attached ? attached->originalParent() : nullptr);
1430     }
1431 
1432     Q_EMIT contentChildrenChanged();
1433     Q_EMIT itemRemoved(item);
1434 
1435     return item;
1436 }
1437 
1438 QQuickItem *ColumnView::removeItem(const int index)
1439 {
1440     if (m_contentItem->m_items.isEmpty() || index < 0 || index >= count()) {
1441         return nullptr;
1442     } else {
1443         return removeItem(m_contentItem->m_items[index]);
1444     }
1445 }
1446 
1447 QQuickItem *ColumnView::removeItem(const QVariant &item)
1448 {
1449     if (item.canConvert<QQuickItem *>()) {
1450         return removeItem(item.value<QQuickItem *>());
1451     } else if (item.canConvert<int>()) {
1452         return removeItem(item.toInt());
1453     } else {
1454         return nullptr;
1455     }
1456 }
1457 
1458 QQuickItem *ColumnView::pop(const QVariant &item)
1459 {
1460     if (item.canConvert<QQuickItem *>()) {
1461         return pop(item.value<QQuickItem *>());
1462     } else if (item.canConvert<int>()) {
1463         return pop(item.toInt());
1464     } else if (item.isNull()) {
1465         return pop();
1466     }
1467     return nullptr;
1468 }
1469 QQuickItem *ColumnView::pop(QQuickItem *item)
1470 {
1471     QQuickItem *removed = nullptr;
1472 
1473     while (!m_contentItem->m_items.isEmpty() && m_contentItem->m_items.last() != item) {
1474         removed = removeItem(m_contentItem->m_items.last());
1475     }
1476     return removed;
1477 }
1478 
1479 QQuickItem *ColumnView::pop(const int index)
1480 {
1481     if (index >= 0 && index < count() - 1) {
1482         return pop(m_contentItem->m_items.at(index));
1483     } else if (index == -1) {
1484         return pop(nullptr);
1485     }
1486     return nullptr;
1487 }
1488 
1489 QQuickItem *ColumnView::pop()
1490 {
1491     if (count() > 0) {
1492         return removeItem(count() - 1);
1493     }
1494     return nullptr;
1495 }
1496 
1497 void ColumnView::clear()
1498 {
1499     // Don't do an iterator on a list that gets progressively destroyed, treat it as a stack
1500     while (!m_contentItem->m_items.isEmpty()) {
1501         QQuickItem *item = m_contentItem->m_items.first();
1502         removeItem(item);
1503     }
1504 
1505     m_contentItem->m_items.clear();
1506     Q_EMIT contentChildrenChanged();
1507 }
1508 
1509 bool ColumnView::containsItem(QQuickItem *item)
1510 {
1511     return m_contentItem->m_items.contains(item);
1512 }
1513 
1514 QQuickItem *ColumnView::itemAt(qreal x, qreal y)
1515 {
1516     return m_contentItem->childAt(x, y);
1517 }
1518 
1519 ColumnViewAttached *ColumnView::qmlAttachedProperties(QObject *object)
1520 {
1521     return new ColumnViewAttached(object);
1522 }
1523 
1524 void ColumnView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1525 {
1526     m_contentItem->setY(m_topPadding);
1527     m_contentItem->setHeight(newGeometry.height() - m_topPadding - m_bottomPadding);
1528     m_contentItem->m_shouldAnimate = false;
1529     polish();
1530 
1531     m_contentItem->updateVisibleItems();
1532     QQuickItem::geometryChange(newGeometry, oldGeometry);
1533 }
1534 
1535 bool ColumnView::childMouseEventFilter(QQuickItem *item, QEvent *event)
1536 {
1537     if (!m_interactive || item == m_contentItem) {
1538         return QQuickItem::childMouseEventFilter(item, event);
1539     }
1540 
1541     switch (event->type()) {
1542     case QEvent::MouseButtonPress: {
1543         QMouseEvent *me = static_cast<QMouseEvent *>(event);
1544 
1545         if (me->button() != Qt::LeftButton) {
1546             return false;
1547         }
1548 
1549         // On press, we set the current index of the view to the root item
1550         QQuickItem *candidateItem = item;
1551         while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1552             candidateItem = candidateItem->parentItem();
1553         }
1554         if (int idx = m_contentItem->m_items.indexOf(candidateItem); idx >= 0 && candidateItem->parentItem() == m_contentItem) {
1555             setCurrentIndex(idx);
1556         }
1557 
1558         // if !m_acceptsMouse we don't drag with mouse
1559         if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1560             event->setAccepted(false);
1561             return false;
1562         }
1563 
1564         m_contentItem->m_slideAnim->stop();
1565         if (item->property("preventStealing").toBool()) {
1566             m_contentItem->snapToItem();
1567             return false;
1568         }
1569         m_oldMouseX = m_startMouseX = mapFromItem(item, me->position()).x();
1570         m_oldMouseY = m_startMouseY = mapFromItem(item, me->position()).y();
1571 
1572         m_mouseDown = true;
1573         me->setAccepted(false);
1574         setKeepMouseGrab(false);
1575 
1576         break;
1577     }
1578     case QEvent::MouseMove: {
1579         QMouseEvent *me = static_cast<QMouseEvent *>(event);
1580 
1581         if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1582             return false;
1583         }
1584 
1585         if (!(me->buttons() & Qt::LeftButton)) {
1586             return false;
1587         }
1588 
1589         const QPointF pos = mapFromItem(item, me->position());
1590 
1591         bool verticalScrollIntercepted = false;
1592 
1593         QQuickItem *candidateItem = item;
1594         while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1595             candidateItem = candidateItem->parentItem();
1596         }
1597         if (candidateItem->parentItem() == m_contentItem) {
1598             ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1599             if (attached->preventStealing()) {
1600                 return false;
1601             }
1602         }
1603 
1604         {
1605             ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1606 
1607             ScrollIntentionEvent scrollIntentionEvent;
1608             scrollIntentionEvent.delta = QPointF(pos.x() - m_oldMouseX, pos.y() - m_oldMouseY);
1609 
1610             Q_EMIT attached->scrollIntention(&scrollIntentionEvent);
1611 
1612             if (scrollIntentionEvent.accepted) {
1613                 verticalScrollIntercepted = true;
1614                 event->setAccepted(true);
1615             }
1616         }
1617 
1618         if ((!keepMouseGrab() && item->keepMouseGrab()) || item->property("preventStealing").toBool()) {
1619             m_contentItem->snapToItem();
1620             m_oldMouseX = pos.x();
1621             m_oldMouseY = pos.y();
1622             return false;
1623         }
1624 
1625         const bool wasDragging = m_dragging;
1626         // If a drag happened, start to steal all events, use startDragDistance * 2 to give time to widgets to take the mouse grab by themselves
1627         m_dragging = keepMouseGrab() || qAbs(mapFromItem(item, me->position()).x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 3;
1628 
1629         if (m_dragging != wasDragging) {
1630             m_moving = true;
1631             Q_EMIT movingChanged();
1632             Q_EMIT draggingChanged();
1633         }
1634 
1635         if (m_dragging) {
1636             m_contentItem->setBoundedX(m_contentItem->x() + pos.x() - m_oldMouseX);
1637         }
1638 
1639         m_contentItem->m_lastDragDelta = pos.x() - m_oldMouseX;
1640         m_oldMouseX = pos.x();
1641         m_oldMouseY = pos.y();
1642 
1643         setKeepMouseGrab(m_dragging);
1644         me->setAccepted(m_dragging);
1645 
1646         return m_dragging && !verticalScrollIntercepted;
1647     }
1648     case QEvent::MouseButtonRelease: {
1649         QMouseEvent *me = static_cast<QMouseEvent *>(event);
1650         if (item->property("preventStealing").toBool()) {
1651             return false;
1652         }
1653 
1654         if (me->button() == Qt::BackButton && m_currentIndex > 0) {
1655             setCurrentIndex(m_currentIndex - 1);
1656             me->accept();
1657             return true;
1658         } else if (me->button() == Qt::ForwardButton) {
1659             setCurrentIndex(m_currentIndex + 1);
1660             me->accept();
1661             return true;
1662         }
1663 
1664         if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1665             return false;
1666         }
1667 
1668         if (me->button() != Qt::LeftButton) {
1669             return false;
1670         }
1671 
1672         m_mouseDown = false;
1673 
1674         if (m_dragging) {
1675             m_contentItem->snapToItem();
1676             m_contentItem->m_lastDragDelta = 0;
1677             m_dragging = false;
1678             Q_EMIT draggingChanged();
1679         }
1680 
1681         event->accept();
1682 
1683         // if a drag happened, don't pass the event
1684         const bool block = keepMouseGrab();
1685         setKeepMouseGrab(false);
1686 
1687         me->setAccepted(block);
1688         return block;
1689     }
1690     default:
1691         break;
1692     }
1693 
1694     return QQuickItem::childMouseEventFilter(item, event);
1695 }
1696 
1697 void ColumnView::mousePressEvent(QMouseEvent *event)
1698 {
1699     if (!m_acceptsMouse && event->source() == Qt::MouseEventNotSynthesized) {
1700         event->setAccepted(false);
1701         return;
1702     }
1703 
1704     if (event->button() == Qt::BackButton || event->button() == Qt::ForwardButton) {
1705         event->accept();
1706         return;
1707     }
1708 
1709     if (!m_interactive) {
1710         return;
1711     }
1712 
1713     m_contentItem->snapToItem();
1714     m_oldMouseX = event->position().x();
1715     m_startMouseX = event->position().x();
1716     m_mouseDown = true;
1717     setKeepMouseGrab(false);
1718     event->accept();
1719 }
1720 
1721 void ColumnView::mouseMoveEvent(QMouseEvent *event)
1722 {
1723     if (event->buttons() & Qt::BackButton || event->buttons() & Qt::ForwardButton) {
1724         event->accept();
1725         return;
1726     }
1727 
1728     if (!m_interactive) {
1729         return;
1730     }
1731 
1732     const bool wasDragging = m_dragging;
1733     // Same startDragDistance * 2 as the event filter
1734     m_dragging = keepMouseGrab() || qAbs(event->position().x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 2;
1735     if (m_dragging != wasDragging) {
1736         m_moving = true;
1737         Q_EMIT movingChanged();
1738         Q_EMIT draggingChanged();
1739     }
1740 
1741     setKeepMouseGrab(m_dragging);
1742 
1743     if (m_dragging) {
1744         m_contentItem->setBoundedX(m_contentItem->x() + event->pos().x() - m_oldMouseX);
1745     }
1746 
1747     m_contentItem->m_lastDragDelta = event->pos().x() - m_oldMouseX;
1748     m_oldMouseX = event->pos().x();
1749     event->accept();
1750 }
1751 
1752 void ColumnView::mouseReleaseEvent(QMouseEvent *event)
1753 {
1754     if (event->button() == Qt::BackButton && m_currentIndex > 0) {
1755         setCurrentIndex(m_currentIndex - 1);
1756         event->accept();
1757         return;
1758     } else if (event->button() == Qt::ForwardButton) {
1759         setCurrentIndex(m_currentIndex + 1);
1760         event->accept();
1761         return;
1762     }
1763 
1764     m_mouseDown = false;
1765 
1766     if (!m_interactive) {
1767         return;
1768     }
1769 
1770     m_contentItem->snapToItem();
1771     m_contentItem->m_lastDragDelta = 0;
1772 
1773     if (m_dragging) {
1774         m_dragging = false;
1775         Q_EMIT draggingChanged();
1776     }
1777 
1778     setKeepMouseGrab(false);
1779     event->accept();
1780 }
1781 
1782 void ColumnView::mouseUngrabEvent()
1783 {
1784     m_mouseDown = false;
1785 
1786     if (m_contentItem->m_slideAnim->state() != QAbstractAnimation::Running) {
1787         m_contentItem->snapToItem();
1788     }
1789     m_contentItem->m_lastDragDelta = 0;
1790 
1791     if (m_dragging) {
1792         m_dragging = false;
1793         Q_EMIT draggingChanged();
1794     }
1795 
1796     setKeepMouseGrab(false);
1797 }
1798 
1799 void ColumnView::classBegin()
1800 {
1801     auto syncColumnWidth = [this]() {
1802         m_contentItem->m_columnWidth = privateQmlComponentsPoolSelf->instance(qmlEngine(this))->m_units->gridUnit() * 20;
1803         Q_EMIT columnWidthChanged();
1804     };
1805 
1806     connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, syncColumnWidth);
1807     syncColumnWidth();
1808 
1809     auto syncDuration = [this]() {
1810         m_contentItem->m_slideAnim->setDuration(QmlComponentsPoolSingleton::instance(qmlEngine(this))->m_units->longDuration());
1811         Q_EMIT scrollDurationChanged();
1812     };
1813 
1814     connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, syncDuration);
1815     syncDuration();
1816 
1817     QQuickItem::classBegin();
1818 }
1819 
1820 void ColumnView::componentComplete()
1821 {
1822     m_complete = true;
1823     QQuickItem::componentComplete();
1824 }
1825 
1826 void ColumnView::updatePolish()
1827 {
1828     m_contentItem->layoutItems();
1829 }
1830 
1831 void ColumnView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1832 {
1833     switch (change) {
1834     case QQuickItem::ItemChildAddedChange:
1835         if (m_contentItem && value.item != m_contentItem && !value.item->inherits("QQuickRepeater")) {
1836             addItem(value.item);
1837         }
1838         break;
1839     default:
1840         break;
1841     }
1842     QQuickItem::itemChange(change, value);
1843 }
1844 
1845 void ColumnView::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item)
1846 {
1847     // This can only be called from QML
1848     ColumnView *view = static_cast<ColumnView *>(prop->object);
1849     if (!view) {
1850         return;
1851     }
1852 
1853     view->m_contentItem->m_items.append(item);
1854     connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1855         view->removeItem(item);
1856     });
1857 
1858     ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1859     attached->setOriginalParent(item->parentItem());
1860     attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1861 
1862     item->setParentItem(view->m_contentItem);
1863 }
1864 
1865 qsizetype ColumnView::contentChildren_count(QQmlListProperty<QQuickItem> *prop)
1866 {
1867     ColumnView *view = static_cast<ColumnView *>(prop->object);
1868     if (!view) {
1869         return 0;
1870     }
1871 
1872     return view->m_contentItem->m_items.count();
1873 }
1874 
1875 QQuickItem *ColumnView::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index)
1876 {
1877     ColumnView *view = static_cast<ColumnView *>(prop->object);
1878     if (!view) {
1879         return nullptr;
1880     }
1881 
1882     if (index < 0 || index >= view->m_contentItem->m_items.count()) {
1883         return nullptr;
1884     }
1885     return view->m_contentItem->m_items.value(index);
1886 }
1887 
1888 void ColumnView::contentChildren_clear(QQmlListProperty<QQuickItem> *prop)
1889 {
1890     ColumnView *view = static_cast<ColumnView *>(prop->object);
1891     if (!view) {
1892         return;
1893     }
1894 
1895     return view->m_contentItem->m_items.clear();
1896 }
1897 
1898 QQmlListProperty<QQuickItem> ColumnView::contentChildren()
1899 {
1900     return QQmlListProperty<QQuickItem>(this, //
1901                                         nullptr,
1902                                         contentChildren_append,
1903                                         contentChildren_count,
1904                                         contentChildren_at,
1905                                         contentChildren_clear);
1906 }
1907 
1908 void ColumnView::contentData_append(QQmlListProperty<QObject> *prop, QObject *object)
1909 {
1910     ColumnView *view = static_cast<ColumnView *>(prop->object);
1911     if (!view) {
1912         return;
1913     }
1914 
1915     view->m_contentData.append(object);
1916     QQuickItem *item = qobject_cast<QQuickItem *>(object);
1917     // exclude repeaters from layout
1918     if (item && item->inherits("QQuickRepeater")) {
1919         item->setParentItem(view);
1920 
1921         connect(item, SIGNAL(modelChanged()), view->m_contentItem, SLOT(updateRepeaterModel()));
1922 
1923     } else if (item) {
1924         view->m_contentItem->m_items.append(item);
1925         connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1926             view->removeItem(item);
1927         });
1928 
1929         ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1930         attached->setOriginalParent(item->parentItem());
1931         attached->setShouldDeleteOnRemove(view->m_complete && !item->parentItem() && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1932 
1933         item->setParentItem(view->m_contentItem);
1934 
1935     } else {
1936         object->setParent(view);
1937     }
1938 }
1939 
1940 qsizetype ColumnView::contentData_count(QQmlListProperty<QObject> *prop)
1941 {
1942     ColumnView *view = static_cast<ColumnView *>(prop->object);
1943     if (!view) {
1944         return 0;
1945     }
1946 
1947     return view->m_contentData.count();
1948 }
1949 
1950 QObject *ColumnView::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
1951 {
1952     ColumnView *view = static_cast<ColumnView *>(prop->object);
1953     if (!view) {
1954         return nullptr;
1955     }
1956 
1957     if (index < 0 || index >= view->m_contentData.count()) {
1958         return nullptr;
1959     }
1960     return view->m_contentData.value(index);
1961 }
1962 
1963 void ColumnView::contentData_clear(QQmlListProperty<QObject> *prop)
1964 {
1965     ColumnView *view = static_cast<ColumnView *>(prop->object);
1966     if (!view) {
1967         return;
1968     }
1969 
1970     return view->m_contentData.clear();
1971 }
1972 
1973 QQmlListProperty<QObject> ColumnView::contentData()
1974 {
1975     return QQmlListProperty<QObject>(this, //
1976                                      nullptr,
1977                                      contentData_append,
1978                                      contentData_count,
1979                                      contentData_at,
1980                                      contentData_clear);
1981 }
1982 
1983 #include "moc_columnview.cpp"
1984 #include "moc_columnview_p.cpp"