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

KDE's Doxygen guidelines are available online.