X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/313a03d44807ba271cb4df0fc2a0752ba56aec89..613758b5ec7c57a5c115ae7a40ccaff71b923db2:/src/kitemviews/kitemlistview.cpp diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 67ba01a2f..b7d4c2470 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -55,6 +55,7 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_enabledSelectionToggles(false), m_grouped(false), m_activeTransactions(0), + m_endTransactionAnimationHint(Animation), m_itemSize(), m_controller(0), m_model(0), @@ -125,6 +126,8 @@ void KItemListView::setScrollOrientation(Qt::Orientation orientation) it.next(); it.value()->setScrollOrientation(orientation); } + updateGroupHeaderHeight(); + } doLayout(NoAnimation); @@ -148,7 +151,9 @@ void KItemListView::setItemSize(const QSizeF& itemSize) // Skip animations when the number of rows or columns // are changed in the grid layout. Although the animation // engine can handle this usecase, it looks obtrusive. - const bool animate = !changesItemGridLayout(m_layouter->size(), itemSize); + const bool animate = !changesItemGridLayout(m_layouter->size(), + itemSize, + m_layouter->itemMargin()); m_itemSize = itemSize; @@ -331,14 +336,31 @@ void KItemListView::setStyleOption(const KItemListStyleOption& option) const KItemListStyleOption previousOption = m_styleOption; m_styleOption = option; + bool animate = true; + const QSizeF margin(option.horizontalMargin, option.verticalMargin); + if (margin != m_layouter->itemMargin()) { + // Skip animations when the number of rows or columns + // are changed in the grid layout. Although the animation + // engine can handle this usecase, it looks obtrusive. + animate = !changesItemGridLayout(m_layouter->size(), + m_layouter->itemSize(), + margin); + m_layouter->setItemMargin(margin); + } + + if (m_grouped) { + updateGroupHeaderHeight(); + } + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); it.value()->setStyleOption(option); } - m_sizeHintResolver->clearCache(); - doLayout(Animation); + m_sizeHintResolver->clearCache(); + doLayout(animate ? Animation : NoAnimation); + onStyleOptionChanged(option, previousOption); } @@ -391,7 +413,9 @@ void KItemListView::setGeometry(const QRectF& rect) m_layouter->setSize(newSize); doLayout(Animation); } else { - const bool animate = !changesItemGridLayout(newSize, m_layouter->itemSize()); + const bool animate = !changesItemGridLayout(newSize, + m_layouter->itemSize(), + m_layouter->itemMargin()); m_layouter->setSize(newSize); if (animate) { @@ -548,7 +572,8 @@ void KItemListView::endTransaction() if (m_activeTransactions == 0) { onTransactionEnd(); - doLayout(NoAnimation); + doLayout(m_endTransactionAnimationHint); + m_endTransactionAnimationHint = Animation; } } @@ -557,7 +582,6 @@ bool KItemListView::isTransactionActive() const return m_activeTransactions > 0; } - void KItemListView::setHeaderShown(bool show) { @@ -809,6 +833,7 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) if (!hasMultipleRanges) { doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count); + updateSiblingsInformation(); } } @@ -817,7 +842,9 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) } if (hasMultipleRanges) { + m_endTransactionAnimationHint = NoAnimation; endTransaction(); + updateSiblingsInformation(); } } @@ -893,8 +920,9 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) // geometry update if necessary. const int activeTransactions = m_activeTransactions; m_activeTransactions = 0; - doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); + doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); m_activeTransactions = activeTransactions; + updateSiblingsInformation(); } } @@ -903,7 +931,9 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) } if (hasMultipleRanges) { + m_endTransactionAnimationHint = NoAnimation; endTransaction(); + updateSiblingsInformation(); } } @@ -931,6 +961,7 @@ void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList } doLayout(NoAnimation); + updateSiblingsInformation(); } void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, @@ -978,12 +1009,7 @@ void KItemListView::slotGroupedSortingChanged(bool current) m_layouter->markAsDirty(); if (m_grouped) { - // Apply the height of the header to the layouter - const qreal groupHeaderHeight = m_styleOption.fontMetrics.height() + - m_styleOption.margin * 2; - m_layouter->setGroupHeaderHeight(groupHeaderHeight); - - updateVisibleGroupHeaders(); + updateGroupHeaderHeight(); } else { // Clear all visible headers QMutableHashIterator it (m_visibleGroups); @@ -1205,6 +1231,15 @@ void KItemListView::triggerAutoScrolling() m_autoScrollTimer->start(RepeatingAutoScrollDelay); } +void KItemListView::slotGeometryOfGroupHeaderParentChanged() +{ + KItemListWidget* widget = qobject_cast(sender()); + Q_ASSERT(widget); + KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + Q_ASSERT(groupHeader); + updateGroupHeaderLayout(widget); +} + void KItemListView::setController(KItemListController* controller) { if (m_controller != controller) { @@ -1287,7 +1322,16 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha m_layoutTimer->stop(); } - if (!m_model || m_model->count() < 0 || m_activeTransactions > 0) { + if (m_activeTransactions > 0) { + if (hint == NoAnimation) { + // As soon as at least one property change should be done without animation, + // the whole transaction will be marked as not animated. + m_endTransactionAnimationHint = NoAnimation; + } + return; + } + + if (!m_model || m_model->count() < 0) { return; } @@ -1309,6 +1353,10 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha const int lastVisibleIndex = m_layouter->lastVisibleIndex(); + int firstSibblingIndex = -1; + int lastSibblingIndex = -1; + const bool supportsExpanding = supportsItemExpanding(); + QList reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint); // Assure that for each visible item a KItemListWidget is available. KItemListWidget @@ -1353,6 +1401,13 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha } applyNewPos = false; } + + if (supportsExpanding && changedCount == 0) { + if (firstSibblingIndex < 0) { + firstSibblingIndex = i; + } + lastSibblingIndex = i; + } } if (animate) { @@ -1415,6 +1470,11 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha recycleWidget(m_visibleItems.value(index)); } + if (supportsExpanding && firstSibblingIndex >= 0) { + Q_ASSERT(lastSibblingIndex >= 0); + updateSiblingsInformation(firstSibblingIndex, lastSibblingIndex); + } + if (m_grouped) { // Update the layout of all visible group headers QHashIterator it(m_visibleGroups); @@ -1482,8 +1542,9 @@ bool KItemListView::moveWidget(KItemListWidget* widget,const QRectF& itemBounds) // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation. // Otherwise instead of a moving-animation a create-animation on the new position will be used // instead. This is done to prevent overlapping (and confusing) moving-animations. - const qreal xMax = m_itemSize.width(); - const qreal yMax = m_itemSize.height(); + const QSizeF itemMargin = m_layouter->itemMargin(); + const qreal xMax = m_itemSize.width() + itemMargin.width(); + const qreal yMax = m_itemSize.height() + itemMargin.height(); qreal xDiff = qAbs(oldPos.x() - newPos.x()); qreal yDiff = qAbs(oldPos.y() - newPos.y()); if (scrollOrientation() == Qt::Vertical) { @@ -1589,6 +1650,7 @@ void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) widget->setEnabledSelectionToggle(enabledSelectionToggles()); widget->setIndex(index); widget->setData(m_model->data(index)); + widget->setSiblingsInformation(QBitArray()); } void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) @@ -1608,13 +1670,14 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) return; } - KItemListGroupHeader* header = m_visibleGroups.value(widget); - if (!header) { - header = m_groupHeaderCreator->create(this); - header->setParentItem(widget); - m_visibleGroups.insert(widget, header); + KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + if (!groupHeader) { + groupHeader = m_groupHeaderCreator->create(this); + groupHeader->setParentItem(widget); + m_visibleGroups.insert(widget, groupHeader); + connect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); } - Q_ASSERT(header->parentItem() == widget); + Q_ASSERT(groupHeader->parentItem() == widget); // Determine the shown data for the header by doing a binary // search in the groups-list @@ -1630,18 +1693,19 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) } } while (groups.at(mid).first != index && min <= max); - header->setData(groups.at(mid).second); - header->setRole(model()->sortRole()); - header->setStyleOption(m_styleOption); - header->setScrollOrientation(scrollOrientation()); + groupHeader->setData(groups.at(mid).second); + groupHeader->setRole(model()->sortRole()); + groupHeader->setStyleOption(m_styleOption); + groupHeader->setScrollOrientation(scrollOrientation()); + groupHeader->setItemIndex(index); - header->show(); + groupHeader->show(); } void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) { - KItemListGroupHeader* header = m_visibleGroups.value(widget); - Q_ASSERT(header); + KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + Q_ASSERT(groupHeader); const int index = widget->index(); const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index); @@ -1649,10 +1713,16 @@ void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) // The group-header is a child of the itemlist widget. Translate the // group header position to the relative position. - const QPointF groupHeaderPos(groupHeaderRect.x() - itemRect.x(), - - groupHeaderRect.height()); - header->setPos(groupHeaderPos); - header->resize(groupHeaderRect.size()); + 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. + groupHeader->setPos(-widget->x(), -groupHeaderRect.height()); + groupHeader->resize(size().width(), groupHeaderRect.size().height()); + } else { + groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y()); + groupHeader->resize(groupHeaderRect.size()); + } } void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) @@ -1662,6 +1732,7 @@ void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) header->setParentItem(0); m_groupHeaderCreator->recycle(header); m_visibleGroups.remove(widget); + disconnect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); } } @@ -1838,7 +1909,9 @@ QRectF KItemListView::headerBoundaries() const return m_header ? m_header->geometry() : QRectF(); } -bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, const QSizeF& newItemSize) const +bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, + const QSizeF& newItemSize, + const QSizeF& newItemMargin) const { if (newItemSize.isEmpty() || newGridSize.isEmpty()) { return false; @@ -1847,16 +1920,28 @@ bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, const QSize if (m_layouter->scrollOrientation() == Qt::Vertical) { const qreal itemWidth = m_layouter->itemSize().width(); if (itemWidth > 0) { - const int oldColumnCount = m_layouter->size().width() / itemWidth; - const int newColumnCount = newGridSize.width() / newItemSize.width(); - return oldColumnCount != newColumnCount; + 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 oldRowCount = m_layouter->size().height() / itemHeight; - const int newRowCount = newGridSize.height() / newItemSize.height(); - return oldRowCount != newRowCount; + 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; + } } } @@ -1876,6 +1961,170 @@ bool KItemListView::animateChangedItemCount(int changedItemCount) const 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(); +} + +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 += 2 * m_styleOption.padding; + groupHeaderMargin = m_styleOption.iconSize / 2; + } else { + groupHeaderHeight += 2 * m_styleOption.padding; + groupHeaderMargin = m_styleOption.iconSize / 4; + } + m_layouter->setGroupHeaderHeight(groupHeaderHeight); + m_layouter->setGroupHeaderMargin(groupHeaderMargin); + + updateVisibleGroupHeaders(); +} + +void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) +{ + if (!supportsItemExpanding()) { + 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 = 0; + 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); + } + } +} + +bool KItemListView::hasSiblingSuccessor(int index) const +{ + bool hasSuccessor = false; + const int parentsCount = m_model->expandedParentsCount(index); + int successorIndex = index + 1; + + // Search the next sibling + const int itemCount = m_model->count(); + while (successorIndex < itemCount) { + const int currentParentsCount = m_model->expandedParentsCount(successorIndex); + if (currentParentsCount == parentsCount) { + hasSuccessor = true; + break; + } else if (currentParentsCount < parentsCount) { + break; + } + ++successorIndex; + } + + if (m_grouped && hasSuccessor) { + // If the sibling is part of another group, don't mark it as + // successor as the group header is between the sibling connections. + for (int i = index + 1; i <= successorIndex; ++i) { + if (m_layouter->isFirstGroupItem(i)) { + hasSuccessor = false; + break; + } + } + } + + return hasSuccessor; +} + int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) { int inc = 0; @@ -1902,6 +2151,13 @@ int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldIn return inc; } +int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin) +{ + const qreal availableSize = size - itemMargin; + const int count = availableSize / (itemSize + itemMargin); + return count; +} + KItemListCreatorBase::~KItemListCreatorBase()