File indexing completed on 2024-05-05 12:19:30
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"