+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ updateWidgetColumnWidths(it.value());
+ }
+}
+
+qreal KItemListView::columnWidthsSum() const
+{
+ qreal widthsSum = 0;
+ for (const QByteArray& role : qAsConst(m_visibleRoles)) {
+ widthsSum += m_headerWidget->columnWidth(role);
+ }
+ return widthsSum;
+}
+
+QRectF KItemListView::headerBoundaries() const
+{
+ return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF();
+}
+
+bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize,
+ const QSizeF& newItemSize,
+ const QSizeF& newItemMargin) const
+{
+ if (newItemSize.isEmpty() || newGridSize.isEmpty()) {
+ return false;
+ }
+
+ if (m_layouter->scrollOrientation() == Qt::Vertical) {
+ const qreal itemWidth = m_layouter->itemSize().width();
+ if (itemWidth > 0) {
+ const int newColumnCount = itemsPerSize(newGridSize.width(),
+ newItemSize.width(),
+ newItemMargin.width());
+ if (m_model->count() > newColumnCount) {
+ const int oldColumnCount = itemsPerSize(m_layouter->size().width(),
+ itemWidth,
+ m_layouter->itemMargin().width());
+ return oldColumnCount != newColumnCount;
+ }
+ }
+ } else {
+ const qreal itemHeight = m_layouter->itemSize().height();
+ if (itemHeight > 0) {
+ const int newRowCount = itemsPerSize(newGridSize.height(),
+ newItemSize.height(),
+ newItemMargin.height());
+ if (m_model->count() > newRowCount) {
+ const int oldRowCount = itemsPerSize(m_layouter->size().height(),
+ itemHeight,
+ m_layouter->itemMargin().height());
+ return oldRowCount != newRowCount;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool KItemListView::animateChangedItemCount(int changedItemCount) const
+{
+ if (m_itemSize.isEmpty()) {
+ // We have only columns or only rows, but no grid: An animation is usually
+ // welcome when inserting or removing items.
+ return !supportsItemExpanding();
+ }
+
+ if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) {
+ return false;
+ }
+
+ const int maximum = (scrollOrientation() == Qt::Vertical)
+ ? m_layouter->size().width() / m_layouter->itemSize().width()
+ : m_layouter->size().height() / m_layouter->itemSize().height();
+ // Only animate if up to 2/3 of a row or column are inserted or removed
+ return changedItemCount <= maximum * 2 / 3;
+}
+
+
+bool KItemListView::scrollBarRequired(const QSizeF& size) const
+{
+ const QSizeF oldSize = m_layouter->size();
+
+ m_layouter->setSize(size);
+ const qreal maxOffset = m_layouter->maximumScrollOffset();
+ m_layouter->setSize(oldSize);
+
+ return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height()
+ : maxOffset > size.width();
+}
+
+int KItemListView::showDropIndicator(const QPointF& pos)
+{
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ const KItemListWidget* widget = it.value();
+
+ const QPointF mappedPos = widget->mapFromItem(this, pos);
+ const QRectF rect = itemRect(widget->index());
+ if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) {
+ if (m_model->supportsDropping(widget->index())) {
+ // Keep 30% of the rectangle as the gap instead of always having a fixed gap
+ const int gap = qMax(qreal(4.0), qreal(0.3) * rect.height());
+ if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) {
+ return -1;
+ }
+ }
+
+ const bool isAboveItem = (mappedPos.y () < rect.height() / 2);
+ const qreal y = isAboveItem ? rect.top() : rect.bottom();
+
+ const QRectF draggingInsertIndicator(rect.left(), y, rect.width(), 1);
+ if (m_dropIndicator != draggingInsertIndicator) {
+ m_dropIndicator = draggingInsertIndicator;
+ update();
+ }
+
+ int index = widget->index();
+ if (!isAboveItem) {
+ ++index;
+ }
+ return index;
+ }
+ }
+
+ const QRectF firstItemRect = itemRect(firstVisibleIndex());
+ return (pos.y() <= firstItemRect.top()) ? 0 : -1;
+}
+
+void KItemListView::hideDropIndicator()
+{
+ if (!m_dropIndicator.isNull()) {
+ m_dropIndicator = QRectF();
+ update();
+ }
+}
+
+void KItemListView::updateGroupHeaderHeight()
+{
+ qreal groupHeaderHeight = m_styleOption.fontMetrics.height();
+ qreal groupHeaderMargin = 0;
+
+ if (scrollOrientation() == Qt::Horizontal) {
+ // The vertical margin above and below the header should be
+ // equal to the horizontal margin, not the vertical margin
+ // from m_styleOption.
+ groupHeaderHeight += 2 * m_styleOption.horizontalMargin;
+ groupHeaderMargin = m_styleOption.horizontalMargin;
+ } else if (m_itemSize.isEmpty()){
+ groupHeaderHeight += 4 * m_styleOption.padding;
+ groupHeaderMargin = m_styleOption.iconSize / 2;
+ } else {
+ groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin;
+ groupHeaderMargin = m_styleOption.iconSize / 4;
+ }
+ m_layouter->setGroupHeaderHeight(groupHeaderHeight);
+ m_layouter->setGroupHeaderMargin(groupHeaderMargin);
+
+ updateVisibleGroupHeaders();
+}
+
+void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex)
+{
+ if (!supportsItemExpanding() || !m_model) {
+ return;
+ }
+
+ if (firstIndex < 0 || lastIndex < 0) {
+ firstIndex = m_layouter->firstVisibleIndex();
+ lastIndex = m_layouter->lastVisibleIndex();
+ } else {
+ const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() &&
+ lastIndex >= m_layouter->firstVisibleIndex());
+ if (!isRangeVisible) {
+ return;
+ }
+ }
+
+ int previousParents = 0;
+ QBitArray previousSiblings;
+
+ // The rootIndex describes the first index where the siblings get
+ // calculated from. For the calculation the upper most parent item
+ // is required. For performance reasons it is checked first whether
+ // the visible items before or after the current range already
+ // contain a siblings information which can be used as base.
+ int rootIndex = firstIndex;
+
+ KItemListWidget* widget = m_visibleItems.value(firstIndex - 1);
+ if (!widget) {
+ // There is no visible widget before the range, check whether there
+ // is one after the range:
+ widget = m_visibleItems.value(lastIndex + 1);
+ if (widget) {
+ // The sibling information of the widget may only be used if
+ // all items of the range have the same number of parents.
+ const int parents = m_model->expandedParentsCount(lastIndex + 1);
+ for (int i = lastIndex; i >= firstIndex; --i) {
+ if (m_model->expandedParentsCount(i) != parents) {
+ widget = nullptr;
+ break;
+ }
+ }
+ }
+ }
+
+ if (widget) {
+ // Performance optimization: Use the sibling information of the visible
+ // widget beside the given range.
+ previousSiblings = widget->siblingsInformation();
+ if (previousSiblings.isEmpty()) {
+ return;
+ }
+ previousParents = previousSiblings.count() - 1;
+ previousSiblings.truncate(previousParents);
+ } else {
+ // Potentially slow path: Go back to the upper most parent of firstIndex
+ // to be able to calculate the initial value for the siblings.
+ while (rootIndex > 0 && m_model->expandedParentsCount(rootIndex) > 0) {
+ --rootIndex;
+ }
+ }
+
+ Q_ASSERT(previousParents >= 0);
+ for (int i = rootIndex; i <= lastIndex; ++i) {
+ // Update the parent-siblings in case if the current item represents
+ // a child or an upper parent.
+ const int currentParents = m_model->expandedParentsCount(i);
+ Q_ASSERT(currentParents >= 0);
+ if (previousParents < currentParents) {
+ previousParents = currentParents;
+ previousSiblings.resize(currentParents);
+ previousSiblings.setBit(currentParents - 1, hasSiblingSuccessor(i - 1));
+ } else if (previousParents > currentParents) {
+ previousParents = currentParents;
+ previousSiblings.truncate(currentParents);
+ }
+
+ if (i >= firstIndex) {
+ // The index represents a visible item. Apply the parent-siblings
+ // and update the sibling of the current item.
+ KItemListWidget* widget = m_visibleItems.value(i);
+ if (!widget) {
+ continue;
+ }
+
+ QBitArray siblings = previousSiblings;
+ siblings.resize(siblings.count() + 1);
+ siblings.setBit(siblings.count() - 1, hasSiblingSuccessor(i));
+
+ widget->setSiblingsInformation(siblings);
+ }