File indexing completed on 2024-04-28 15:27:41

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