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 
805 ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const
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"
void append(const T &value)
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:217
int count
How many columns this view containsItem.
Definition: columnview.h:167
qreal left() const const
QPointF mapFromItem(const QQuickItem *item, const QPointF &point) const const
virtual bool event(QEvent *ev) override
bool interactive
True if it supports moving the contents by dragging.
Definition: columnview.h:243
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
True both when the user is dragging around with touch gestures the view contents or the view is anima...
Definition: columnview.h:238
T value() const const
void itemRemoved(QQuickItem *item)
An item has just been removed from the view.
QQuickItem * pop(QQuickItem *item)
Removes all the items after item.
int scrollDuration
The duration for scrolling animations.
Definition: columnview.h:207
bool inherits(const char *className) const const
void activeFocusChanged(bool)
virtual QObject * create(QQmlContext *context)
T singletonInstance(int qmlTypeId)
bool containsItem(QQuickItem *item)
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
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)
Move an item inside the view.
void setSize(const QSizeF &size)
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
QQuickItem currentItem
The currently active column.
Definition: columnview.h:177
ColumnView is a container that lays out items horizontally in a row, when not all items fit in the Co...
Definition: columnview.h:147
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
True if the contents can be dragged also with mouse besides touch.
Definition: columnview.h:248
void setKeepMouseGrab(bool keep)
void setData(const QByteArray &data, const QUrl &url)
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
void setAccepted(bool accepted)
QQuickItem lastVisibleItem
The last of visibleItems provided from convenience.
Definition: columnview.h:227
bool dragging
True when the user is dragging around with touch gestures the view contents.
Definition: columnview.h:233
Qt::MouseButtons buttons() const const
Qt::MouseEventSource source() const const
QQmlListProperty< QObject > contentData
every item declared inside the view, both visual and non-visual items
Definition: columnview.h:258
int toInt(bool *ok) const const
virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
ColumnView view
The view this column belongs to.
Definition: columnview.h:73
void replaceItem(int pos, QQuickItem *item)
Replaces an item in the view at a given position with a new item.
qreal columnWidth
The width of all columns when columnResizeMode is FixedColumns.
Definition: columnview.h:163
bool toBool() const const
QQuickItem firstVisibleItem
The first of visibleItems provided from convenience.
Definition: columnview.h:222
bool canConvert(int targetTypeId) const const
KGuiItem del()
bool setProperty(const char *name, const QVariant &value)
ColumnResizeMode columnResizeMode
The strategy to follow while automatically resizing the columns, the enum can have the following valu...
Definition: columnview.h:158
qreal contentX
The value of the horizontal scroll of the view, in pixels.
Definition: columnview.h:187
QQmlEngine::ObjectOwnership objectOwnership(QObject *object)
qreal x() const const
qreal y() const const
int currentIndex
The position of the currently active column.
Definition: columnview.h:172
QQuickItem contentItem
The main content item of this view: it's the parent of the column items.
Definition: columnview.h:182
bool separatorVisible
True if columns should be visually separated by a separator line.
Definition: columnview.h:212
QQmlListProperty< QQuickItem > contentChildren
Every column item the view contains.
Definition: columnview.h:254
void setParent(QObject *parent)
void itemInserted(int position, QQuickItem *item)
A new item has been inserted.
void xChanged()
RightToLeft
void clear()
bool inViewport
True if this column is at least partly visible in the ColumnView's viewport.
Definition: columnview.h:79
qreal bottomPadding
The padding this will have at the bottom.
Definition: columnview.h:202
qreal topPadding
The padding this will have at the top.
Definition: columnview.h:197
void clear()
qreal contentWidth
The compound width of all columns in the view.
Definition: columnview.h:192
bool isVisible() const const
T * data() const const
void moveLeft(qreal x)
void addItem(QQuickItem *item)
Pushes a new item at 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)
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
Like the same property of MouseArea, when this is true, the column view won't try to manage events by...
Definition: columnview.h:62
This is an attached property to every item that is inserted in the ColumnView, used to access the vie...
Definition: columnview.h:38
void accept()
void clear()
Removes every item in the view.
QQuickItem * itemAt(qreal x, qreal y)
Returns the visible item containing the point x, y in content coordinates.
QVariant property(const char *name) const const
virtual void classBegin() override
qreal height() const const
bool fillWidth
If true, the column will expand to take the whole viewport space minus reservedSpace.
Definition: columnview.h:50
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Feb 7 2023 04:14:23 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.