Kirigami2

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

KDE's Doxygen guidelines are available online.