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