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:
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(KirigamiLog)<<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::InOutQuad));
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 return qRound(qBound(m_columnWidth, (parentItem()->width() - attached->reservedSpace()), std::max(m_columnWidth, parentItem()->width())));
478
479 } else if (m_columnResizeMode == ColumnView::FixedColumns) {
480 return qRound(qMin(parentItem()->width(), m_columnWidth));
481
482 // DynamicColumns
483 } else {
484 // TODO:look for Layout size hints
485 qreal width = child->implicitWidth();
486
487 if (width < 1.0) {
488 width = m_columnWidth;
489 }
490
491 return qRound(qMin(m_view->width(), width));
492 }
493}
494
495void ContentItem::layoutItems()
496{
497 setY(m_view->topPadding());
498 setHeight(m_view->height() - m_view->topPadding() - m_view->bottomPadding());
499
500 qreal implicitWidth = 0;
501 qreal implicitHeight = 0;
502 qreal partialWidth = 0;
503 int i = 0;
504 m_leftPinnedSpace = 0;
505 m_rightPinnedSpace = 0;
506
507 bool reverse = qApp->layoutDirection() == Qt::RightToLeft;
508 auto it = !reverse ? m_items.begin() : m_items.end();
509 int increment = reverse ? -1 : +1;
510 auto lastPos = reverse ? m_items.begin() : m_items.end();
511
512 for (; it != lastPos; it += increment) {
513 // for (QQuickItem *child : std::as_const(m_items)) {
514 QQuickItem *child = reverse ? *(it - 1) : *it;
515 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
516 if (child == m_globalHeaderParent || child == m_globalFooterParent) {
517 continue;
518 }
519
520 if (child->isVisible()) {
521 if (attached->isPinned() && m_view->columnResizeMode() != ColumnView::SingleColumn) {
522 QQuickItem *sep = nullptr;
523 int sepWidth = 0;
524 if (m_view->separatorVisible()) {
525 sep = ensureTrailingSeparator(child);
526 sepWidth = (sep ? sep->width() : 0);
527 }
528 const qreal width = childWidth(child);
529 const qreal pageX = std::clamp(partialWidth, -x(), -x() + m_view->width() - child->width());
530 qreal headerHeight = .0;
531 qreal footerHeight = .0;
532 if (QQuickItem *header = attached->globalHeader()) {
533 headerHeight = header->isVisible() ? header->height() : .0;
534 header->setWidth(width + sepWidth);
535 header->setPosition(QPointF(pageX, .0));
536 header->setZ(2);
537 if (m_view->separatorVisible()) {
538 QQuickItem *sep = ensureTrailingSeparator(header);
539 sep->setProperty("inToolBar", true);
540 }
541 }
542 if (QQuickItem *footer = attached->globalFooter()) {
543 footerHeight = footer->isVisible() ? footer->height() : .0;
544 footer->setWidth(width + sepWidth);
545 footer->setPosition(QPointF(pageX, height() - footerHeight));
546 footer->setZ(2);
547 if (m_view->separatorVisible()) {
548 QQuickItem *sep = ensureTrailingSeparator(footer);
549 sep->setProperty("inToolBar", true);
550 }
551 }
552
553 child->setSize(QSizeF(width + sepWidth, height() - headerHeight - footerHeight));
554 child->setPosition(QPointF(pageX, headerHeight));
555 child->setZ(1);
556
557 if (partialWidth <= -x()) {
558 m_leftPinnedSpace = qMax(m_leftPinnedSpace, width);
559 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
560 m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
561 }
562
563 partialWidth += width;
564
565 } else {
566 const qreal width = childWidth(child);
567 qreal headerHeight = .0;
568 qreal footerHeight = .0;
569 if (QQuickItem *header = attached->globalHeader(); header && qmlEngine(header)) {
570 if (m_view->separatorVisible()) {
571 QQuickItem *sep = ensureLeadingSeparator(header);
572 sep->setProperty("inToolBar", true);
573 }
574 headerHeight = header->isVisible() ? header->height() : .0;
575 header->setWidth(width);
576 header->setPosition(QPointF(partialWidth, .0));
577 header->setZ(1);
578 auto it = m_trailingSeparators.find(header);
579 if (it != m_trailingSeparators.end()) {
580 it.value()->deleteLater();
581 m_trailingSeparators.erase(it);
582 }
583 }
584 if (QQuickItem *footer = attached->globalFooter(); footer && qmlEngine(footer)) {
585 if (m_view->separatorVisible()) {
586 QQuickItem *sep = ensureLeadingSeparator(footer);
587 sep->setProperty("inToolBar", true);
588 }
589 footerHeight = footer->isVisible() ? footer->height() : .0;
590 footer->setWidth(width);
591 footer->setPosition(QPointF(partialWidth, height() - footerHeight));
592 footer->setZ(1);
593 auto it = m_trailingSeparators.find(footer);
594 if (it != m_trailingSeparators.end()) {
595 it.value()->deleteLater();
596 m_trailingSeparators.erase(it);
597 }
598 }
599
600 child->setSize(QSizeF(width, height() - headerHeight - footerHeight));
601
602 auto it = m_trailingSeparators.find(child);
603 if (it != m_trailingSeparators.end()) {
604 it.value()->deleteLater();
605 m_trailingSeparators.erase(it);
606 }
607 child->setPosition(QPointF(partialWidth, headerHeight));
608 child->setZ(0);
609
610 partialWidth += child->width();
611 }
612 }
613
614 if (reverse) {
615 attached->setIndex(m_items.count() - (++i));
616 } else {
617 attached->setIndex(i++);
618 }
619
620 implicitWidth += child->implicitWidth();
621
622 implicitHeight = qMax(implicitHeight, child->implicitHeight());
623 }
624
625 setWidth(partialWidth);
626
627 setImplicitWidth(implicitWidth);
628 setImplicitHeight(implicitHeight);
629
630 m_view->setImplicitWidth(implicitWidth);
631 m_view->setImplicitHeight(implicitHeight + m_view->topPadding() + m_view->bottomPadding());
632
633 const qreal newContentX = m_viewAnchorItem ? -m_viewAnchorItem->x() : 0.0;
634 if (m_shouldAnimate) {
635 animateX(newContentX);
636 } else {
637 setBoundedX(newContentX);
638 }
639
640 updateVisibleItems();
641}
642
643void ContentItem::layoutPinnedItems()
644{
645 if (m_view->columnResizeMode() == ColumnView::SingleColumn) {
646 return;
647 }
648
649 qreal partialWidth = 0;
650 m_leftPinnedSpace = 0;
651 m_rightPinnedSpace = 0;
652
653 for (QQuickItem *child : std::as_const(m_items)) {
654 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
655
656 if (child->isVisible()) {
657 if (attached->isPinned()) {
658 QQuickItem *sep = nullptr;
659 int sepWidth = 0;
660 if (m_view->separatorVisible()) {
661 sep = ensureTrailingSeparator(child);
662 sepWidth = (sep ? sep->width() : 0);
663 }
664
665 const qreal pageX = qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth);
666 qreal headerHeight = .0;
667 qreal footerHeight = .0;
668 if (QQuickItem *header = attached->globalHeader()) {
669 headerHeight = header->isVisible() ? header->height() : .0;
670 header->setPosition(QPointF(pageX, .0));
671 if (m_view->separatorVisible()) {
672 QQuickItem *sep = ensureTrailingSeparator(header);
673 sep->setProperty("inToolBar", true);
674 }
675 }
676 if (QQuickItem *footer = attached->globalFooter()) {
677 footerHeight = footer->isVisible() ? footer->height() : .0;
678 footer->setPosition(QPointF(pageX, height() - footerHeight));
679 if (m_view->separatorVisible()) {
680 QQuickItem *sep = ensureTrailingSeparator(footer);
681 sep->setProperty("inToolBar", true);
682 }
683 }
684 child->setPosition(QPointF(pageX, headerHeight));
685
686 if (partialWidth <= -x()) {
687 m_leftPinnedSpace = qMax(m_leftPinnedSpace, child->width() - sepWidth);
688 } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
689 m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
690 }
691 }
692
693 partialWidth += child->width();
694 }
695 }
696}
697
698void ContentItem::updateVisibleItems()
699{
700 QList<QObject *> newItems;
701
702 for (auto *item : std::as_const(m_items)) {
703 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
704
705 if (item->isVisible() && item->x() + x() < m_view->width() && item->x() + item->width() + x() > 0) {
706 newItems << item;
707 connect(item, &QObject::destroyed, this, [this, item] {
708 m_visibleItems.removeAll(item);
709 });
710 attached->setInViewport(true);
711 } else {
712 attached->setInViewport(false);
713 }
714 }
715
716 for (auto *item : std::as_const(m_visibleItems)) {
717 disconnect(item, &QObject::destroyed, this, nullptr);
718 }
719
720 const QQuickItem *oldLeadingVisibleItem = m_view->leadingVisibleItem();
721 const QQuickItem *oldTrailingVisibleItem = m_view->trailingVisibleItem();
722
723 if (newItems != m_visibleItems) {
724 m_visibleItems = newItems;
725 Q_EMIT m_view->visibleItemsChanged();
726 if (!m_visibleItems.isEmpty() && m_visibleItems.first() != oldLeadingVisibleItem) {
727 Q_EMIT m_view->leadingVisibleItemChanged();
728 }
729 if (!m_visibleItems.isEmpty() && m_visibleItems.last() != oldTrailingVisibleItem) {
730 Q_EMIT m_view->trailingVisibleItemChanged();
731 }
732 }
733}
734
735void ContentItem::forgetItem(QQuickItem *item)
736{
737 if (!m_items.contains(item)) {
738 return;
739 }
740
741 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
742 attached->setView(nullptr);
743 attached->setIndex(-1);
744
745 disconnect(attached, nullptr, this, nullptr);
746 disconnect(item, nullptr, this, nullptr);
747 disconnect(item, nullptr, m_view, nullptr);
748
749 QQuickItem *separatorItem = m_leadingSeparators.take(item);
750 if (separatorItem) {
751 separatorItem->deleteLater();
752 }
753 separatorItem = m_trailingSeparators.take(item);
754 if (separatorItem) {
755 separatorItem->deleteLater();
756 }
757
758 if (QQuickItem *header = attached->globalHeader()) {
759 header->setVisible(false);
760 header->setParentItem(item);
761 separatorItem = m_leadingSeparators.take(header);
762 if (separatorItem) {
763 separatorItem->deleteLater();
764 }
765 separatorItem = m_trailingSeparators.take(header);
766 if (separatorItem) {
767 separatorItem->deleteLater();
768 }
769 }
770 if (QQuickItem *footer = attached->globalFooter()) {
771 footer->setVisible(false);
772 footer->setParentItem(item);
773 separatorItem = m_leadingSeparators.take(footer);
774 if (separatorItem) {
775 separatorItem->deleteLater();
776 }
777 separatorItem = m_trailingSeparators.take(footer);
778 if (separatorItem) {
779 separatorItem->deleteLater();
780 }
781 }
782
783 const int index = m_items.indexOf(item);
784 m_items.removeAll(item);
785 // We are connected not only to destroyed but also to lambdas
786 disconnect(item, nullptr, this, nullptr);
787 updateVisibleItems();
788 m_shouldAnimate = true;
789 m_view->polish();
790
791 if (index <= m_view->currentIndex()) {
792 m_view->setCurrentIndex(m_items.isEmpty() ? 0 : qBound(0, index - 1, m_items.count() - 1));
793 }
794 Q_EMIT m_view->countChanged();
795}
796
797QQuickItem *ContentItem::ensureLeadingSeparator(QQuickItem *item)
798{
799 QQuickItem *separatorItem = m_leadingSeparators.value(item);
800
801 if (!separatorItem) {
802 separatorItem = qobject_cast<QQuickItem *>(
803 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
804 if (separatorItem) {
805 separatorItem->setParent(this);
806 separatorItem->setParentItem(item);
807 separatorItem->setZ(9999);
808 separatorItem->setProperty("column", QVariant::fromValue(item));
809 separatorItem->setProperty("view", QVariant::fromValue(m_view));
810 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->completeCreate();
811 m_leadingSeparators[item] = separatorItem;
812 }
813 }
814
815 return separatorItem;
816}
817
818QQuickItem *ContentItem::ensureTrailingSeparator(QQuickItem *item)
819{
820 QQuickItem *separatorItem = m_trailingSeparators.value(item);
821
822 if (!separatorItem) {
823 separatorItem = qobject_cast<QQuickItem *>(
824 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
825 if (separatorItem) {
826 separatorItem->setParent(this);
827 separatorItem->setParentItem(item);
828 separatorItem->setZ(9999);
829 separatorItem->setProperty("column", QVariant::fromValue(item));
830 QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->completeCreate();
831 m_trailingSeparators[item] = separatorItem;
832 }
833 }
834
835 return separatorItem;
836}
837
838void ContentItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
839{
840 if (m_creationInProgress) {
841 QQuickItem::itemChange(change, value);
842 return;
843 }
844 switch (change) {
846 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(value.item, true));
847 attached->setView(m_view);
848
849 // connect(attached, &ColumnViewAttached::fillWidthChanged, m_view, &ColumnView::polish);
850 connect(attached, &ColumnViewAttached::fillWidthChanged, this, [this] {
851 m_view->polish();
852 });
853 connect(attached, &ColumnViewAttached::reservedSpaceChanged, m_view, &ColumnView::polish);
854
855 value.item->setVisible(true);
856
857 if (!m_items.contains(value.item)) {
858 connect(value.item, &QQuickItem::widthChanged, m_view, &ColumnView::polish);
859 QQuickItem *item = value.item;
860 m_items << item;
861 connect(item, &QObject::destroyed, this, [this, item]() {
862 m_view->removeItem(item);
863 });
864 }
865
866 if (m_view->separatorVisible()) {
867 ensureLeadingSeparator(value.item);
868 }
869
870 m_shouldAnimate = true;
871 m_view->polish();
872 Q_EMIT m_view->countChanged();
873 break;
874 }
876 forgetItem(value.item);
877 break;
878 }
880 updateVisibleItems();
881 if (value.boolValue) {
882 m_view->polish();
883 }
884 break;
885 default:
886 break;
887 }
888 QQuickItem::itemChange(change, value);
889}
890
891void ContentItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
892{
893 updateVisibleItems();
894 QQuickItem::geometryChange(newGeometry, oldGeometry);
895}
896
897void ContentItem::syncItemsOrder()
898{
899 if (m_items == childItems()) {
900 return;
901 }
902
903 m_items = childItems();
904 // NOTE: polish() here sometimes gets indefinitely delayed and items changing order isn't seen
905 layoutItems();
906}
907
908void ContentItem::updateRepeaterModel()
909{
910 if (!sender()) {
911 return;
912 }
913
914 QObject *modelObj = sender()->property("model").value<QObject *>();
915
916 if (!modelObj) {
917 m_models.remove(sender());
918 return;
919 }
920
921 if (m_models[sender()]) {
922 disconnect(m_models[sender()], nullptr, this, nullptr);
923 }
924
925 m_models[sender()] = modelObj;
926
927 QAbstractItemModel *qaim = qobject_cast<QAbstractItemModel *>(modelObj);
928
929 if (qaim) {
930 connect(qaim, &QAbstractItemModel::rowsMoved, this, &ContentItem::syncItemsOrder);
931
932 } else {
933 connect(modelObj, SIGNAL(childrenChanged()), this, SLOT(syncItemsOrder()));
934 }
935}
936
937void ContentItem::connectHeader(QQuickItem *oldHeader, QQuickItem *newHeader)
938{
939 if (oldHeader) {
940 disconnect(oldHeader, nullptr, this, nullptr);
941 oldHeader->setParentItem(nullptr);
942 }
943 if (newHeader) {
944 connect(newHeader, &QQuickItem::heightChanged, this, &ContentItem::layoutItems);
945 connect(newHeader, &QQuickItem::visibleChanged, this, &ContentItem::layoutItems);
946 newHeader->setParentItem(m_globalHeaderParent);
947 }
948}
949
950void ContentItem::connectFooter(QQuickItem *oldFooter, QQuickItem *newFooter)
951{
952 if (oldFooter) {
953 disconnect(oldFooter, nullptr, this, nullptr);
954 oldFooter->setParentItem(nullptr);
955 }
956 if (newFooter) {
957 connect(newFooter, &QQuickItem::heightChanged, this, &ContentItem::layoutItems);
958 connect(newFooter, &QQuickItem::visibleChanged, this, &ContentItem::layoutItems);
959 newFooter->setParentItem(m_globalFooterParent);
960 }
961}
962
963ColumnView::ColumnView(QQuickItem *parent)
964 : QQuickItem(parent)
965 , m_contentItem(nullptr)
966{
967 // NOTE: this is to *not* trigger itemChange
968 m_contentItem = new ContentItem(this);
969 // Prevent interactions outside of ColumnView bounds, and let it act as a viewport.
970 setClip(true);
971 setAcceptedMouseButtons(Qt::LeftButton | Qt::BackButton | Qt::ForwardButton);
972 setAcceptTouchEvents(false); // Relies on synthetized mouse events
973 setFiltersChildMouseEvents(true);
974
975 connect(m_contentItem->m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
976 m_moving = false;
977 Q_EMIT movingChanged();
978 });
979 connect(m_contentItem, &ContentItem::widthChanged, this, &ColumnView::contentWidthChanged);
980 connect(m_contentItem, &ContentItem::xChanged, this, &ColumnView::contentXChanged);
981
982 connect(this, &ColumnView::activeFocusChanged, this, [this]() {
983 if (hasActiveFocus() && m_currentItem) {
984 m_currentItem->forceActiveFocus();
985 }
986 });
987 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(this, true));
988 attached->setView(this);
989 attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(m_contentItem, true));
990 attached->setView(this);
991}
992
993ColumnView::~ColumnView()
994{
995}
996
997ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const
998{
999 return m_contentItem->m_columnResizeMode;
1000}
1001
1002void ColumnView::setColumnResizeMode(ColumnResizeMode mode)
1003{
1004 if (m_contentItem->m_columnResizeMode == mode) {
1005 return;
1006 }
1007
1008 m_contentItem->m_columnResizeMode = mode;
1009 if (mode == SingleColumn && m_currentItem) {
1010 m_contentItem->m_viewAnchorItem = m_currentItem;
1011 }
1012 m_contentItem->m_shouldAnimate = false;
1013 polish();
1014 Q_EMIT columnResizeModeChanged();
1015}
1016
1017qreal ColumnView::columnWidth() const
1018{
1019 return m_contentItem->m_columnWidth;
1020}
1021
1022void ColumnView::setColumnWidth(qreal width)
1023{
1024 // Always forget the internal binding when the user sets anything, even the same value
1025 disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, nullptr);
1026
1027 if (m_contentItem->m_columnWidth == width) {
1028 return;
1029 }
1030
1031 m_contentItem->m_columnWidth = width;
1032 m_contentItem->m_shouldAnimate = false;
1033 polish();
1034 Q_EMIT columnWidthChanged();
1035}
1036
1037int ColumnView::currentIndex() const
1038{
1039 return m_currentIndex;
1040}
1041
1042void ColumnView::setCurrentIndex(int index)
1043{
1044 if (m_currentIndex == index || index < -1 || index >= m_contentItem->m_items.count()) {
1045 return;
1046 }
1047
1048 m_currentIndex = index;
1049
1050 if (index == -1) {
1051 m_currentItem.clear();
1052
1053 } else {
1054 m_currentItem = m_contentItem->m_items[index];
1055 Q_ASSERT(m_currentItem);
1056 m_currentItem->forceActiveFocus();
1057
1058 // If the current item is not on view, scroll
1059 QRectF mappedCurrent = m_currentItem->mapRectToItem(this, QRectF(QPointF(0, 0), m_currentItem->size()));
1060
1061 if (m_contentItem->m_slideAnim->state() == QAbstractAnimation::Running) {
1062 mappedCurrent.moveLeft(mappedCurrent.left() + m_contentItem->x() + m_contentItem->m_slideAnim->endValue().toInt());
1063 }
1064
1065 // m_contentItem->m_slideAnim->stop();
1066
1067 QRectF contentsRect(m_contentItem->m_leftPinnedSpace, //
1068 0,
1069 width() - m_contentItem->m_rightPinnedSpace - m_contentItem->m_leftPinnedSpace,
1070 height());
1071
1072 if (!m_mouseDown) {
1073 if (!contentsRect.contains(mappedCurrent)) {
1074 m_contentItem->m_viewAnchorItem = m_currentItem;
1075 if (qApp->layoutDirection() == Qt::RightToLeft) {
1076 m_contentItem->animateX(-m_currentItem->x() - m_currentItem->width() + width());
1077 } else {
1078 m_contentItem->animateX(-m_currentItem->x() + m_contentItem->m_leftPinnedSpace);
1079 }
1080 } else {
1081 m_contentItem->snapToItem();
1082 }
1083 }
1084 }
1085
1086 Q_EMIT currentIndexChanged();
1087 Q_EMIT currentItemChanged();
1088}
1089
1091{
1092 return m_currentItem;
1093}
1094
1096{
1097 return m_contentItem->m_visibleItems;
1098}
1099
1101{
1102 if (m_contentItem->m_visibleItems.isEmpty()) {
1103 return nullptr;
1104 }
1105
1106 return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.first());
1107}
1108
1110{
1111 if (m_contentItem->m_visibleItems.isEmpty()) {
1112 return nullptr;
1113 }
1114
1115 return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.last());
1116}
1117
1118int ColumnView::count() const
1119{
1120 return m_contentItem->m_items.count();
1121}
1122
1123qreal ColumnView::topPadding() const
1124{
1125 return m_topPadding;
1126}
1127
1128void ColumnView::setTopPadding(qreal padding)
1129{
1130 if (padding == m_topPadding) {
1131 return;
1132 }
1133
1134 m_topPadding = padding;
1135 polish();
1136 Q_EMIT topPaddingChanged();
1137}
1138
1139qreal ColumnView::bottomPadding() const
1140{
1141 return m_bottomPadding;
1142}
1143
1144void ColumnView::setBottomPadding(qreal padding)
1145{
1146 if (padding == m_bottomPadding) {
1147 return;
1148 }
1149
1150 m_bottomPadding = padding;
1151 polish();
1152 Q_EMIT bottomPaddingChanged();
1153}
1154
1156{
1157 return m_contentItem;
1158}
1159
1161{
1162 return m_contentItem->m_slideAnim->duration();
1163}
1164
1165void ColumnView::setScrollDuration(int duration)
1166{
1167 disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, nullptr);
1168
1169 if (m_contentItem->m_slideAnim->duration() == duration) {
1170 return;
1171 }
1172
1173 m_contentItem->m_slideAnim->setDuration(duration);
1174 Q_EMIT scrollDurationChanged();
1175}
1176
1178{
1179 return m_separatorVisible;
1180}
1181
1182void ColumnView::setSeparatorVisible(bool visible)
1183{
1184 if (visible == m_separatorVisible) {
1185 return;
1186 }
1187
1188 m_separatorVisible = visible;
1189
1190 Q_EMIT separatorVisibleChanged();
1191}
1192
1193bool ColumnView::dragging() const
1194{
1195 return m_dragging;
1196}
1197
1198bool ColumnView::moving() const
1199{
1200 return m_moving;
1201}
1202
1203qreal ColumnView::contentWidth() const
1204{
1205 return m_contentItem->width();
1206}
1207
1208qreal ColumnView::contentX() const
1209{
1210 return -m_contentItem->x();
1211}
1212
1213void ColumnView::setContentX(qreal x) const
1214{
1215 m_contentItem->setX(qRound(-x));
1216}
1217
1218bool ColumnView::interactive() const
1219{
1220 return m_interactive;
1221}
1222
1223void ColumnView::setInteractive(bool interactive)
1224{
1225 if (m_interactive == interactive) {
1226 return;
1227 }
1228
1229 m_interactive = interactive;
1230
1231 if (!m_interactive) {
1232 if (m_dragging) {
1233 m_dragging = false;
1234 Q_EMIT draggingChanged();
1235 }
1236
1237 m_contentItem->snapToItem();
1238 setKeepMouseGrab(false);
1239 }
1240
1241 Q_EMIT interactiveChanged();
1242}
1243
1244bool ColumnView::acceptsMouse() const
1245{
1246 return m_acceptsMouse;
1247}
1248
1249void ColumnView::setAcceptsMouse(bool accepts)
1250{
1251 if (m_acceptsMouse == accepts) {
1252 return;
1253 }
1254
1255 m_acceptsMouse = accepts;
1256
1257 if (!m_acceptsMouse) {
1258 if (m_dragging) {
1259 m_dragging = false;
1260 Q_EMIT draggingChanged();
1261 }
1262
1263 m_contentItem->snapToItem();
1264 setKeepMouseGrab(false);
1265 }
1266
1267 Q_EMIT acceptsMouseChanged();
1268}
1269
1271{
1272 insertItem(m_contentItem->m_items.length(), item);
1273}
1274
1276{
1277 if (!item || m_contentItem->m_items.contains(item)) {
1278 return;
1279 }
1280
1281 m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1282
1283 connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1284 removeItem(item);
1285 });
1286 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1287 attached->setOriginalParent(item->parentItem());
1288 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1289 item->setParentItem(m_contentItem);
1290
1291 item->forceActiveFocus();
1292
1293 if (attached->globalHeader()) {
1294 m_contentItem->connectHeader(nullptr, attached->globalHeader());
1295 }
1296 if (attached->globalFooter()) {
1297 m_contentItem->connectFooter(nullptr, attached->globalFooter());
1298 }
1299 connect(attached, &ColumnViewAttached::globalHeaderChanged, m_contentItem, &ContentItem::connectHeader);
1300 connect(attached, &ColumnViewAttached::globalFooterChanged, m_contentItem, &ContentItem::connectFooter);
1301
1302 // Animate shift to new item.
1303 m_contentItem->m_shouldAnimate = true;
1304 m_contentItem->layoutItems();
1305 Q_EMIT contentChildrenChanged();
1306
1307 // In order to keep the same current item we need to increase the current index if displaced
1308 // NOTE: just updating m_currentIndex does *not* update currentItem (which is what we need atm) while setCurrentIndex will update also currentItem
1309 if (m_currentIndex >= pos) {
1310 ++m_currentIndex;
1311 Q_EMIT currentIndexChanged();
1312 }
1313
1314 Q_EMIT itemInserted(pos, item);
1315}
1316
1318{
1319 if (pos < 0 || pos >= m_contentItem->m_items.length()) {
1320 qCWarning(KirigamiLog) << "Position" << pos << "passed to ColumnView::replaceItem is out of range.";
1321 return;
1322 }
1323
1324 if (!item) {
1325 qCWarning(KirigamiLog) << "Null item passed to ColumnView::replaceItem.";
1326 return;
1327 }
1328
1329 QQuickItem *oldItem = m_contentItem->m_items[pos];
1330
1331 // In order to keep the same current item we need to increase the current index if displaced
1332 if (m_currentIndex >= pos) {
1333 setCurrentIndex(m_currentIndex - 1);
1334 }
1335
1336 m_contentItem->forgetItem(oldItem);
1337 oldItem->setVisible(false);
1338
1339 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(oldItem, false));
1340
1341 if (attached && attached->shouldDeleteOnRemove()) {
1342 oldItem->deleteLater();
1343 } else {
1344 oldItem->setParentItem(attached ? attached->originalParent() : nullptr);
1345 }
1346
1347 Q_EMIT itemRemoved(oldItem);
1348
1349 if (!m_contentItem->m_items.contains(item)) {
1350 m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
1351
1352 connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
1353 removeItem(item);
1354 });
1355 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1356 attached->setOriginalParent(item->parentItem());
1357 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1358 item->setParentItem(m_contentItem);
1359
1360 if (attached->globalHeader()) {
1361 m_contentItem->connectHeader(nullptr, attached->globalHeader());
1362 }
1363 if (attached->globalFooter()) {
1364 m_contentItem->connectFooter(nullptr, attached->globalFooter());
1365 }
1366 connect(attached, &ColumnViewAttached::globalHeaderChanged, m_contentItem, &ContentItem::connectHeader);
1367 connect(attached, &ColumnViewAttached::globalFooterChanged, m_contentItem, &ContentItem::connectFooter);
1368
1369 if (m_currentIndex >= pos) {
1370 ++m_currentIndex;
1371 Q_EMIT currentIndexChanged();
1372 }
1373
1374 Q_EMIT itemInserted(pos, item);
1375 }
1376
1377 // Disable animation so replacement happens immediately.
1378 m_contentItem->m_shouldAnimate = false;
1379 m_contentItem->layoutItems();
1380 Q_EMIT contentChildrenChanged();
1381}
1382
1383void ColumnView::moveItem(int from, int to)
1384{
1385 if (m_contentItem->m_items.isEmpty() //
1386 || from < 0 || from >= m_contentItem->m_items.length() //
1387 || to < 0 || to >= m_contentItem->m_items.length()) {
1388 return;
1389 }
1390
1391 m_contentItem->m_items.move(from, to);
1392 m_contentItem->m_shouldAnimate = true;
1393
1394 if (from == m_currentIndex) {
1395 m_currentIndex = to;
1396 Q_EMIT currentIndexChanged();
1397 } else if (from < m_currentIndex && to > m_currentIndex) {
1398 --m_currentIndex;
1399 Q_EMIT currentIndexChanged();
1400 } else if (from > m_currentIndex && to <= m_currentIndex) {
1401 ++m_currentIndex;
1402 Q_EMIT currentIndexChanged();
1403 }
1404
1405 polish();
1406}
1407
1409{
1410 if (m_contentItem->m_items.isEmpty() || !m_contentItem->m_items.contains(item)) {
1411 return nullptr;
1412 }
1413
1414 const int index = m_contentItem->m_items.indexOf(item);
1415
1416 // In order to keep the same current item we need to increase the current index if displaced
1417 if (m_currentIndex >= index) {
1418 setCurrentIndex(m_currentIndex - 1);
1419 }
1420
1421 m_contentItem->forgetItem(item);
1422 item->setVisible(false);
1423
1424 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, false));
1425
1426 if (attached && attached->shouldDeleteOnRemove()) {
1427 item->deleteLater();
1428 } else {
1429 item->setParentItem(attached ? attached->originalParent() : nullptr);
1430 }
1431
1432 Q_EMIT contentChildrenChanged();
1433 Q_EMIT itemRemoved(item);
1434
1435 return item;
1436}
1437
1439{
1440 if (m_contentItem->m_items.isEmpty() || index < 0 || index >= count()) {
1441 return nullptr;
1442 } else {
1443 return removeItem(m_contentItem->m_items[index]);
1444 }
1445}
1446
1448{
1449 if (item.canConvert<QQuickItem *>()) {
1450 return removeItem(item.value<QQuickItem *>());
1451 } else if (item.canConvert<int>()) {
1452 return removeItem(item.toInt());
1453 } else {
1454 return nullptr;
1455 }
1456}
1457
1459{
1460 if (item.canConvert<QQuickItem *>()) {
1461 return pop(item.value<QQuickItem *>());
1462 } else if (item.canConvert<int>()) {
1463 return pop(item.toInt());
1464 } else if (item.isNull()) {
1465 return pop();
1466 }
1467 return nullptr;
1468}
1470{
1471 QQuickItem *removed = nullptr;
1472
1473 while (!m_contentItem->m_items.isEmpty() && m_contentItem->m_items.last() != item) {
1474 removed = removeItem(m_contentItem->m_items.last());
1475 }
1476 return removed;
1477}
1478
1480{
1481 if (index >= 0 && index < count() - 1) {
1482 return pop(m_contentItem->m_items.at(index));
1483 } else if (index == -1) {
1484 return pop(nullptr);
1485 }
1486 return nullptr;
1487}
1488
1490{
1491 if (count() > 0) {
1492 return removeItem(count() - 1);
1493 }
1494 return nullptr;
1495}
1496
1498{
1499 // Don't do an iterator on a list that gets progressively destroyed, treat it as a stack
1500 while (!m_contentItem->m_items.isEmpty()) {
1501 QQuickItem *item = m_contentItem->m_items.first();
1502 removeItem(item);
1503 }
1504
1505 m_contentItem->m_items.clear();
1506 Q_EMIT contentChildrenChanged();
1507}
1508
1510{
1511 return m_contentItem->m_items.contains(item);
1512}
1513
1515{
1516 return m_contentItem->childAt(x, y);
1517}
1518
1519ColumnViewAttached *ColumnView::qmlAttachedProperties(QObject *object)
1520{
1521 return new ColumnViewAttached(object);
1522}
1523
1524void ColumnView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1525{
1526 m_contentItem->setY(m_topPadding);
1527 m_contentItem->setHeight(newGeometry.height() - m_topPadding - m_bottomPadding);
1528 m_contentItem->m_shouldAnimate = false;
1529 polish();
1530
1531 m_contentItem->updateVisibleItems();
1532 QQuickItem::geometryChange(newGeometry, oldGeometry);
1533}
1534
1535bool ColumnView::childMouseEventFilter(QQuickItem *item, QEvent *event)
1536{
1537 if (!m_interactive || item == m_contentItem) {
1539 }
1540
1541 switch (event->type()) {
1543 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1544
1545 if (me->button() != Qt::LeftButton) {
1546 return false;
1547 }
1548
1549 // On press, we set the current index of the view to the root item
1550 QQuickItem *candidateItem = item;
1551 while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1552 candidateItem = candidateItem->parentItem();
1553 }
1554 if (int idx = m_contentItem->m_items.indexOf(candidateItem); idx >= 0 && candidateItem->parentItem() == m_contentItem) {
1555 setCurrentIndex(idx);
1556 }
1557
1558 // if !m_acceptsMouse we don't drag with mouse
1559 if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1560 event->setAccepted(false);
1561 return false;
1562 }
1563
1564 m_contentItem->m_slideAnim->stop();
1565 if (item->property("preventStealing").toBool()) {
1566 m_contentItem->snapToItem();
1567 return false;
1568 }
1569 m_oldMouseX = m_startMouseX = mapFromItem(item, me->position()).x();
1570 m_oldMouseY = m_startMouseY = mapFromItem(item, me->position()).y();
1571
1572 m_mouseDown = true;
1573 me->setAccepted(false);
1574 setKeepMouseGrab(false);
1575
1576 break;
1577 }
1578 case QEvent::MouseMove: {
1579 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1580
1581 if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1582 return false;
1583 }
1584
1585 if (!(me->buttons() & Qt::LeftButton)) {
1586 return false;
1587 }
1588
1589 const QPointF pos = mapFromItem(item, me->position());
1590
1591 bool verticalScrollIntercepted = false;
1592
1593 QQuickItem *candidateItem = item;
1594 while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
1595 candidateItem = candidateItem->parentItem();
1596 }
1597 if (candidateItem->parentItem() == m_contentItem) {
1598 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1599 if (attached->preventStealing()) {
1600 return false;
1601 }
1602 }
1603
1604 {
1605 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
1606
1607 ScrollIntentionEvent scrollIntentionEvent;
1608 scrollIntentionEvent.delta = QPointF(pos.x() - m_oldMouseX, pos.y() - m_oldMouseY);
1609
1610 Q_EMIT attached->scrollIntention(&scrollIntentionEvent);
1611
1612 if (scrollIntentionEvent.accepted) {
1613 verticalScrollIntercepted = true;
1614 event->setAccepted(true);
1615 }
1616 }
1617
1618 if ((!keepMouseGrab() && item->keepMouseGrab()) || item->property("preventStealing").toBool()) {
1619 m_contentItem->snapToItem();
1620 m_oldMouseX = pos.x();
1621 m_oldMouseY = pos.y();
1622 return false;
1623 }
1624
1625 const bool wasDragging = m_dragging;
1626 // If a drag happened, start to steal all events, use startDragDistance * 2 to give time to widgets to take the mouse grab by themselves
1627 m_dragging = keepMouseGrab() || qAbs(mapFromItem(item, me->position()).x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 3;
1628
1629 if (m_dragging != wasDragging) {
1630 m_moving = true;
1631 Q_EMIT movingChanged();
1632 Q_EMIT draggingChanged();
1633 }
1634
1635 if (m_dragging) {
1636 m_contentItem->setBoundedX(m_contentItem->x() + pos.x() - m_oldMouseX);
1637 }
1638
1639 m_contentItem->m_lastDragDelta = pos.x() - m_oldMouseX;
1640 m_oldMouseX = pos.x();
1641 m_oldMouseY = pos.y();
1642
1643 setKeepMouseGrab(m_dragging);
1644 me->setAccepted(m_dragging);
1645
1646 return m_dragging && !verticalScrollIntercepted;
1647 }
1649 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1650 if (item->property("preventStealing").toBool()) {
1651 return false;
1652 }
1653
1654 if (me->button() == Qt::BackButton && m_currentIndex > 0) {
1655 setCurrentIndex(m_currentIndex - 1);
1656 me->accept();
1657 return true;
1658 } else if (me->button() == Qt::ForwardButton) {
1659 setCurrentIndex(m_currentIndex + 1);
1660 me->accept();
1661 return true;
1662 }
1663
1664 if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
1665 return false;
1666 }
1667
1668 if (me->button() != Qt::LeftButton) {
1669 return false;
1670 }
1671
1672 m_mouseDown = false;
1673
1674 if (m_dragging) {
1675 m_contentItem->snapToItem();
1676 m_contentItem->m_lastDragDelta = 0;
1677 m_dragging = false;
1678 Q_EMIT draggingChanged();
1679 }
1680
1681 event->accept();
1682
1683 // if a drag happened, don't pass the event
1684 const bool block = keepMouseGrab();
1685 setKeepMouseGrab(false);
1686
1687 me->setAccepted(block);
1688 return block;
1689 }
1690 default:
1691 break;
1692 }
1693
1695}
1696
1697void ColumnView::mousePressEvent(QMouseEvent *event)
1698{
1699 if (!m_acceptsMouse && event->source() == Qt::MouseEventNotSynthesized) {
1700 event->setAccepted(false);
1701 return;
1702 }
1703
1704 if (event->button() == Qt::BackButton || event->button() == Qt::ForwardButton) {
1705 event->accept();
1706 return;
1707 }
1708
1709 if (!m_interactive) {
1710 return;
1711 }
1712
1713 m_contentItem->snapToItem();
1714 m_oldMouseX = event->position().x();
1715 m_startMouseX = event->position().x();
1716 m_mouseDown = true;
1717 setKeepMouseGrab(false);
1718 event->accept();
1719}
1720
1721void ColumnView::mouseMoveEvent(QMouseEvent *event)
1722{
1723 if (event->buttons() & Qt::BackButton || event->buttons() & Qt::ForwardButton) {
1724 event->accept();
1725 return;
1726 }
1727
1728 if (!m_interactive) {
1729 return;
1730 }
1731
1732 const bool wasDragging = m_dragging;
1733 // Same startDragDistance * 2 as the event filter
1734 m_dragging = keepMouseGrab() || qAbs(event->position().x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 2;
1735 if (m_dragging != wasDragging) {
1736 m_moving = true;
1737 Q_EMIT movingChanged();
1738 Q_EMIT draggingChanged();
1739 }
1740
1741 setKeepMouseGrab(m_dragging);
1742
1743 if (m_dragging) {
1744 m_contentItem->setBoundedX(m_contentItem->x() + event->pos().x() - m_oldMouseX);
1745 }
1746
1747 m_contentItem->m_lastDragDelta = event->pos().x() - m_oldMouseX;
1748 m_oldMouseX = event->pos().x();
1749 event->accept();
1750}
1751
1752void ColumnView::mouseReleaseEvent(QMouseEvent *event)
1753{
1754 if (event->button() == Qt::BackButton && m_currentIndex > 0) {
1755 setCurrentIndex(m_currentIndex - 1);
1756 event->accept();
1757 return;
1758 } else if (event->button() == Qt::ForwardButton) {
1759 setCurrentIndex(m_currentIndex + 1);
1760 event->accept();
1761 return;
1762 }
1763
1764 m_mouseDown = false;
1765
1766 if (!m_interactive) {
1767 return;
1768 }
1769
1770 m_contentItem->snapToItem();
1771 m_contentItem->m_lastDragDelta = 0;
1772
1773 if (m_dragging) {
1774 m_dragging = false;
1775 Q_EMIT draggingChanged();
1776 }
1777
1778 setKeepMouseGrab(false);
1779 event->accept();
1780}
1781
1782void ColumnView::mouseUngrabEvent()
1783{
1784 m_mouseDown = false;
1785
1786 if (m_contentItem->m_slideAnim->state() != QAbstractAnimation::Running) {
1787 m_contentItem->snapToItem();
1788 }
1789 m_contentItem->m_lastDragDelta = 0;
1790
1791 if (m_dragging) {
1792 m_dragging = false;
1793 Q_EMIT draggingChanged();
1794 }
1795
1796 setKeepMouseGrab(false);
1797}
1798
1799void ColumnView::classBegin()
1800{
1801 auto syncColumnWidth = [this]() {
1802 m_contentItem->m_columnWidth = privateQmlComponentsPoolSelf->instance(qmlEngine(this))->m_units->gridUnit() * 20;
1803 Q_EMIT columnWidthChanged();
1804 };
1805
1806 connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, syncColumnWidth);
1807 syncColumnWidth();
1808
1809 auto syncDuration = [this]() {
1810 m_contentItem->m_slideAnim->setDuration(QmlComponentsPoolSingleton::instance(qmlEngine(this))->m_units->longDuration());
1811 Q_EMIT scrollDurationChanged();
1812 };
1813
1814 connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, syncDuration);
1815 syncDuration();
1816
1818}
1819
1820void ColumnView::componentComplete()
1821{
1822 m_complete = true;
1824}
1825
1826void ColumnView::updatePolish()
1827{
1828 m_contentItem->layoutItems();
1829}
1830
1831void ColumnView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1832{
1833 switch (change) {
1835 if (m_contentItem && value.item != m_contentItem && !value.item->inherits("QQuickRepeater")) {
1836 addItem(value.item);
1837 }
1838 break;
1839 default:
1840 break;
1841 }
1842 QQuickItem::itemChange(change, value);
1843}
1844
1845void ColumnView::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item)
1846{
1847 // This can only be called from QML
1848 ColumnView *view = static_cast<ColumnView *>(prop->object);
1849 if (!view) {
1850 return;
1851 }
1852
1853 view->m_contentItem->m_items.append(item);
1854 connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1855 view->removeItem(item);
1856 });
1857
1858 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1859 attached->setOriginalParent(item->parentItem());
1860 attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1861
1862 item->setParentItem(view->m_contentItem);
1863}
1864
1865qsizetype ColumnView::contentChildren_count(QQmlListProperty<QQuickItem> *prop)
1866{
1867 ColumnView *view = static_cast<ColumnView *>(prop->object);
1868 if (!view) {
1869 return 0;
1870 }
1871
1872 return view->m_contentItem->m_items.count();
1873}
1874
1875QQuickItem *ColumnView::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index)
1876{
1877 ColumnView *view = static_cast<ColumnView *>(prop->object);
1878 if (!view) {
1879 return nullptr;
1880 }
1881
1882 if (index < 0 || index >= view->m_contentItem->m_items.count()) {
1883 return nullptr;
1884 }
1885 return view->m_contentItem->m_items.value(index);
1886}
1887
1888void ColumnView::contentChildren_clear(QQmlListProperty<QQuickItem> *prop)
1889{
1890 ColumnView *view = static_cast<ColumnView *>(prop->object);
1891 if (!view) {
1892 return;
1893 }
1894
1895 return view->m_contentItem->m_items.clear();
1896}
1897
1899{
1900 return QQmlListProperty<QQuickItem>(this, //
1901 nullptr,
1902 contentChildren_append,
1903 contentChildren_count,
1904 contentChildren_at,
1905 contentChildren_clear);
1906}
1907
1908void ColumnView::contentData_append(QQmlListProperty<QObject> *prop, QObject *object)
1909{
1910 ColumnView *view = static_cast<ColumnView *>(prop->object);
1911 if (!view) {
1912 return;
1913 }
1914
1915 view->m_contentData.append(object);
1916 QQuickItem *item = qobject_cast<QQuickItem *>(object);
1917 // exclude repeaters from layout
1918 if (item && item->inherits("QQuickRepeater")) {
1919 item->setParentItem(view);
1920
1921 connect(item, SIGNAL(modelChanged()), view->m_contentItem, SLOT(updateRepeaterModel()));
1922
1923 } else if (item) {
1924 view->m_contentItem->m_items.append(item);
1925 connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
1926 view->removeItem(item);
1927 });
1928
1929 ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
1930 attached->setOriginalParent(item->parentItem());
1931 attached->setShouldDeleteOnRemove(view->m_complete && !item->parentItem() && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
1932
1933 item->setParentItem(view->m_contentItem);
1934
1935 } else {
1936 object->setParent(view);
1937 }
1938}
1939
1940qsizetype ColumnView::contentData_count(QQmlListProperty<QObject> *prop)
1941{
1942 ColumnView *view = static_cast<ColumnView *>(prop->object);
1943 if (!view) {
1944 return 0;
1945 }
1946
1947 return view->m_contentData.count();
1948}
1949
1950QObject *ColumnView::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
1951{
1952 ColumnView *view = static_cast<ColumnView *>(prop->object);
1953 if (!view) {
1954 return nullptr;
1955 }
1956
1957 if (index < 0 || index >= view->m_contentData.count()) {
1958 return nullptr;
1959 }
1960 return view->m_contentData.value(index);
1961}
1962
1963void ColumnView::contentData_clear(QQmlListProperty<QObject> *prop)
1964{
1965 ColumnView *view = static_cast<ColumnView *>(prop->object);
1966 if (!view) {
1967 return;
1968 }
1969
1970 return view->m_contentData.clear();
1971}
1972
1974{
1975 return QQmlListProperty<QObject>(this, //
1976 nullptr,
1977 contentData_append,
1978 contentData_count,
1979 contentData_at,
1980 contentData_clear);
1981}
1982
1983#include "moc_columnview.cpp"
1984#include "moc_columnview_p.cpp"
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
QList< QObject * > visibleItems
The list of all visible column items that are at least partially in the viewport at any given moment.
Definition columnview.h:232
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:269
bool moving
True both when the user is dragging around with touch gestures the view contents or the view is anima...
Definition columnview.h:253
QQmlListProperty< QObject > contentData
every item declared inside the view, both visual and non-visual items
Definition columnview.h:273
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:197
int currentIndex
The position of the currently active column.
Definition columnview.h:187
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:242
qreal topPadding
The padding this will have at the top.
Definition columnview.h:212
int count
How many columns this view containsItem.
Definition columnview.h:182
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:178
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:192
qreal contentX
The value of the horizontal scroll of the view, in pixels.
Definition columnview.h:202
qreal bottomPadding
The padding this will have at the bottom.
Definition columnview.h:217
QML_ELEMENTColumnResizeMode columnResizeMode
The strategy to follow while automatically resizing the columns, the enum can have the following valu...
Definition columnview.h:173
int scrollDuration
The duration for scrolling animations.
Definition columnview.h:222
bool dragging
True when the user is dragging around with touch gestures the view contents.
Definition columnview.h:248
qreal contentWidth
The compound width of all columns in the view.
Definition columnview.h:207
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:263
QQuickItem * leadingVisibleItem
The first of visibleItems provided from convenience.
Definition columnview.h:237
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:258
bool separatorVisible
True if columns should be visually separated by a separator line.
Definition columnview.h:227
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)
KIOCORE_EXPORT DeleteJob * del(const QList< QUrl > &src, JobFlags flags=DefaultFlags)
void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
MouseButtonPress
void accept()
void setAccepted(bool accepted)
void append(const T &value)
void clear()
int count(const T &value) const const
T value(int i) const const
Qt::MouseButton button() const const
Qt::MouseButtons buttons() const const
Qt::MouseEventSource source() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void deleteLater()
void destroyed(QObject *obj)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
bool inherits(const char *className) const const
QObject * parent() const const
QVariant property(const char *name) const const
void setParent(QObject *parent)
bool setProperty(const char *name, const QVariant &value)
void clear()
T * data() const const
qreal x() const const
qreal y() const const
QQmlContext * contextForObject(const QObject *object)
QQmlEngine::ObjectOwnership objectOwnership(QObject *object)
T singletonInstance(int qmlTypeId)
void activeFocusChanged(bool)
virtual bool childMouseEventFilter(QQuickItem *item, QEvent *event)
virtual void classBegin() override
virtual void componentComplete() override
virtual bool event(QEvent *ev) override
void forceActiveFocus()
virtual void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
bool keepMouseGrab() 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
qreal left() const const
void moveLeft(qreal x)
RightToLeft
LeftButton
MouseEventNotSynthesized
bool canConvert(int targetTypeId) const const
QVariant fromValue(const 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-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:45:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.