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