Kirigami2

columnview.cpp
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 "platform/units.h"
20
21class QmlComponentsPoolSingleton
22{
23public:
24 QmlComponentsPoolSingleton()
25 {
26 }
27 static QmlComponentsPool *instance(QQmlEngine *engine);
28
29private:
30 QHash<QQmlEngine *, QmlComponentsPool *> m_instances;
31};
32
33Q_GLOBAL_STATIC(QmlComponentsPoolSingleton, privateQmlComponentsPoolSelf)
34
35QmlComponentsPool *QmlComponentsPoolSingleton::instance(QQmlEngine *engine)
36{
37 Q_ASSERT(engine);
38 auto componentPool = privateQmlComponentsPoolSelf->m_instances.value(engine);
39
40 if (componentPool) {
41 return componentPool;
42 }
43
44 componentPool = new QmlComponentsPool(engine);
45
46 const auto removePool = [engine]() {
47 // NB: do not derefence engine. it may be dangling already!
48 if (privateQmlComponentsPoolSelf) {
49 privateQmlComponentsPoolSelf->m_instances.remove(engine);
50 }
51 };
52 QObject::connect(engine, &QObject::destroyed, engine, removePool);
53 QObject::connect(componentPool, &QObject::destroyed, componentPool, removePool);
54
55 privateQmlComponentsPoolSelf->m_instances[engine] = componentPool;
56 return componentPool;
57}
58
59QmlComponentsPool::QmlComponentsPool(QQmlEngine *engine)
60 : QObject(engine)
61{
62 QQmlComponent component(engine);
63 component.loadFromModule("org.kde.kirigami.layouts.private", "ColumnViewSeparator");
64
65 m_instance = component.create();
66
67 if (component.isError()) {
68 qCWarning(KirigamiLayoutsLog) << component.errors();
69 }
70 Q_ASSERT(m_instance);
71 m_instance->setParent(this);
72
73 m_leadingSeparatorComponent = m_instance->property("leadingSeparator").value<QQmlComponent *>();
74 Q_ASSERT(m_leadingSeparatorComponent);
75
76 m_trailingSeparatorComponent = m_instance->property("trailingSeparator").value<QQmlComponent *>();
77 Q_ASSERT(m_trailingSeparatorComponent);
78
79 m_units = engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
80 Q_ASSERT(m_units);
81
82 connect(m_units, &Kirigami::Platform::Units::gridUnitChanged, this, &QmlComponentsPool::gridUnitChanged);
83 connect(m_units, &Kirigami::Platform::Units::longDurationChanged, this, &QmlComponentsPool::longDurationChanged);
84}
85
86QmlComponentsPool::~QmlComponentsPool()
87{
88}
89
90/////////
91
92ColumnViewAttached::ColumnViewAttached(QObject *parent)
93 : QObject(parent)
94{
95}
96
97ColumnViewAttached::~ColumnViewAttached()
98{
99}
100
101void ColumnViewAttached::setIndex(int index)
102{
103 if (!m_customFillWidth && m_view) {
104 const bool oldFillWidth = m_fillWidth;
105 m_fillWidth = index == m_view->count() - 1;
106 if (oldFillWidth != m_fillWidth) {
107 Q_EMIT fillWidthChanged();
108 }
109 }
110
111 if (index == m_index) {
112 return;
113 }
114
115 m_index = index;
116 Q_EMIT indexChanged();
117}
118
120{
121 return m_index;
122}
123
124void ColumnViewAttached::setFillWidth(bool fill)
125{
126 if (m_view) {
127 disconnect(m_view.data(), &ColumnView::countChanged, this, nullptr);
128 }
129 m_customFillWidth = true;
130
131 if (fill == m_fillWidth) {
132 return;
133 }
134
135 m_fillWidth = fill;
136 Q_EMIT fillWidthChanged();
137
138 if (m_view) {
139 m_view->polish();
140 }
141}
142
144{
145 return m_fillWidth;
146}
147
149{
150 return m_reservedSpace;
151}
152
153void ColumnViewAttached::setReservedSpace(qreal space)
154{
155 if (m_view) {
156 disconnect(m_view.data(), &ColumnView::columnWidthChanged, this, nullptr);
157 }
158 m_customReservedSpace = true;
159
160 if (qFuzzyCompare(space, m_reservedSpace)) {
161 return;
162 }
163
164 m_reservedSpace = space;
165 Q_EMIT reservedSpaceChanged();
166
167 if (m_view) {
168 m_view->polish();
169 }
170}
171
173{
174 return m_view;
175}
176
177void ColumnViewAttached::setView(ColumnView *view)
178{
179 if (view == m_view) {
180 return;
181 }
182
183 if (m_view) {
184 disconnect(m_view.data(), nullptr, this, nullptr);
185 }
186 m_view = view;
187
188 if (!m_customFillWidth && m_view) {
189 m_fillWidth = m_index == m_view->count() - 1;
190 connect(m_view.data(), &ColumnView::countChanged, this, [this]() {
191 m_fillWidth = m_index == m_view->count() - 1;
192 Q_EMIT fillWidthChanged();
193 });
194 }
195 if (!m_customReservedSpace && m_view) {
196 m_reservedSpace = m_view->columnWidth();
197 connect(m_view.data(), &ColumnView::columnWidthChanged, this, [this]() {
198 m_reservedSpace = m_view->columnWidth();
199 Q_EMIT reservedSpaceChanged();
200 });
201 }
202
203 Q_EMIT viewChanged();
204}
205
206QQuickItem *ColumnViewAttached::originalParent() const
207{
208 return m_originalParent;
209}
210
211void ColumnViewAttached::setOriginalParent(QQuickItem *parent)
212{
213 m_originalParent = parent;
214}
215
216bool ColumnViewAttached::shouldDeleteOnRemove() const
217{
218 return m_shouldDeleteOnRemove;
219}
220
221void ColumnViewAttached::setShouldDeleteOnRemove(bool del)
222{
223 m_shouldDeleteOnRemove = del;
224}
225
227{
228 return m_preventStealing;
229}
230
231void ColumnViewAttached::setPreventStealing(bool prevent)
232{
233 if (prevent == m_preventStealing) {
234 return;
235 }
236
237 m_preventStealing = prevent;
238 Q_EMIT preventStealingChanged();
239}
240
241bool ColumnViewAttached::isPinned() const
242{
243 return m_pinned;
244}
245
246void ColumnViewAttached::setPinned(bool pinned)
247{
248 if (pinned == m_pinned) {
249 return;
250 }
251
252 m_pinned = pinned;
253
254 Q_EMIT pinnedChanged();
255
256 if (m_view) {
257 m_view->polish();
258 }
259}
260
262{
263 return m_inViewport;
264}
265
266void ColumnViewAttached::setInViewport(bool inViewport)
267{
268 if (m_inViewport == inViewport) {
269 return;
270 }
271
272 m_inViewport = inViewport;
273
274 Q_EMIT inViewportChanged();
275}
276
277QQuickItem *ColumnViewAttached::globalHeader() const
278{
279 return m_globalHeader;
280}
281
282void ColumnViewAttached::setGlobalHeader(QQuickItem *header)
283{
284 if (header == m_globalHeader) {
285 return;
286 }
287
288 QQuickItem *oldHeader = m_globalHeader;
289 if (m_globalHeader) {
290 disconnect(m_globalHeader, nullptr, this, nullptr);
291 }
292
293 m_globalHeader = header;
294
295 connect(header, &QObject::destroyed, this, [this, header]() {
296 globalHeaderChanged(header, nullptr);
297 });
298
299 Q_EMIT globalHeaderChanged(oldHeader, header);
300}
301
302QQuickItem *ColumnViewAttached::globalFooter() const
303{
304 return m_globalFooter;
305}
306
307void ColumnViewAttached::setGlobalFooter(QQuickItem *footer)
308{
309 if (footer == m_globalFooter) {
310 return;
311 }
312
313 QQuickItem *oldFooter = m_globalFooter;
314 if (m_globalFooter) {
315 disconnect(m_globalFooter, nullptr, this, nullptr);
316 }
317
318 m_globalFooter = footer;
319
320 connect(footer, &QObject::destroyed, this, [this, footer]() {
321 globalFooterChanged(footer, nullptr);
322 });
323
324 Q_EMIT globalFooterChanged(oldFooter, footer);
325}
326
327/////////
328
329ContentItem::ContentItem(ColumnView *parent)
330 : QQuickItem(parent)
331 , m_view(parent)
332{
333 m_globalHeaderParent = new QQuickItem(this);
334 m_globalFooterParent = new QQuickItem(this);
335
336 setFlags(flags() | ItemIsFocusScope);
337 m_slideAnim = new QPropertyAnimation(this);
338 m_slideAnim->setTargetObject(this);
339 m_slideAnim->setPropertyName("x");
340 // NOTE: the duration will be taken from kirigami units upon classBegin
341 m_slideAnim->setDuration(0);
342 m_slideAnim->setEasingCurve(QEasingCurve(QEasingCurve::OutExpo));
343 connect(m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
344 if (!m_view->currentItem()) {
345 m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
346 } else {
347 QRectF mapped = m_view->currentItem()->mapRectToItem(m_view, QRectF(QPointF(0, 0), m_view->currentItem()->size()));
348 if (!QRectF(QPointF(0, 0), m_view->size()).intersects(mapped)) {
349 m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
350 }
351 }
352 });
353
354 connect(this, &QQuickItem::xChanged, this, &ContentItem::layoutPinnedItems);
355 m_creationInProgress = false;
356}
357
358ContentItem::~ContentItem()
359{
360}
361
362void ContentItem::setBoundedX(qreal x)
363{
364 if (!parentItem()) {
365 return;
366 }
367 m_slideAnim->stop();
368 setX(qRound(qBound(qMin(0.0, -width() + parentItem()->width()), x, 0.0)));
369}
370
371void ContentItem::animateX(qreal newX)
372{
373 if (!parentItem()) {
374 return;
375 }
376
377 const qreal to = qRound(qBound(qMin(0.0, -width() + parentItem()->width()), newX, 0.0));
378
379 m_slideAnim->stop();
380 m_slideAnim->setStartValue(x());
381 m_slideAnim->setEndValue(to);
382 m_slideAnim->start();
383}
384
385void ContentItem::snapToItem()
386{
387 QQuickItem *firstItem = childAt(viewportLeft(), height() / 2);
388 if (!firstItem) {
389 return;
390 }
391 QQuickItem *nextItem = childAt(firstItem->x() + firstItem->width() + 1, height() / 2);
392
393 // need to make the last item visible?
394 if (nextItem && //
395 ((m_view->dragging() && m_lastDragDelta < 0) //
396 || (!m_view->dragging() //
397 && (width() - viewportRight()) < (viewportLeft() - firstItem->x())))) {
398 m_viewAnchorItem = nextItem;
399 animateX(-nextItem->x() + m_leftPinnedSpace);
400
401 // The first one found?
402 } else if ((m_view->dragging() && m_lastDragDelta >= 0) //
403 || (!m_view->dragging() && (viewportLeft() <= (firstItem->x() + (firstItem->width() / 2)))) //
404 || !nextItem) {
405 m_viewAnchorItem = firstItem;
406 animateX(-firstItem->x() + m_leftPinnedSpace);
407
408 // the second?
409 } else {
410 m_viewAnchorItem = nextItem;
411 animateX(-nextItem->x() + m_leftPinnedSpace);
412 }
413}
414
415qreal ContentItem::viewportLeft() const
416{
417 return -x() + m_leftPinnedSpace;
418}
419
420qreal ContentItem::viewportRight() const
421{
422 return -x() + m_view->width() - m_rightPinnedSpace;
423}
424
425qreal ContentItem::childWidth(QQuickItem *child)
426{
427 if (!parentItem()) {
428 return 0.0;
429 }
430
431 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
432
433 if (m_columnResizeMode == ColumnView::SingleColumn) {
434 return qRound(parentItem()->width());
435
436 } else if (attached->fillWidth()) {
437 if (m_view->count() == 1) {
438 // single column
439 return qRound(parentItem()->width());
440 }
441
442 return qRound(qBound(m_columnWidth, (parentItem()->width() - attached->reservedSpace()), std::max(m_columnWidth, parentItem()->width())));
443
444 } else if (m_columnResizeMode == ColumnView::FixedColumns) {
445 return qRound(qMin(parentItem()->width(), m_columnWidth));
446
447 // DynamicColumns
448 } else {
449 // TODO:look for Layout size hints
450 qreal width = child->implicitWidth();
451
452 if (width < 1.0) {
453 width = m_columnWidth;
454 }
455
456 return qRound(qMin(m_view->width(), width));
457 }
458}
459
460void ContentItem::layoutItems()
461{
462 setY(m_view->topPadding());
463 setHeight(m_view->height() - m_view->topPadding() - m_view->bottomPadding());
464
465 qreal implicitWidth = 0;
466 qreal implicitHeight = 0;
467 qreal partialWidth = 0;
468 int i = 0;
469 m_leftPinnedSpace = 0;
470 m_rightPinnedSpace = 0;
471
472 bool reverse = qApp->layoutDirection() == Qt::RightToLeft;
473 auto it = !reverse ? m_items.begin() : m_items.end();
474 int increment = reverse ? -1 : +1;
475 auto lastPos = reverse ? m_items.begin() : m_items.end();
476
477 for (; it != lastPos; it += increment) {
478 // for (QQuickItem *child : std::as_const(m_items)) {
479 QQuickItem *child = reverse ? *(it - 1) : *it;
480 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
481 if (child == m_globalHeaderParent || child == m_globalFooterParent) {
482 continue;
483 }
484
485 if (child->isVisible()) {
486 if (attached->isPinned() && m_view->columnResizeMode() != ColumnView::SingleColumn) {
487 QQuickItem *sep = nullptr;
488 int sepWidth = 0;
489 if (m_view->separatorVisible()) {
490 sep = ensureTrailingSeparator(child);
491 sepWidth = (sep ? sep->width() : 0);
492 }
493 const qreal width = childWidth(child);
494 const qreal widthDiff = std::max(0.0, m_view->width() - child->width()); // it's possible for the view width to be smaller than the child width
495 const qreal pageX = std::clamp(partialWidth, -x(), -x() + widthDiff);
496 qreal headerHeight = .0;
497 qreal footerHeight = .0;
498 if (QQuickItem *header = attached->globalHeader()) {
499 headerHeight = header->isVisible() ? header->height() : .0;
500 header->setWidth(width + sepWidth);
501 header->setPosition(QPointF(pageX, .0));
502 header->setZ(2);
503 if (m_view->separatorVisible()) {
504 QQuickItem *sep = ensureTrailingSeparator(header);
505 sep->setProperty("inToolBar", true);
506 }
507 }
508 if (QQuickItem *footer = attached->globalFooter()) {
509 footerHeight = footer->isVisible() ? footer->height() : .0;
510 footer->setWidth(width + sepWidth);
511 footer->setPosition(QPointF(pageX, height() - footerHeight));
512 footer->setZ(2);
513 if (m_view->separatorVisible()) {
514 QQuickItem *sep = ensureTrailingSeparator(footer);
515 sep->setProperty("inToolBar", true);
516 }
517 }
518
519 child->setSize(QSizeF(width + sepWidth, height() - headerHeight - footerHeight));
520 child->setPosition(QPointF(pageX, headerHeight));
521 child->setZ(1);
522
523 if (partialWidth <= -x()) {
524 m_leftPinnedSpace = qMax(m_leftPinnedSpace, width);
525 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
526 m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
527 }
528
529 partialWidth += width;
530
531 } else {
532 const qreal width = childWidth(child);
533 qreal headerHeight = .0;
534 qreal footerHeight = .0;
535 if (QQuickItem *header = attached->globalHeader(); header && qmlEngine(header)) {
536 if (m_view->separatorVisible()) {
537 QQuickItem *sep = ensureLeadingSeparator(header);
538 sep->setProperty("inToolBar", true);
539 }
540 headerHeight = header->isVisible() ? header->height() : .0;
541 header->setWidth(width);
542 header->setPosition(QPointF(partialWidth, .0));
543 header->setZ(1);
544 auto it = m_trailingSeparators.find(header);
545 if (it != m_trailingSeparators.end()) {
546 it.value()->deleteLater();
547 m_trailingSeparators.erase(it);
548 }
549 }
550 if (QQuickItem *footer = attached->globalFooter(); footer && qmlEngine(footer)) {
551 if (m_view->separatorVisible()) {
552 QQuickItem *sep = ensureLeadingSeparator(footer);
553 sep->setProperty("inToolBar", true);
554 }
555 footerHeight = footer->isVisible() ? footer->height() : .0;
556 footer->setWidth(width);
557 footer->setPosition(QPointF(partialWidth, height() - footerHeight));
558 footer->setZ(1);
559 auto it = m_trailingSeparators.find(footer);
560 if (it != m_trailingSeparators.end()) {
561 it.value()->deleteLater();
562 m_trailingSeparators.erase(it);
563 }
564 }
565
566 child->setSize(QSizeF(width, height() - headerHeight - footerHeight));
567
568 auto it = m_trailingSeparators.find(child);
569 if (it != m_trailingSeparators.end()) {
570 it.value()->deleteLater();
571 m_trailingSeparators.erase(it);
572 }
573 child->setPosition(QPointF(partialWidth, headerHeight));
574 child->setZ(0);
575
576 partialWidth += child->width();
577 }
578 }
579
580 if (reverse) {
581 attached->setIndex(m_items.count() - (++i));
582 } else {
583 attached->setIndex(i++);
584 }
585
586 implicitWidth += child->implicitWidth();
587
588 implicitHeight = qMax(implicitHeight, child->implicitHeight());
589 }
590
591 setWidth(partialWidth);
592
593 setImplicitWidth(implicitWidth);
594 setImplicitHeight(implicitHeight);
595
596 m_view->setImplicitWidth(implicitWidth);
597 m_view->setImplicitHeight(implicitHeight + m_view->topPadding() + m_view->bottomPadding());
598
599 const qreal newContentX = m_viewAnchorItem ? -m_viewAnchorItem->x() : 0.0;
600 if (m_shouldAnimate) {
601 animateX(newContentX);
602 } else {
603 setBoundedX(newContentX);
604 }
605
606 updateVisibleItems();
607}
608
609void ContentItem::layoutPinnedItems()
610{
611 if (m_view->columnResizeMode() == ColumnView::SingleColumn) {
612 return;
613 }
614
615 qreal partialWidth = 0;
616 m_leftPinnedSpace = 0;
617 m_rightPinnedSpace = 0;
618
619 for (QQuickItem *child : std::as_const(m_items)) {
620 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
621
622 if (child->isVisible()) {
623 if (attached->isPinned()) {
624 QQuickItem *sep = nullptr;
625 int sepWidth = 0;
626 if (m_view->separatorVisible()) {
627 sep = ensureTrailingSeparator(child);
628 sepWidth = (sep ? sep->width() : 0);
629 }
630
631 const qreal pageX = qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth);
632 qreal headerHeight = .0;
633 qreal footerHeight = .0;
634 if (QQuickItem *header = attached->globalHeader()) {
635 headerHeight = header->isVisible() ? header->height() : .0;
636 header->setPosition(QPointF(pageX, .0));
637 if (m_view->separatorVisible()) {
638 QQuickItem *sep = ensureTrailingSeparator(header);
639 sep->setProperty("inToolBar", true);
640 }
641 }
642 if (QQuickItem *footer = attached->globalFooter()) {
643 footerHeight = footer->isVisible() ? footer->height() : .0;
644 footer->setPosition(QPointF(pageX, height() - footerHeight));
645 if (m_view->separatorVisible()) {
646 QQuickItem *sep = ensureTrailingSeparator(footer);
647 sep->setProperty("inToolBar", true);
648 }
649 }
650 child->setPosition(QPointF(pageX, headerHeight));
651
652 if (partialWidth <= -x()) {
653 m_leftPinnedSpace = qMax(m_leftPinnedSpace, child->width() - sepWidth);
654 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
655 m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
656 }
657 }
658
659 partialWidth += child->width();
660 }
661 }
662}
663
664void ContentItem::updateVisibleItems()
665{
666 QList<QQuickItem *> newItems;
667
668 for (auto *item : std::as_const(m_items)) {
669 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
670
671 if (item->isVisible() && item->x() + x() < m_view->width() && item->x() + item->width() + x() > 0) {
672 newItems << item;
673 connect(item, &QObject::destroyed, this, [this, item] {
674 m_visibleItems.removeAll(item);
675 });
676 attached->setInViewport(true);
677 item->setEnabled(true);
678 if (attached->globalHeader()) {
679 attached->globalHeader()->setEnabled(true);
680 }
681 if (attached->globalFooter()) {
682 attached->globalFooter()->setEnabled(true);
683 }
684 } else {
685 attached->setInViewport(false);
686 item->setEnabled(false);
687 if (attached->globalHeader()) {
688 attached->globalHeader()->setEnabled(false);
689 }
690 if (attached->globalFooter()) {
691 attached->globalFooter()->setEnabled(false);
692 }
693 }
694 }
695
696 for (auto *item : std::as_const(m_visibleItems)) {
697 disconnect(item, &QObject::destroyed, this, nullptr);
698 }
699
700 const QQuickItem *oldLeadingVisibleItem = m_view->leadingVisibleItem();
701 const QQuickItem *oldTrailingVisibleItem = m_view->trailingVisibleItem();
702
703 if (newItems != m_visibleItems) {
704 m_visibleItems = newItems;
705 Q_EMIT m_view->visibleItemsChanged();
706 if (!m_visibleItems.isEmpty() && m_visibleItems.first() != oldLeadingVisibleItem) {
707 Q_EMIT m_view->leadingVisibleItemChanged();
708 }
709 if (!m_visibleItems.isEmpty() && m_visibleItems.last() != oldTrailingVisibleItem) {
710 Q_EMIT m_view->trailingVisibleItemChanged();
711 }
712 }
713}
714
715void ContentItem::forgetItem(QQuickItem *item)
716{
717 if (!m_items.contains(item)) {
718 return;
719 }
720
721 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
722 attached->setView(nullptr);
723 attached->setIndex(-1);
724
725 disconnect(attached, nullptr, this, nullptr);
726 disconnect(item, nullptr, this, nullptr);
727 disconnect(item, nullptr, m_view, nullptr);
728
729 QQuickItem *separatorItem = m_leadingSeparators.take(item);
730 if (separatorItem) {
731 separatorItem->deleteLater();
732 }
733 separatorItem = m_trailingSeparators.take(item);
734 if (separatorItem) {
735 separatorItem->deleteLater();
736 }
737
738 if (QQuickItem *header = attached->globalHeader()) {
739 header->setVisible(false);
740 header->setParentItem(item);
741 separatorItem = m_leadingSeparators.take(header);
742 if (separatorItem) {
743 separatorItem->deleteLater();
744 }
745 separatorItem = m_trailingSeparators.take(header);
746 if (separatorItem) {
747 separatorItem->deleteLater();
748 }
749 }
750 if (QQuickItem *footer = attached->globalFooter()) {
751 footer->setVisible(false);
752 footer->setParentItem(item);
753 separatorItem = m_leadingSeparators.take(footer);
754 if (separatorItem) {
755 separatorItem->deleteLater();
756 }
757 separatorItem = m_trailingSeparators.take(footer);
758 if (separatorItem) {
759 separatorItem->deleteLater();
760 }
761 }
762
763 const int index = m_items.indexOf(item);
764 m_items.removeAll(item);
765 // We are connected not only to destroyed but also to lambdas
766 disconnect(item, nullptr, this, nullptr);
767 updateVisibleItems();
768 m_shouldAnimate = true;
769 m_view->polish();
770
771 if (index <= m_view->currentIndex()) {
772 m_view->setCurrentIndex(m_items.isEmpty() ? 0 : qBound(0, index - 1, m_items.count() - 1));
773 }
774 Q_EMIT m_view->countChanged();
775}
776
777QQuickItem *ContentItem::ensureLeadingSeparator(QQuickItem *item)
778{
779 QQuickItem *separatorItem = m_leadingSeparators.value(item);
780
781 if (!separatorItem) {
782 separatorItem = qobject_cast<QQuickItem *>(
783 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
784 if (separatorItem) {
785 separatorItem->setParent(this);
786 separatorItem->setParentItem(item);
787 separatorItem->setZ(9999);
788 separatorItem->setProperty("column", QVariant::fromValue(item));
789 separatorItem->setProperty("view", QVariant::fromValue(m_view));
790 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->completeCreate();
791 m_leadingSeparators[item] = separatorItem;
792 }
793 }
794
795 return separatorItem;
796}
797
798QQuickItem *ContentItem::ensureTrailingSeparator(QQuickItem *item)
799{
800 QQuickItem *separatorItem = m_trailingSeparators.value(item);
801
802 if (!separatorItem) {
803 separatorItem = qobject_cast<QQuickItem *>(
804 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
805 if (separatorItem) {
806 separatorItem->setParent(this);
807 separatorItem->setParentItem(item);
808 separatorItem->setZ(9999);
809 separatorItem->setProperty("column", QVariant::fromValue(item));
810 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->completeCreate();
811 m_trailingSeparators[item] = separatorItem;
812 }
813 }
814
815 return separatorItem;
816}
817
818void ContentItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
819{
820 if (m_creationInProgress) {
821 QQuickItem::itemChange(change, value);
822 return;
823 }
824 switch (change) {
826 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(value.item, true));
827 attached->setView(m_view);
828
829 // connect(attached, &ColumnViewAttached::fillWidthChanged, m_view, &ColumnView::polish);
830 connect(attached, &ColumnViewAttached::fillWidthChanged, this, [this] {
831 m_view->polish();
832 });
833 connect(attached, &ColumnViewAttached::reservedSpaceChanged, m_view, &ColumnView::polish);
834
835 value.item->setVisible(true);
836
837 if (!m_items.contains(value.item)) {
839 QQuickItem *item = value.item;
840 m_items << item;
841 connect(item, &QObject::destroyed, this, [this, item]() {
842 m_view->removeItem(item);
843 });
844 }
845
846 if (m_view->separatorVisible()) {
847 ensureLeadingSeparator(value.item);
848 }
849
850 m_shouldAnimate = true;
851 m_view->polish();
852 Q_EMIT m_view->countChanged();
853 break;
854 }
856 forgetItem(value.item);
857 break;
858 }
860 updateVisibleItems();
861 if (value.boolValue) {
862 m_view->polish();
863 }
864 break;
865 default:
866 break;
867 }
868 QQuickItem::itemChange(change, value);
869}
870
871void ContentItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
872{
873 updateVisibleItems();
874 QQuickItem::geometryChange(newGeometry, oldGeometry);
875}
876
877void ContentItem::syncItemsOrder()
878{
879 if (m_items == childItems()) {
880 return;
881 }
882
883 m_items = childItems();
884 // NOTE: polish() here sometimes gets indefinitely delayed and items changing order isn't seen
885 layoutItems();
886}
887
888void ContentItem::updateRepeaterModel()
889{
890 if (!sender()) {
891 return;
892 }
893
894 QObject *modelObj = sender()->property("model").value<QObject *>();
895
896 if (!modelObj) {
897 m_models.remove(sender());
898 return;
899 }
900
901 if (m_models[sender()]) {
902 disconnect(m_models[sender()], nullptr, this, nullptr);
903 }
904
905 m_models[sender()] = modelObj;
906
907 QAbstractItemModel *qaim = qobject_cast<QAbstractItemModel *>(modelObj);
908
909 if (qaim) {
910 connect(qaim, &QAbstractItemModel::rowsMoved, this, &ContentItem::syncItemsOrder);
911
912 } else {
913 connect(modelObj, SIGNAL(childrenChanged()), this, SLOT(syncItemsOrder()));
914 }
915}
916
917void ContentItem::connectHeader(QQuickItem *oldHeader, QQuickItem *newHeader)
918{
919 if (oldHeader) {
920 disconnect(oldHeader, nullptr, this, nullptr);
921 oldHeader->setParentItem(nullptr);
922 }
923 if (newHeader) {
924 connect(newHeader, &QQuickItem::heightChanged, this, &ContentItem::layoutItems);
925 connect(newHeader, &QQuickItem::visibleChanged, this, &ContentItem::layoutItems);
926 newHeader->setParentItem(m_globalHeaderParent);
927 }
928}
929
930void ContentItem::connectFooter(QQuickItem *oldFooter, QQuickItem *newFooter)
931{
932 if (oldFooter) {
933 disconnect(oldFooter, nullptr, this, nullptr);
934 oldFooter->setParentItem(nullptr);
935 }
936 if (newFooter) {
937 connect(newFooter, &QQuickItem::heightChanged, this, &ContentItem::layoutItems);
938 connect(newFooter, &QQuickItem::visibleChanged, this, &ContentItem::layoutItems);
939 newFooter->setParentItem(m_globalFooterParent);
940 }
941}
942
943ColumnView::ColumnView(QQuickItem *parent)
944 : QQuickItem(parent)
945 , m_contentItem(nullptr)
946{
947 // NOTE: this is to *not* trigger itemChange
948 m_contentItem = new ContentItem(this);
949 // Prevent interactions outside of ColumnView bounds, and let it act as a viewport.
950 setClip(true);
951 setAcceptedMouseButtons(Qt::LeftButton | Qt::BackButton | Qt::ForwardButton);
952 setAcceptTouchEvents(false); // Relies on synthetized mouse events
953 setFiltersChildMouseEvents(true);
954
955 connect(m_contentItem->m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
956 m_moving = false;
957 Q_EMIT movingChanged();
958 });
959 connect(m_contentItem, &ContentItem::widthChanged, this, &ColumnView::contentWidthChanged);
960 connect(m_contentItem, &ContentItem::xChanged, this, &ColumnView::contentXChanged);
961
962 connect(this, &ColumnView::activeFocusChanged, this, [this]() {
963 if (hasActiveFocus() && m_currentItem) {
964 m_currentItem->forceActiveFocus();
965 }
966 });
967 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(this, true));
968 attached->setView(this);
969 attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(m_contentItem, true));
970 attached->setView(this);
971}
972
973ColumnView::~ColumnView()
974{
975}
976
977ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const
978{
979 return m_contentItem->m_columnResizeMode;
980}
981
982void ColumnView::setColumnResizeMode(ColumnResizeMode mode)
983{
984 if (m_contentItem->m_columnResizeMode == mode) {
985 return;
986 }
987
988 m_contentItem->m_columnResizeMode = mode;
989 if (mode == SingleColumn && m_currentItem) {
990 m_contentItem->m_viewAnchorItem = m_currentItem;
991 }
992 m_contentItem->m_shouldAnimate = false;
993 polish();
994 Q_EMIT columnResizeModeChanged();
995}
996
997qreal ColumnView::columnWidth() const
998{
999 return m_contentItem->m_columnWidth;
1000}
1001
1002void ColumnView::setColumnWidth(qreal width)
1003{
1004 // Always forget the internal binding when the user sets anything, even the same value
1005 disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, nullptr);
1006
1007 if (m_contentItem->m_columnWidth == width) {
1008 return;
1009 }
1010
1011 m_contentItem->m_columnWidth = width;
1012 m_contentItem->m_shouldAnimate = false;
1013 polish();
1014 Q_EMIT columnWidthChanged();
1015}
1016
1017int ColumnView::currentIndex() const
1018{
1019 return m_currentIndex;
1020}
1021
1022void ColumnView::setCurrentIndex(int index)
1023{
1024 if (m_currentIndex == index || index < -1 || index >= m_contentItem->m_items.count()) {
1025 return;
1026 }
1027
1028 m_currentIndex = index;
1029
1030 if (index == -1) {
1031 m_currentItem.clear();
1032
1033 } else {
1034 m_currentItem = m_contentItem->m_items[index];
1035 Q_ASSERT(m_currentItem);
1036 m_currentItem->forceActiveFocus();
1037
1038 // If the current item is not on view, scroll
1039 QRectF mappedCurrent = m_currentItem->mapRectToItem(this, QRectF(QPointF(0, 0), m_currentItem->size()));
1040
1041 if (m_contentItem->m_slideAnim->state() == QAbstractAnimation::Running) {
1042 mappedCurrent.moveLeft(mappedCurrent.left() + m_contentItem->x() + m_contentItem->m_slideAnim->endValue().toInt());
1043 }
1044
1045 // m_contentItem->m_slideAnim->stop();
1046
1047 QRectF contentsRect(m_contentItem->m_leftPinnedSpace, //
1048 0,
1049 width() - m_contentItem->m_rightPinnedSpace - m_contentItem->m_leftPinnedSpace,
1050 height());
1051
1052 if (!m_mouseDown) {
1053 if (!contentsRect.contains(mappedCurrent)) {
1054 m_contentItem->m_viewAnchorItem = m_currentItem;
1055 if (qApp->layoutDirection() == Qt::RightToLeft) {
1056 m_contentItem->animateX(-m_currentItem->x() - m_currentItem->width() + width());
1057 } else {
1058 m_contentItem->animateX(-m_currentItem->x() + m_contentItem->m_leftPinnedSpace);
1059 }
1060 } else {
1061 m_contentItem->snapToItem();
1062 }
1063 }
1064 }
1065
1066 Q_EMIT currentIndexChanged();
1067 Q_EMIT currentItemChanged();
1068}
1069
1071{
1072 return m_currentItem;
1073}
1074
1076{
1077 return m_contentItem->m_visibleItems;
1078}
1079
1081{
1082 if (m_contentItem->m_visibleItems.isEmpty()) {
1083 return nullptr;
1084 }
1085
1086 return m_contentItem->m_visibleItems.first();
1087}
1088
1090{
1091 if (m_contentItem->m_visibleItems.isEmpty()) {
1092 return nullptr;
1093 }
1094
1095 return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.last());
1096}
1097
1098int ColumnView::count() const
1099{
1100 return m_contentItem->m_items.count();
1101}
1102
1103qreal ColumnView::topPadding() const
1104{
1105 return m_topPadding;
1106}
1107
1108void ColumnView::setTopPadding(qreal padding)
1109{
1110 if (padding == m_topPadding) {
1111 return;
1112 }
1113
1114 m_topPadding = padding;
1115 polish();
1116 Q_EMIT topPaddingChanged();
1117}
1118
1119qreal ColumnView::bottomPadding() const
1120{
1121 return m_bottomPadding;
1122}
1123
1124void ColumnView::setBottomPadding(qreal padding)
1125{
1126 if (padding == m_bottomPadding) {
1127 return;
1128 }
1129
1130 m_bottomPadding = padding;
1131 polish();
1132 Q_EMIT bottomPaddingChanged();
1133}
1134
1136{
1137 return m_contentItem;
1138}
1139
1141{
1142 return m_contentItem->m_slideAnim->duration();
1143}
1144
1145void ColumnView::setScrollDuration(int duration)
1146{
1147 disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, nullptr);
1148
1149 if (m_contentItem->m_slideAnim->duration() == duration) {
1150 return;
1151 }
1152
1153 m_contentItem->m_slideAnim->setDuration(duration);
1154 Q_EMIT scrollDurationChanged();
1155}
1156
1158{
1159 return m_separatorVisible;
1160}
1161
1162void ColumnView::setSeparatorVisible(bool visible)
1163{
1164 if (visible == m_separatorVisible) {
1165 return;
1166 }
1167
1168 m_separatorVisible = visible;
1169
1170 Q_EMIT separatorVisibleChanged();
1171}
1172
1173bool ColumnView::dragging() const
1174{
1175 return m_dragging;
1176}
1177
1178bool ColumnView::moving() const
1179{
1180 return m_moving;
1181}
1182
1183qreal ColumnView::contentWidth() const
1184{
1185 return m_contentItem->width();
1186}
1187
1188qreal ColumnView::contentX() const
1189{
1190 return -m_contentItem->x();
1191}
1192
1193void ColumnView::setContentX(qreal x) const
1194{
1195 m_contentItem->setX(qRound(-x));
1196}
1197
1198bool ColumnView::interactive() const
1199{
1200 return m_interactive;
1201}
1202
1203void ColumnView::setInteractive(bool interactive)
1204{
1205 if (m_interactive == interactive) {
1206 return;
1207 }
1208
1209 m_interactive = interactive;
1210
1211 if (!m_interactive) {
1212 if (m_dragging) {
1213 m_dragging = false;
1214 Q_EMIT draggingChanged();
1215 }
1216
1217 m_contentItem->snapToItem();
1218 setKeepMouseGrab(false);
1219 }
1220
1221 Q_EMIT interactiveChanged();
1222}
1223
1224bool ColumnView::acceptsMouse() const
1225{
1226 return m_acceptsMouse;
1227}
1228
1229void ColumnView::setAcceptsMouse(bool accepts)
1230{
1231 if (m_acceptsMouse == accepts) {
1232 return;
1233 }
1234
1235 m_acceptsMouse = accepts;
1236
1237 if (!m_acceptsMouse) {
1238 if (m_dragging) {
1239 m_dragging = false;
1240 Q_EMIT draggingChanged();
1241 }
1242
1243 m_contentItem->snapToItem();
1244 setKeepMouseGrab(false);
1245 }
1246
1247 Q_EMIT acceptsMouseChanged();
1248}
1249
1251{
1252 insertItem(m_contentItem->m_items.length(), item);
1253}
1254
1256{
1257 if (!item || m_contentItem->m_items.contains(item)) {
1258 return;
1259 }
1260
1261 m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1262
1263 connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1264 removeItem(item);
1265 });
1266 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1267 attached->setOriginalParent(item->parentItem());
1268 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1269 item->setParentItem(m_contentItem);
1271
1272 item->forceActiveFocus();
1273
1274 if (attached->globalHeader()) {
1275 m_contentItem->connectHeader(nullptr, attached->globalHeader());
1276 }
1277 if (attached->globalFooter()) {
1278 m_contentItem->connectFooter(nullptr, attached->globalFooter());
1279 }
1280 connect(attached, &ColumnViewAttached::globalHeaderChanged, m_contentItem, &ContentItem::connectHeader);
1281 connect(attached, &ColumnViewAttached::globalFooterChanged, m_contentItem, &ContentItem::connectFooter);
1282
1283 // Animate shift to new item.
1284 m_contentItem->m_shouldAnimate = true;
1285 m_contentItem->layoutItems();
1286 Q_EMIT contentChildrenChanged();
1287
1288 // In order to keep the same current item we need to increase the current index if displaced
1289 // NOTE: just updating m_currentIndex does *not* update currentItem (which is what we need atm) while setCurrentIndex will update also currentItem
1290 if (m_currentIndex >= pos) {
1291 ++m_currentIndex;
1292 Q_EMIT currentIndexChanged();
1293 }
1294
1295 Q_EMIT itemInserted(pos, item);
1296}
1297
1299{
1300 if (pos < 0 || pos >= m_contentItem->m_items.length()) {
1301 qCWarning(KirigamiLayoutsLog) << "Position" << pos << "passed to ColumnView::replaceItem is out of range.";
1302 return;
1303 }
1304
1305 if (!item) {
1306 qCWarning(KirigamiLayoutsLog) << "Null item passed to ColumnView::replaceItem.";
1307 return;
1308 }
1309
1310 QQuickItem *oldItem = m_contentItem->m_items[pos];
1311
1312 // In order to keep the same current item we need to increase the current index if displaced
1313 if (m_currentIndex >= pos) {
1314 setCurrentIndex(m_currentIndex - 1);
1315 }
1316
1317 m_contentItem->forgetItem(oldItem);
1318 oldItem->setVisible(false);
1319
1320 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(oldItem, false));
1321
1322 if (attached && attached->shouldDeleteOnRemove()) {
1323 oldItem->deleteLater();
1324 } else {
1325 oldItem->setParentItem(attached ? attached->originalParent() : nullptr);
1326 }
1327
1328 Q_EMIT itemRemoved(oldItem);
1329
1330 if (!m_contentItem->m_items.contains(item)) {
1331 m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1332
1333 connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1334 removeItem(item);
1335 });
1336 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1337 attached->setOriginalParent(item->parentItem());
1338 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1339 item->setParentItem(m_contentItem);
1341
1342 if (attached->globalHeader()) {
1343 m_contentItem->connectHeader(nullptr, attached->globalHeader());
1344 }
1345 if (attached->globalFooter()) {
1346 m_contentItem->connectFooter(nullptr, attached->globalFooter());
1347 }
1348 connect(attached, &ColumnViewAttached::globalHeaderChanged, m_contentItem, &ContentItem::connectHeader);
1349 connect(attached, &ColumnViewAttached::globalFooterChanged, m_contentItem, &ContentItem::connectFooter);
1350
1351 if (m_currentIndex >= pos) {
1352 ++m_currentIndex;
1353 Q_EMIT currentIndexChanged();
1354 }
1355
1356 Q_EMIT itemInserted(pos, item);
1357 }
1358
1359 // Disable animation so replacement happens immediately.
1360 m_contentItem->m_shouldAnimate = false;
1361 m_contentItem->layoutItems();
1362 Q_EMIT contentChildrenChanged();
1363}
1364
1365void ColumnView::moveItem(int from, int to)
1366{
1367 if (m_contentItem->m_items.isEmpty() //
1368 || from < 0 || from >= m_contentItem->m_items.length() //
1369 || to < 0 || to >= m_contentItem->m_items.length()) {
1370 return;
1371 }
1372
1373 m_contentItem->m_items.move(from, to);
1374 m_contentItem->m_shouldAnimate = true;
1375
1376 if (from == m_currentIndex) {
1377 m_currentIndex = to;
1378 Q_EMIT currentIndexChanged();
1379 } else if (from < m_currentIndex && to > m_currentIndex) {
1380 --m_currentIndex;
1381 Q_EMIT currentIndexChanged();
1382 } else if (from > m_currentIndex && to <= m_currentIndex) {
1383 ++m_currentIndex;
1384 Q_EMIT currentIndexChanged();
1385 }
1386
1387 polish();
1388}
1389
1391{
1392 if (m_contentItem->m_items.isEmpty() || !m_contentItem->m_items.contains(item)) {
1393 return nullptr;
1394 }
1395
1396 const int index = m_contentItem->m_items.indexOf(item);
1397
1398 // In order to keep the same current item we need to increase the current index if displaced
1399 if (m_currentIndex >= index) {
1400 setCurrentIndex(m_currentIndex - 1);
1401 }
1402
1403 m_contentItem->forgetItem(item);
1404 item->setVisible(false);
1405
1406 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, false));
1407
1408 if (attached && attached->shouldDeleteOnRemove()) {
1409 item->deleteLater();
1410 } else {
1411 item->setParentItem(attached ? attached->originalParent() : nullptr);
1412 }
1413
1414 Q_EMIT contentChildrenChanged();
1415 Q_EMIT itemRemoved(item);
1416
1417 return item;
1418}
1419
1421{
1422 if (m_contentItem->m_items.isEmpty() || index < 0 || index >= count()) {
1423 return nullptr;
1424 } else {
1425 return removeItem(m_contentItem->m_items[index]);
1426 }
1427}
1428
1430{
1431 if (item.canConvert<QQuickItem *>()) {
1432 return removeItem(item.value<QQuickItem *>());
1433 } else if (item.canConvert<int>()) {
1434 return removeItem(item.toInt());
1435 } else {
1436 return nullptr;
1437 }
1438}
1439
1441{
1442 if (item.canConvert<QQuickItem *>()) {
1443 return pop(item.value<QQuickItem *>());
1444 } else if (item.canConvert<int>()) {
1445 return pop(item.toInt());
1446 } else if (item.isNull()) {
1447 return pop();
1448 }
1449 return nullptr;
1450}
1452{
1453 QQuickItem *removed = nullptr;
1454
1455 while (!m_contentItem->m_items.isEmpty() && m_contentItem->m_items.last() != item) {
1456 removed = removeItem(m_contentItem->m_items.last());
1457 }
1458 return removed;
1459}
1460
1462{
1463 if (index >= 0 && index < count() - 1) {
1464 return pop(m_contentItem->m_items.at(index));
1465 } else if (index == -1) {
1466 return pop(nullptr);
1467 }
1468 return nullptr;
1469}
1470
1472{
1473 if (count() > 0) {
1474 return removeItem(count() - 1);
1475 }
1476 return nullptr;
1477}
1478
1480{
1481 // Don't do an iterator on a list that gets progressively destroyed, treat it as a stack
1482 while (!m_contentItem->m_items.isEmpty()) {
1483 QQuickItem *item = m_contentItem->m_items.first();
1484 removeItem(item);
1485 }
1486
1487 m_contentItem->m_items.clear();
1488 Q_EMIT contentChildrenChanged();
1489}
1490
1492{
1493 return m_contentItem->m_items.contains(item);
1494}
1495
1497{
1498 return m_contentItem->childAt(x, y);
1499}
1500
1501ColumnViewAttached *ColumnView::qmlAttachedProperties(QObject *object)
1502{
1503 return new ColumnViewAttached(object);
1504}
1505
1506void ColumnView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1507{
1508 m_contentItem->setY(m_topPadding);
1509 m_contentItem->setHeight(newGeometry.height() - m_topPadding - m_bottomPadding);
1510 m_contentItem->m_shouldAnimate = false;
1511 polish();
1512
1513 m_contentItem->updateVisibleItems();
1514 QQuickItem::geometryChange(newGeometry, oldGeometry);
1515}
1516
1517bool ColumnView::childMouseEventFilter(QQuickItem *item, QEvent *event)
1518{
1519 if (!m_interactive || item == m_contentItem) {
1521 }
1522
1523 switch (event->type()) {
1525 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1526
1527 if (me->button() != Qt::LeftButton) {
1528 return false;
1529 }
1530
1531 // On press, we set the current index of the view to the root item
1532 QQuickItem *candidateItem = item;
1533 while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1534 candidateItem = candidateItem->parentItem();
1535 }
1536 if (int idx = m_contentItem->m_items.indexOf(candidateItem); idx >= 0 && candidateItem->parentItem() == m_contentItem) {
1537 setCurrentIndex(idx);
1538 }
1539
1540 // if !m_acceptsMouse we don't drag with mouse
1541 if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1542 event->setAccepted(false);
1543 return false;
1544 }
1545
1546 m_contentItem->m_slideAnim->stop();
1547 if (item->property("preventStealing").toBool()) {
1548 m_contentItem->snapToItem();
1549 return false;
1550 }
1551 m_oldMouseX = m_startMouseX = mapFromItem(item, me->position()).x();
1552 m_oldMouseY = m_startMouseY = mapFromItem(item, me->position()).y();
1553
1554 m_mouseDown = true;
1555 me->setAccepted(false);
1556 setKeepMouseGrab(false);
1557
1558 break;
1559 }
1560 case QEvent::MouseMove: {
1561 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1562
1563 if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1564 return false;
1565 }
1566
1567 if (!(me->buttons() & Qt::LeftButton)) {
1568 return false;
1569 }
1570
1571 // It's possible the synthetyzed mouse press event was not passed
1572 // but now we get the mouse move events, consider the first move
1573 // as the press
1574 if (!m_mouseDown) {
1575 m_mouseDown = true;
1576 m_oldMouseX = m_startMouseX = mapFromItem(item, me->position()).x();
1577 m_oldMouseY = m_startMouseY = mapFromItem(item, me->position()).y();
1578 return false;
1579 }
1580
1581 const QPointF pos = mapFromItem(item, me->position());
1582
1583 bool verticalScrollIntercepted = false;
1584
1585 QQuickItem *candidateItem = item;
1586 while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1587 candidateItem = candidateItem->parentItem();
1588 }
1589 if (candidateItem->parentItem() == m_contentItem) {
1590 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1591 if (attached->preventStealing()) {
1592 return false;
1593 }
1594 }
1595
1596 {
1597 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1598
1599 ScrollIntentionEvent scrollIntentionEvent;
1600 scrollIntentionEvent.delta = QPointF(pos.x() - m_oldMouseX, pos.y() - m_oldMouseY);
1601
1602 Q_EMIT attached->scrollIntention(&scrollIntentionEvent);
1603
1604 if (scrollIntentionEvent.accepted) {
1605 verticalScrollIntercepted = true;
1606 event->setAccepted(true);
1607 }
1608 }
1609
1610 if ((!keepMouseGrab() && (item->keepMouseGrab() || item->keepTouchGrab())) || item->property("preventStealing").toBool()) {
1611 m_contentItem->snapToItem();
1612 m_oldMouseX = pos.x();
1613 m_oldMouseY = pos.y();
1614 return false;
1615 }
1616
1617 const bool wasDragging = m_dragging;
1618 // If a drag happened, start to steal all events, use startDragDistance * 2 to give time to widgets to take the mouse grab by themselves
1619 m_dragging = keepMouseGrab() || qAbs(mapFromItem(item, me->position()).x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 3;
1620
1621 if (m_dragging != wasDragging) {
1622 m_moving = true;
1623 Q_EMIT movingChanged();
1624 Q_EMIT draggingChanged();
1625 }
1626
1627 if (m_dragging) {
1628 m_contentItem->setBoundedX(m_contentItem->x() + pos.x() - m_oldMouseX);
1629 }
1630
1631 m_contentItem->m_lastDragDelta = pos.x() - m_oldMouseX;
1632 m_oldMouseX = pos.x();
1633 m_oldMouseY = pos.y();
1634
1635 setKeepMouseGrab(m_dragging);
1636 me->setAccepted(m_dragging);
1637
1638 return m_dragging && !verticalScrollIntercepted;
1639 }
1641 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1642 if (item->property("preventStealing").toBool()) {
1643 return false;
1644 }
1645
1646 if (me->button() == Qt::BackButton && m_currentIndex > 0) {
1647 setCurrentIndex(m_currentIndex - 1);
1648 me->accept();
1649 return true;
1650 } else if (me->button() == Qt::ForwardButton) {
1651 setCurrentIndex(m_currentIndex + 1);
1652 me->accept();
1653 return true;
1654 }
1655
1656 if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1657 return false;
1658 }
1659
1660 if (me->button() != Qt::LeftButton) {
1661 return false;
1662 }
1663
1664 m_mouseDown = false;
1665
1666 if (m_dragging) {
1667 m_contentItem->snapToItem();
1668 m_contentItem->m_lastDragDelta = 0;
1669 m_dragging = false;
1670 Q_EMIT draggingChanged();
1671 }
1672
1673 event->accept();
1674
1675 // if a drag happened, don't pass the event
1676 const bool block = keepMouseGrab();
1677 setKeepMouseGrab(false);
1678
1679 me->setAccepted(block);
1680 return block;
1681 }
1682 default:
1683 break;
1684 }
1685
1687}
1688
1689void ColumnView::mousePressEvent(QMouseEvent *event)
1690{
1691 if (!m_acceptsMouse && event->source() == Qt::MouseEventNotSynthesized) {
1692 event->setAccepted(false);
1693 return;
1694 }
1695
1696 if (event->button() == Qt::BackButton || event->button() == Qt::ForwardButton) {
1697 event->accept();
1698 return;
1699 }
1700
1701 if (!m_interactive) {
1702 return;
1703 }
1704
1705 m_contentItem->snapToItem();
1706 m_oldMouseX = event->position().x();
1707 m_startMouseX = event->position().x();
1708 m_mouseDown = true;
1709 setKeepMouseGrab(false);
1710 event->accept();
1711}
1712
1713void ColumnView::mouseMoveEvent(QMouseEvent *event)
1714{
1715 if (event->buttons() & Qt::BackButton || event->buttons() & Qt::ForwardButton) {
1716 event->accept();
1717 return;
1718 }
1719
1720 if (!m_interactive) {
1721 return;
1722 }
1723
1724 const bool wasDragging = m_dragging;
1725 // Same startDragDistance * 2 as the event filter
1726 m_dragging = keepMouseGrab() || qAbs(event->position().x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 2;
1727 if (m_dragging != wasDragging) {
1728 m_moving = true;
1729 Q_EMIT movingChanged();
1730 Q_EMIT draggingChanged();
1731 }
1732
1733 setKeepMouseGrab(m_dragging);
1734
1735 if (m_dragging) {
1736 m_contentItem->setBoundedX(m_contentItem->x() + event->pos().x() - m_oldMouseX);
1737 }
1738
1739 m_contentItem->m_lastDragDelta = event->pos().x() - m_oldMouseX;
1740 m_oldMouseX = event->pos().x();
1741 event->accept();
1742}
1743
1744void ColumnView::mouseReleaseEvent(QMouseEvent *event)
1745{
1746 if (event->button() == Qt::BackButton && m_currentIndex > 0) {
1747 setCurrentIndex(m_currentIndex - 1);
1748 event->accept();
1749 return;
1750 } else if (event->button() == Qt::ForwardButton) {
1751 setCurrentIndex(m_currentIndex + 1);
1752 event->accept();
1753 return;
1754 }
1755
1756 m_mouseDown = false;
1757
1758 if (!m_interactive) {
1759 return;
1760 }
1761
1762 m_contentItem->snapToItem();
1763 m_contentItem->m_lastDragDelta = 0;
1764
1765 if (m_dragging) {
1766 m_dragging = false;
1767 Q_EMIT draggingChanged();
1768 }
1769
1770 setKeepMouseGrab(false);
1771 event->accept();
1772}
1773
1774void ColumnView::mouseUngrabEvent()
1775{
1776 m_mouseDown = false;
1777
1778 if (m_contentItem->m_slideAnim->state() != QAbstractAnimation::Running) {
1779 m_contentItem->snapToItem();
1780 }
1781 m_contentItem->m_lastDragDelta = 0;
1782
1783 if (m_dragging) {
1784 m_dragging = false;
1785 Q_EMIT draggingChanged();
1786 }
1787
1788 setKeepMouseGrab(false);
1789}
1790
1791void ColumnView::classBegin()
1792{
1793 auto syncColumnWidth = [this]() {
1794 m_contentItem->m_columnWidth = privateQmlComponentsPoolSelf->instance(qmlEngine(this))->m_units->gridUnit() * 20;
1795 Q_EMIT columnWidthChanged();
1796 };
1797
1798 connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, syncColumnWidth);
1799 syncColumnWidth();
1800
1801 auto syncDuration = [this]() {
1802 m_contentItem->m_slideAnim->setDuration(QmlComponentsPoolSingleton::instance(qmlEngine(this))->m_units->veryLongDuration());
1803 Q_EMIT scrollDurationChanged();
1804 };
1805
1806 connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, syncDuration);
1807 syncDuration();
1808
1810}
1811
1812void ColumnView::componentComplete()
1813{
1814 m_complete = true;
1816}
1817
1818void ColumnView::updatePolish()
1819{
1820 m_contentItem->layoutItems();
1821}
1822
1823void ColumnView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1824{
1825 switch (change) {
1827 if (m_contentItem && value.item != m_contentItem && !value.item->inherits("QQuickRepeater")) {
1828 addItem(value.item);
1829 }
1830 break;
1831 default:
1832 break;
1833 }
1834 QQuickItem::itemChange(change, value);
1835}
1836
1837void ColumnView::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item)
1838{
1839 // This can only be called from QML
1840 ColumnView *view = static_cast<ColumnView *>(prop->object);
1841 if (!view) {
1842 return;
1843 }
1844
1845 view->m_contentItem->m_items.append(item);
1846 connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1847 view->removeItem(item);
1848 });
1849
1850 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1851 attached->setOriginalParent(item->parentItem());
1852 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1853
1854 item->setParentItem(view->m_contentItem);
1855}
1856
1857qsizetype ColumnView::contentChildren_count(QQmlListProperty<QQuickItem> *prop)
1858{
1859 ColumnView *view = static_cast<ColumnView *>(prop->object);
1860 if (!view) {
1861 return 0;
1862 }
1863
1864 return view->m_contentItem->m_items.count();
1865}
1866
1867QQuickItem *ColumnView::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index)
1868{
1869 ColumnView *view = static_cast<ColumnView *>(prop->object);
1870 if (!view) {
1871 return nullptr;
1872 }
1873
1874 if (index < 0 || index >= view->m_contentItem->m_items.count()) {
1875 return nullptr;
1876 }
1877 return view->m_contentItem->m_items.value(index);
1878}
1879
1880void ColumnView::contentChildren_clear(QQmlListProperty<QQuickItem> *prop)
1881{
1882 ColumnView *view = static_cast<ColumnView *>(prop->object);
1883 if (!view) {
1884 return;
1885 }
1886
1887 return view->m_contentItem->m_items.clear();
1888}
1889
1891{
1892 return QQmlListProperty<QQuickItem>(this, //
1893 nullptr,
1894 contentChildren_append,
1895 contentChildren_count,
1896 contentChildren_at,
1897 contentChildren_clear);
1898}
1899
1900void ColumnView::contentData_append(QQmlListProperty<QObject> *prop, QObject *object)
1901{
1902 ColumnView *view = static_cast<ColumnView *>(prop->object);
1903 if (!view) {
1904 return;
1905 }
1906
1907 view->m_contentData.append(object);
1908 QQuickItem *item = qobject_cast<QQuickItem *>(object);
1909 // exclude repeaters from layout
1910 if (item && item->inherits("QQuickRepeater")) {
1911 item->setParentItem(view);
1912
1913 connect(item, SIGNAL(modelChanged()), view->m_contentItem, SLOT(updateRepeaterModel()));
1914
1915 } else if (item) {
1916 view->m_contentItem->m_items.append(item);
1917 connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1918 view->removeItem(item);
1919 });
1920
1921 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1922 attached->setOriginalParent(item->parentItem());
1923 attached->setShouldDeleteOnRemove(view->m_complete && !item->parentItem() && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1924
1925 item->setParentItem(view->m_contentItem);
1926
1927 } else {
1928 object->setParent(view);
1929 }
1930}
1931
1932qsizetype ColumnView::contentData_count(QQmlListProperty<QObject> *prop)
1933{
1934 ColumnView *view = static_cast<ColumnView *>(prop->object);
1935 if (!view) {
1936 return 0;
1937 }
1938
1939 return view->m_contentData.count();
1940}
1941
1942QObject *ColumnView::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
1943{
1944 ColumnView *view = static_cast<ColumnView *>(prop->object);
1945 if (!view) {
1946 return nullptr;
1947 }
1948
1949 if (index < 0 || index >= view->m_contentData.count()) {
1950 return nullptr;
1951 }
1952 return view->m_contentData.value(index);
1953}
1954
1955void ColumnView::contentData_clear(QQmlListProperty<QObject> *prop)
1956{
1957 ColumnView *view = static_cast<ColumnView *>(prop->object);
1958 if (!view) {
1959 return;
1960 }
1961
1962 return view->m_contentData.clear();
1963}
1964
1966{
1967 return QQmlListProperty<QObject>(this, //
1968 nullptr,
1969 contentData_append,
1970 contentData_count,
1971 contentData_at,
1972 contentData_clear);
1973}
1974
1975#include "moc_columnview.cpp"
1976#include "moc_columnview_p.cpp"
This is an attached property to every item that is inserted in the ColumnView, used to access the vie...
Definition columnview.h:39
qreal reservedSpace
When a column is fillWidth, it will keep reservedSpace amount of pixels from going to fill the full v...
Definition columnview.h:55
bool preventStealing
Like the same property of MouseArea, when this is true, the column view won't try to manage events by...
Definition columnview.h:62
bool inViewport
True if this column is at least partly visible in the ColumnView's viewport.
Definition columnview.h:79
bool pinned
If true the page will never go out of view, but will stay either at the right or left side of the Col...
Definition columnview.h:68
ColumnView * view
The view this column belongs to.
Definition columnview.h:73
bool fillWidth
If true, the column will expand to take the whole viewport space minus reservedSpace.
Definition columnview.h:50
int index
The index position of the column in the view, starting from 0.
Definition columnview.h:45
ColumnView is a container that lays out items horizontally in a row, when not all items fit in the Co...
Definition columnview.h:161
QQuickItem * removeItem(QQuickItem *item)
This method removes the specified item from the view.
QQmlListProperty< QQuickItem > contentChildren
Every column item the view contains.
Definition columnview.h:270
bool moving
True both when the user is dragging around with touch gestures the view contents or the view is anima...
Definition columnview.h:254
QQmlListProperty< QObject > contentData
every item declared inside the view, both visual and non-visual items
Definition columnview.h:274
void itemRemoved(QQuickItem *item)
An item has just been removed from the view.
QQuickItem * contentItem
The main content item of this view: it's the parent of the column items.
Definition columnview.h:198
int currentIndex
The position of the currently active column.
Definition columnview.h:188
void replaceItem(int pos, QQuickItem *item)
Replaces an item in the view at a given position with a new item.
QQuickItem * trailingVisibleItem
The last of visibleItems provided from convenience.
Definition columnview.h:243
qreal topPadding
The padding this will have at the top.
Definition columnview.h:213
int count
How many columns this view containsItem.
Definition columnview.h:183
void addItem(QQuickItem *item)
Pushes a new item at the end of the view.
QQuickItem * itemAt(qreal x, qreal y)
Returns the visible item containing the point x, y in content coordinates.
bool containsItem(QQuickItem *item)
void clear()
Removes every item in the view.
qreal columnWidth
The width of all columns when columnResizeMode is FixedColumns.
Definition columnview.h:179
QList< QQuickItem * > visibleItems
The list of all visible column items that are at least partially in the viewport at any given moment.
Definition columnview.h:233
Q_INVOKABLE QQuickItem * pop()
This method removes the last item from the view and returns it.
QQuickItem * currentItem
The currently active column.
Definition columnview.h:193
qreal contentX
The value of the horizontal scroll of the view, in pixels.
Definition columnview.h:203
qreal bottomPadding
The padding this will have at the bottom.
Definition columnview.h:218
QML_ELEMENTColumnResizeMode columnResizeMode
The strategy to follow while automatically resizing the columns, the enum can have the following valu...
Definition columnview.h:174
int scrollDuration
The duration for scrolling animations.
Definition columnview.h:223
bool dragging
True when the user is dragging around with touch gestures the view contents.
Definition columnview.h:249
qreal contentWidth
The compound width of all columns in the view.
Definition columnview.h:208
void moveItem(int from, int to)
Move an item inside the view.
bool acceptsMouse
True if the contents can be dragged also with mouse besides touch.
Definition columnview.h:264
QQuickItem * leadingVisibleItem
The first of visibleItems provided from convenience.
Definition columnview.h:238
void insertItem(int pos, QQuickItem *item)
Inserts a new item in the view at a given position.
bool interactive
True if it supports moving the contents by dragging.
Definition columnview.h:259
bool separatorVisible
True if columns should be visually separated by a separator line.
Definition columnview.h:228
void itemInserted(int position, QQuickItem *item)
A new item has been inserted.
A set of values to define semantically sizes and durations.
Definition units.h:80
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
KGuiItem del()
void rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
MouseButtonPress
void accept()
ObjectOwnership objectOwnership(QObject *object)
void append(QList< T > &&value)
void clear()
qsizetype count() const const
T value(qsizetype i) const const
Qt::MouseEventSource source() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void destroyed(QObject *obj)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
bool inherits(const char *className) const const
QObject * parent() const const
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
void setParent(QObject *parent)
bool setProperty(const char *name, QVariant &&value)
virtual void setAccepted(bool accepted) override
qreal x() const const
qreal y() const const
QQmlContext * contextForObject(const QObject *object)
T singletonInstance(QAnyStringView uri, QAnyStringView typeName)
QQuickItem(QQuickItem *parent)
void activeFocusChanged(bool)
QQuickItem * childAt(qreal x, qreal y) const const
virtual bool childMouseEventFilter(QQuickItem *item, QEvent *event)
virtual void classBegin() override
virtual void componentComplete() override
virtual bool contains(const QPointF &point) const const
void setEnabled(bool)
void forceActiveFocus()
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
virtual void itemChange(ItemChange change, const ItemChangeData &value)
bool keepMouseGrab() const const
bool keepTouchGrab() const const
QPointF mapFromItem(const QQuickItem *item, const QPointF &point) const const
void setParentItem(QQuickItem *parent)
void polish()
void setKeepMouseGrab(bool keep)
void setSize(const QSizeF &size)
bool isVisible() const const
void xChanged()
void setZ(qreal)
qreal height() const const
bool intersects(const QRectF &rectangle) const const
qreal left() const const
void moveLeft(qreal x)
Qt::MouseButton button() const const
Qt::MouseButtons buttons() const const
QPointF position() const const
RightToLeft
LeftButton
MouseEventNotSynthesized
QFuture< QtPrivate::MapResultType< Iterator, MapFunctor > > mapped(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool canConvert() const const
QVariant fromValue(T &&value)
bool isNull() const const
bool toBool() const const
int toInt(bool *ok) const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:49:27 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.