+ const QList<QPair<int, QVariant> > groups = model()->groups();
+ if (groups.isEmpty() || !groupHeaderCreator()) {
+ return;
+ }
+
+ KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
+ if (!groupHeader) {
+ groupHeader = groupHeaderCreator()->create(this);
+ groupHeader->setParentItem(widget);
+ m_visibleGroups.insert(widget, groupHeader);
+ connect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged);
+ }
+ Q_ASSERT(groupHeader->parentItem() == widget);
+
+ const int groupIndex = groupIndexForItem(index);
+ Q_ASSERT(groupIndex >= 0);
+ groupHeader->setData(groups.at(groupIndex).second);
+ groupHeader->setRole(model()->sortRole());
+ groupHeader->setStyleOption(m_styleOption);
+ groupHeader->setScrollOrientation(scrollOrientation());
+ groupHeader->setItemIndex(index);
+
+ groupHeader->show();
+}
+
+void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget)
+{
+ KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
+ Q_ASSERT(groupHeader);
+
+ const int index = widget->index();
+ const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index);
+ const QRectF itemRect = m_layouter->itemRect(index);
+
+ // The group-header is a child of the itemlist widget. Translate the
+ // group header position to the relative position.
+ if (scrollOrientation() == Qt::Vertical) {
+ // In the vertical scroll orientation the group header should always span
+ // the whole width no matter which temporary position the parent widget
+ // has. In this case the x-position and width will be adjusted manually.
+ const qreal x = -widget->x() - itemOffset();
+ const qreal width = maximumItemOffset();
+ groupHeader->setPos(x, -groupHeaderRect.height());
+ groupHeader->resize(width, groupHeaderRect.size().height());
+ } else {
+ groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y());
+ groupHeader->resize(groupHeaderRect.size());
+ }
+}
+
+void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget)
+{
+ KItemListGroupHeader* header = m_visibleGroups.value(widget);
+ if (header) {
+ header->setParentItem(nullptr);
+ groupHeaderCreator()->recycle(header);
+ m_visibleGroups.remove(widget);
+ disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged);
+ }
+}
+
+void KItemListView::updateVisibleGroupHeaders()
+{
+ Q_ASSERT(m_grouped);
+ m_layouter->markAsDirty();
+
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ updateGroupHeaderForWidget(it.value());
+ }
+}
+
+int KItemListView::groupIndexForItem(int index) const
+{
+ Q_ASSERT(m_grouped);
+
+ const QList<QPair<int, QVariant> > groups = model()->groups();
+ if (groups.isEmpty()) {
+ return -1;
+ }
+
+ int min = 0;
+ int max = groups.count() - 1;
+ int mid = 0;
+ do {
+ mid = (min + max) / 2;
+ if (index > groups[mid].first) {
+ min = mid + 1;
+ } else {
+ max = mid - 1;
+ }
+ } while (groups[mid].first != index && min <= max);
+
+ if (min > max) {
+ while (groups[mid].first > index && mid > 0) {
+ --mid;
+ }
+ }
+
+ return mid;
+}
+
+void KItemListView::updateAlternateBackgrounds()
+{
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ updateAlternateBackgroundForWidget(it.value());
+ }
+}
+
+void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget)
+{
+ bool enabled = useAlternateBackgrounds();
+ if (enabled) {
+ const int index = widget->index();
+ enabled = (index & 0x1) > 0;
+ if (m_grouped) {
+ const int groupIndex = groupIndexForItem(index);
+ if (groupIndex >= 0) {
+ const QList<QPair<int, QVariant> > groups = model()->groups();
+ const int indexOfFirstGroupItem = groups[groupIndex].first;
+ const int relativeIndex = index - indexOfFirstGroupItem;
+ enabled = (relativeIndex & 0x1) > 0;
+ }
+ }
+ }
+ widget->setAlternateBackground(enabled);
+}
+
+bool KItemListView::useAlternateBackgrounds() const
+{
+ return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
+}
+
+QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const
+{
+ QElapsedTimer timer;
+ timer.start();
+
+ QHash<QByteArray, qreal> widths;
+
+ // Calculate the minimum width for each column that is required
+ // to show the headline unclipped.
+ const QFontMetricsF fontMetrics(m_headerWidget->font());
+ const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+ const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
+ for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) {
+ const QString headerText = m_model->roleDescription(visibleRole);
+ const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2;
+ widths.insert(visibleRole, headerWidth);
+ }
+
+ // Calculate the preferred column withs for each item and ignore values
+ // smaller than the width for showing the headline unclipped.
+ const KItemListWidgetCreatorBase* creator = widgetCreator();
+ int calculatedItemCount = 0;
+ bool maxTimeExceeded = false;
+ for (const KItemRange& itemRange : itemRanges) {
+ const int startIndex = itemRange.index;
+ const int endIndex = startIndex + itemRange.count - 1;
+
+ for (int i = startIndex; i <= endIndex; ++i) {
+ for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) {
+ qreal maxWidth = widths.value(visibleRole, 0);
+ const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this);
+ maxWidth = qMax(width, maxWidth);
+ widths.insert(visibleRole, maxWidth);
+ }
+
+ if (calculatedItemCount > 100 && timer.elapsed() > 200) {
+ // When having several thousands of items calculating the sizes can get
+ // very expensive. We accept a possibly too small role-size in favour
+ // of having no blocking user interface.
+ maxTimeExceeded = true;
+ break;
+ }
+ ++calculatedItemCount;
+ }
+ if (maxTimeExceeded) {
+ break;