X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/a803819afddfd16f63e82f66ac46d5b54f508bd4..03415d90783979f8e51880b7950721985fee29bf:/src/kitemviews/kitemlistview.cpp diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index b11fe905d..451e51833 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -52,8 +52,10 @@ namespace { KItemListView::KItemListView(QGraphicsWidget* parent) : QGraphicsWidget(parent), + m_enabledSelectionToggles(false), m_grouped(false), m_activeTransactions(0), + m_endTransactionAnimationHint(Animation), m_itemSize(), m_controller(0), m_model(0), @@ -124,9 +126,11 @@ void KItemListView::setScrollOrientation(Qt::Orientation orientation) it.next(); it.value()->setScrollOrientation(orientation); } + updateGroupHeaderHeight(); + } - doLayout(Animation, 0, 0); + doLayout(NoAnimation); onScrollOrientationChanged(orientation, previousOrientation); emit scrollOrientationChanged(orientation, previousOrientation); @@ -144,21 +148,23 @@ void KItemListView::setItemSize(const QSizeF& itemSize) return; } + // 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, + m_layouter->itemMargin()); + m_itemSize = itemSize; - const bool emptySize = itemSize.isEmpty(); - if (emptySize) { + if (itemSize.isEmpty()) { updateVisibleRolesSizes(); } else { - if (itemSize.width() < previousSize.width() || itemSize.height() < previousSize.height()) { - prepareLayoutForIncreasedItemCount(itemSize, ItemSize); - } else { - m_layouter->setItemSize(itemSize); - } + m_layouter->setItemSize(itemSize); } m_sizeHintResolver->clearCache(); - doLayout(Animation, 0, 0); + doLayout(animate ? Animation : NoAnimation); onItemSizeChanged(itemSize, previousSize); } @@ -180,9 +186,11 @@ void KItemListView::setScrollOffset(qreal offset) m_layouter->setScrollOffset(offset); m_animation->setScrollOffset(offset); - if (!m_layoutTimer->isActive()) { - doLayout(NoAnimation, 0, 0); - } + + // Don't check whether the m_layoutTimer is active: Changing the + // scroll offset must always trigger a synchronous layout, otherwise + // the smooth-scrolling might get jerky. + doLayout(NoAnimation); onScrollOffsetChanged(offset, previousOffset); } @@ -198,13 +206,19 @@ qreal KItemListView::maximumScrollOffset() const void KItemListView::setItemOffset(qreal offset) { + if (m_layouter->itemOffset() == offset) { + return; + } + m_layouter->setItemOffset(offset); if (m_header) { m_header->setPos(-offset, 0); } - if (!m_layoutTimer->isActive()) { - doLayout(NoAnimation, 0, 0); - } + + // Don't check whether the m_layoutTimer is active: Changing the + // item offset must always trigger a synchronous layout, otherwise + // the smooth-scrolling might get jerky. + doLayout(NoAnimation); } qreal KItemListView::itemOffset() const @@ -240,7 +254,7 @@ void KItemListView::setVisibleRoles(const QList& roles) } updateVisibleRolesSizes(); - doLayout(Animation, 0, 0); + doLayout(NoAnimation); onVisibleRolesChanged(roles, previousRoles); } @@ -254,7 +268,7 @@ void KItemListView::setAutoScroll(bool enabled) { if (enabled && !m_autoScrollTimer) { m_autoScrollTimer = new QTimer(this); - m_autoScrollTimer->setSingleShot(false); + m_autoScrollTimer->setSingleShot(true); connect(m_autoScrollTimer, SIGNAL(timeout()), this, SLOT(triggerAutoScrolling())); m_autoScrollTimer->start(InitialAutoScrollDelay); } else if (!enabled && m_autoScrollTimer) { @@ -269,6 +283,24 @@ bool KItemListView::autoScroll() const return m_autoScrollTimer != 0; } +void KItemListView::setEnabledSelectionToggles(bool enabled) +{ + if (m_enabledSelectionToggles != enabled) { + m_enabledSelectionToggles = enabled; + + QHashIterator it(m_visibleItems); + while (it.hasNext()) { + it.next(); + it.value()->setEnabledSelectionToggle(enabled); + } + } +} + +bool KItemListView::enabledSelectionToggles() const +{ + return m_enabledSelectionToggles; +} + KItemListController* KItemListView::controller() const { return m_controller; @@ -304,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, 0, 0); + m_sizeHintResolver->clearCache(); + doLayout(animate ? Animation : NoAnimation); + onStyleOptionChanged(option, previousOption); } @@ -328,20 +377,59 @@ void KItemListView::setGeometry(const QRectF& rect) return; } - if (m_model->count() > 0) { - prepareLayoutForIncreasedItemCount(rect.size(), LayouterSize); + const QSizeF newSize = rect.size(); + if (m_itemSize.isEmpty()) { + // The item size is dynamic: + // Changing the geometry does not require to do an expensive + // update of the visible-roles sizes, only the stretched sizes + // need to be adjusted to the new size. + updateStretchedVisibleRolesSizes(); + + if (m_useHeaderWidths) { + QSizeF dynamicItemSize = m_layouter->itemSize(); + + if (m_itemSize.width() < 0) { + const qreal requiredWidth = visibleRolesSizesWidthSum(); + if (newSize.width() > requiredWidth) { + dynamicItemSize.setWidth(newSize.width()); + } + const qreal headerWidth = qMax(newSize.width(), requiredWidth); + m_header->resize(headerWidth, m_header->size().height()); + } + + if (m_itemSize.height() < 0) { + const qreal requiredHeight = visibleRolesSizesHeightSum(); + if (newSize.height() > requiredHeight) { + dynamicItemSize.setHeight(newSize.height()); + } + // TODO: KItemListHeader is not prepared for vertical alignment + } + + m_layouter->setItemSize(dynamicItemSize); + } + + // Triggering a synchronous layout is fine from a performance point of view, + // as with dynamic item sizes no moving animation must be done. + m_layouter->setSize(newSize); + doLayout(Animation); } else { - m_layouter->setSize(rect.size()); - } - - if (!m_layoutTimer->isActive()) { - m_layoutTimer->start(); + const bool animate = !changesItemGridLayout(newSize, + m_layouter->itemSize(), + m_layouter->itemMargin()); + m_layouter->setSize(newSize); + + if (animate) { + // Trigger an asynchronous relayout with m_layoutTimer to prevent + // performance bottlenecks. If the timer is exceeded, an animated layout + // will be triggered. + if (!m_layoutTimer->isActive()) { + m_layoutTimer->start(); + } + } else { + m_layoutTimer->stop(); + doLayout(NoAnimation); + } } - - // Changing the geometry does not require to do an expensive - // update of the visible-roles sizes, only the stretched sizes - // need to be adjusted to the new size. - updateStretchedVisibleRolesSizes(); } int KItemListView::itemAt(const QPointF& pos) const @@ -362,8 +450,18 @@ int KItemListView::itemAt(const QPointF& pos) const bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const { - Q_UNUSED(index); - Q_UNUSED(pos); + if (!m_enabledSelectionToggles) { + return false; + } + + const KItemListWidget* widget = m_visibleItems.value(index); + if (widget) { + const QRectF selectionToggleRect = widget->selectionToggleRect(); + if (!selectionToggleRect.isEmpty()) { + const QPointF mappedPos = widget->mapFromItem(this, pos); + return selectionToggleRect.contains(mappedPos); + } + } return false; } @@ -402,14 +500,58 @@ QHash KItemListView::visibleRolesSizes(const KItemRangeList& return QHash(); } +bool KItemListView::supportsItemExpanding() const +{ + return false; +} + QRectF KItemListView::itemRect(int index) const { return m_layouter->itemRect(index); } -int KItemListView::itemsPerOffset() const +QRectF KItemListView::itemContextRect(int index) const { - return m_layouter->itemsPerOffset(); + QRectF contextRect; + + const KItemListWidget* widget = m_visibleItems.value(index); + if (widget) { + contextRect = widget->iconRect() | widget->textRect(); + contextRect.translate(itemRect(index).topLeft()); + } + + return contextRect; +} + +void KItemListView::scrollToItem(int index) +{ + QRectF viewGeometry = geometry(); + if (m_header) { + const qreal headerHeight = m_header->size().height(); + viewGeometry.adjust(0, headerHeight, 0, 0); + } + const QRectF currentRect = itemRect(index); + + if (!viewGeometry.contains(currentRect)) { + qreal newOffset = scrollOffset(); + if (scrollOrientation() == Qt::Vertical) { + if (currentRect.top() < viewGeometry.top()) { + newOffset += currentRect.top() - viewGeometry.top(); + } else if (currentRect.bottom() > viewGeometry.bottom()) { + newOffset += currentRect.bottom() - viewGeometry.bottom(); + } + } else { + if (currentRect.left() < viewGeometry.left()) { + newOffset += currentRect.left() - viewGeometry.left(); + } else if (currentRect.right() > viewGeometry.right()) { + newOffset += currentRect.right() - viewGeometry.right(); + } + } + + if (newOffset != scrollOffset()) { + emit scrollTo(newOffset); + } + } } void KItemListView::beginTransaction() @@ -430,7 +572,8 @@ void KItemListView::endTransaction() if (m_activeTransactions == 0) { onTransactionEnd(); - doLayout(Animation, 0, 0); + doLayout(m_endTransactionAnimationHint); + m_endTransactionAnimationHint = Animation; } } @@ -439,7 +582,6 @@ bool KItemListView::isTransactionActive() const return m_activeTransactions > 0; } - void KItemListView::setHeaderShown(bool show) { @@ -451,10 +593,14 @@ void KItemListView::setHeaderShown(bool show) m_header->setVisibleRolesWidths(headerRolesWidths()); m_header->setZValue(1); - m_useHeaderWidths = false; - connect(m_header, SIGNAL(visibleRoleWidthChanged(QByteArray,qreal,qreal)), this, SLOT(slotVisibleRoleWidthChanged(QByteArray,qreal,qreal))); + connect(m_header, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + connect(m_header, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SIGNAL(sortRoleChanged(QByteArray,QByteArray))); + + m_useHeaderWidths = false; m_layouter->setHeaderHeight(m_header->size().height()); } else if (!show && m_header) { @@ -620,34 +766,6 @@ QList KItemListView::visibleItemListWidgets() const return m_visibleItems.values(); } -void KItemListView::resizeEvent(QGraphicsSceneResizeEvent* event) -{ - QGraphicsWidget::resizeEvent(event); - if (m_itemSize.isEmpty() && m_useHeaderWidths) { - QSizeF dynamicItemSize = m_layouter->itemSize(); - const QSizeF newSize = event->newSize(); - - if (m_itemSize.width() < 0) { - const qreal requiredWidth = visibleRolesSizesWidthSum(); - if (newSize.width() > requiredWidth) { - dynamicItemSize.setWidth(newSize.width()); - } - const qreal headerWidth = qMax(newSize.width(), requiredWidth); - m_header->resize(headerWidth, m_header->size().height()); - } - - if (m_itemSize.height() < 0) { - const qreal requiredHeight = visibleRolesSizesHeightSum(); - if (newSize.height() > requiredHeight) { - dynamicItemSize.setHeight(newSize.height()); - } - // TODO: KItemListHeader is not prepared for vertical alignment - } - - m_layouter->setItemSize(dynamicItemSize); - } -} - void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) { updateVisibleRolesSizes(itemRanges); @@ -694,21 +812,27 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) } m_layouter->markAsDirty(); - if (m_model->count() == count && maximumScrollOffset() > size().height()) { - kDebug() << "Scrollbar required, skipping layout"; - const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); - QSizeF layouterSize = m_layouter->size(); - if (scrollOrientation() == Qt::Vertical) { - layouterSize.rwidth() -= scrollBarExtent; - } else { - layouterSize.rheight() -= scrollBarExtent; + if (m_model->count() == count && m_activeTransactions == 0) { + // Check whether a scrollbar is required to show the inserted items. In this case + // the size of the layouter will be decreased before calling doLayout(): This prevents + // an unnecessary temporary animation due to the geometry change of the inserted scrollbar. + const bool verticalScrollOrientation = (scrollOrientation() == Qt::Vertical); + const bool decreaseLayouterSize = ( verticalScrollOrientation && maximumScrollOffset() > size().height()) || + (!verticalScrollOrientation && maximumScrollOffset() > size().width()); + if (decreaseLayouterSize) { + const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); + QSizeF layouterSize = m_layouter->size(); + if (verticalScrollOrientation) { + layouterSize.rwidth() -= scrollBarExtent; + } else { + layouterSize.rheight() -= scrollBarExtent; + } + m_layouter->setSize(layouterSize); } - m_layouter->setSize(layouterSize); } if (!hasMultipleRanges) { - doLayout(Animation, index, count); - update(); + doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count); } } @@ -760,9 +884,8 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) continue; } - if (m_model->count() == 0) { - // For performance reasons no animation is done when all items have - // been removed. + if (m_model->count() == 0 || hasMultipleRanges || !animateChangedItemCount(count)) { + // Remove the widget without animation recycleWidget(widget); } else { // Animate the removing of the items. Special case: When removing an item there @@ -788,8 +911,14 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) m_layouter->markAsDirty(); if (!hasMultipleRanges) { - doLayout(Animation, index, -count); - update(); + // The decrease-layout-size optimization in KItemListView::slotItemsInserted() + // assumes an updated geometry. If items are removed during an active transaction, + // the transaction will be temporary deactivated so that doLayout() triggers a + // geometry update if necessary. + const int activeTransactions = m_activeTransactions; + m_activeTransactions = 0; + doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); + m_activeTransactions = activeTransactions; } } @@ -804,6 +933,13 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) { + m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count); + m_layouter->markAsDirty(); + + if (m_controller) { + m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes); + } + const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index); const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1); @@ -811,12 +947,14 @@ void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList KItemListWidget* widget = m_visibleItems.value(index); if (widget) { updateWidgetProperties(widget, index); + if (m_grouped) { + updateGroupHeaderForWidget(widget); + } + initializeItemListWidget(widget); } } - if (m_controller) { - m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes); - } + doLayout(NoAnimation); } void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, @@ -853,10 +991,7 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, // The sort-role has been changed which might result // in modified group headers updateVisibleGroupHeaders(); - doLayout(NoAnimation, 0, 0); - if (!m_layoutTimer->isActive()) { - m_layoutTimer->start(); - } + doLayout(NoAnimation); } } } @@ -867,12 +1002,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); @@ -883,7 +1013,7 @@ void KItemListView::slotGroupedSortingChanged(bool current) Q_ASSERT(m_visibleGroups.isEmpty()); } - doLayout(Animation, 0, 0); + doLayout(NoAnimation); } void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) @@ -892,7 +1022,7 @@ void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder pr Q_UNUSED(previous); if (m_grouped) { updateVisibleGroupHeaders(); - doLayout(Animation, 0, 0); + doLayout(NoAnimation); } } @@ -902,7 +1032,7 @@ void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteAr Q_UNUSED(previous); if (m_grouped) { updateVisibleGroupHeaders(); - doLayout(Animation, 0, 0); + doLayout(NoAnimation); } } @@ -921,33 +1051,6 @@ void KItemListView::slotCurrentChanged(int current, int previous) Q_ASSERT(!currentWidget->isCurrent()); currentWidget->setCurrent(true); } - - const QRectF viewGeometry = geometry(); - const QRectF currentRect = itemRect(current); - - if (!viewGeometry.contains(currentRect)) { - // Make sure that the new current item is fully visible in the view. - qreal newOffset = scrollOffset(); - if (currentRect.top() < viewGeometry.top()) { - Q_ASSERT(scrollOrientation() == Qt::Vertical); - newOffset += currentRect.top() - viewGeometry.top(); - } else if ((currentRect.bottom() > viewGeometry.bottom())) { - Q_ASSERT(scrollOrientation() == Qt::Vertical); - newOffset += currentRect.bottom() - viewGeometry.bottom(); - } else if (currentRect.left() < viewGeometry.left()) { - if (scrollOrientation() == Qt::Horizontal) { - newOffset += currentRect.left() - viewGeometry.left(); - } - } else if ((currentRect.right() > viewGeometry.right())) { - if (scrollOrientation() == Qt::Horizontal) { - newOffset += currentRect.right() - viewGeometry.right(); - } - } - - if (newOffset != scrollOffset()) { - emit scrollTo(newOffset); - } - } } void KItemListView::slotSelectionChanged(const QSet& current, const QSet& previous) @@ -1003,7 +1106,7 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, void KItemListView::slotLayoutTimerFinished() { m_layouter->setSize(geometry().size()); - doLayout(Animation, 0, 0); + doLayout(Animation); } void KItemListView::slotRubberBandPosChanged() @@ -1058,7 +1161,7 @@ void KItemListView::slotVisibleRoleWidthChanged(const QByteArray& role, widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); } - doLayout(Animation, 0, 0); + doLayout(NoAnimation); } } @@ -1112,7 +1215,9 @@ void KItemListView::triggerAutoScrolling() // the autoscrolling may not get skipped anymore until a new rubberband is created m_skipAutoScrollForRubberBand = false; - setScrollOffset(scrollOffset() + m_autoScrollIncrement); + const qreal maxVisibleOffset = qMax(qreal(0), maximumScrollOffset() - visibleSize); + const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset); + setScrollOffset(newScrollOffset); // Trigger the autoscroll timer which will periodically call // triggerAutoScrolling() @@ -1167,7 +1272,7 @@ void KItemListView::setModel(KItemModelBase* model) } m_model = model; - m_layouter->setModel(model); + m_layouter->setModel(model); m_grouped = model->groupedSorting(); if (m_model) { @@ -1198,16 +1303,23 @@ KItemListRubberBand* KItemListView::rubberBand() const void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount) { if (m_layoutTimer->isActive()) { - kDebug() << "Stopping layout timer, synchronous layout requested"; m_layoutTimer->stop(); } - if (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; } - const int firstVisibleIndex = m_layouter->firstVisibleIndex(); - const int lastVisibleIndex = m_layouter->lastVisibleIndex(); + if (!m_model || m_model->count() < 0) { + return; + } + + int firstVisibleIndex = m_layouter->firstVisibleIndex(); if (firstVisibleIndex < 0) { emitOffsetChanges(); return; @@ -1220,29 +1332,12 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange; if (scrollOffset() > maxOffsetToShowFullRange) { m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange)); + firstVisibleIndex = m_layouter->firstVisibleIndex(); } - // Determine all items that are completely invisible and might be - // reused for items that just got (at least partly) visible. - // Items that do e.g. an animated moving of their position are not - // marked as invisible: This assures that a scrolling inside the view - // can be done without breaking an animation. - QList reusableItems; - QHashIterator it(m_visibleItems); - while (it.hasNext()) { - it.next(); - KItemListWidget* widget = it.value(); - const int index = widget->index(); - const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex); - if (invisible && !m_animation->isStarted(widget)) { - widget->setVisible(false); - reusableItems.append(index); + const int lastVisibleIndex = m_layouter->lastVisibleIndex(); - if (m_grouped) { - recycleGroupHeaderForWidget(widget); - } - } - } + QList reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint); // Assure that for each visible item a KItemListWidget is available. KItemListWidget // instances from invisible items are reused. If no reusable items are @@ -1274,7 +1369,8 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha if (animate && changedCount < 0) { // Items have been deleted, move the created item to the - // imaginary old position. + // imaginary old position. They will get animated to the new position + // later. const QRectF itemRect = m_layouter->itemRect(i - changedCount); if (itemRect.isEmpty()) { const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical) @@ -1285,18 +1381,14 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha } applyNewPos = false; } - } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) { - applyNewPos = false; } if (animate) { const bool itemsRemoved = (changedCount < 0); const bool itemsInserted = (changedCount > 0); - if (itemsRemoved && (i >= changedIndex + changedCount + 1)) { // The item is located after the removed items. Animate the moving of the position. - m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); - applyNewPos = false; + applyNewPos = !moveWidget(widget, itemBounds); } else if (itemsInserted && i >= changedIndex) { // The item is located after the first inserted item if (i <= changedIndex + changedCount - 1) { @@ -1310,14 +1402,14 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha // The item was already there before, so animate the moving of the position. // No moving animation is done if the item is animated by a create animation: This // prevents a "move animation mess" when inserting several ranges in parallel. - m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); - applyNewPos = false; + applyNewPos = !moveWidget(widget, itemBounds); } } else if (!itemsRemoved && !itemsInserted && !wasHidden) { // The size of the view might have been changed. Animate the moving of the position. - m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); - applyNewPos = false; + applyNewPos = !moveWidget(widget, itemBounds); } + } else { + m_animation->stop(widget); } if (applyNewPos) { @@ -1328,7 +1420,21 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha widget->setVisible(true); if (widget->size() != itemBounds.size()) { - m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size()); + // Resize the widget for the item to the changed size. + if (animate) { + // If a dynamic item size is used then no animation is done in the direction + // of the dynamic size. + if (m_itemSize.width() <= 0) { + // The width is dynamic, apply the new width without animation. + widget->resize(itemBounds.width(), widget->size().height()); + } else if (m_itemSize.height() <= 0) { + // The height is dynamic, apply the new height without animation. + widget->resize(widget->size().width(), itemBounds.height()); + } + m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size()); + } else { + widget->resize(itemBounds.size()); + } } } @@ -1349,6 +1455,83 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha emitOffsetChanges(); } +QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, + int lastVisibleIndex, + LayoutAnimationHint hint) +{ + // Determine all items that are completely invisible and might be + // reused for items that just got (at least partly) visible. If the + // animation hint is set to 'Animation' items that do e.g. an animated + // moving of their position are not marked as invisible: This assures + // that a scrolling inside the view can be done without breaking an animation. + + QList items; + + QHashIterator it(m_visibleItems); + while (it.hasNext()) { + it.next(); + + KItemListWidget* widget = it.value(); + const int index = widget->index(); + const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex); + + if (invisible) { + if (m_animation->isStarted(widget)) { + if (hint == NoAnimation) { + // Stopping the animation will call KItemListView::slotAnimationFinished() + // and the widget will be recycled if necessary there. + m_animation->stop(widget); + } + } else { + widget->setVisible(false); + items.append(index); + + if (m_grouped) { + recycleGroupHeaderForWidget(widget); + } + } + } + } + + return items; +} + +bool KItemListView::moveWidget(KItemListWidget* widget,const QRectF& itemBounds) +{ + const QPointF oldPos = widget->pos(); + const QPointF newPos = itemBounds.topLeft(); + if (oldPos == newPos) { + return false; + } + + bool startMovingAnim = m_itemSize.isEmpty() || widget->size() != itemBounds.size(); + if (!startMovingAnim) { + // When having a grid the moving-animation should only be started, if it is done within + // 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 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) { + startMovingAnim = (xDiff > yDiff && yDiff < yMax); + } else { + startMovingAnim = (yDiff > xDiff && xDiff < xMax); + } + } + + if (startMovingAnim) { + m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); + return true; + } + + m_animation->stop(widget); + m_animation->start(widget, KItemListViewAnimation::CreateAnimation); + return false; +} + void KItemListView::emitOffsetChanges() { const qreal newScrollOffset = m_layouter->scrollOffset(); @@ -1412,52 +1595,6 @@ void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) initializeItemListWidget(widget); } -void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType) -{ - // Calculate the first visible index and last visible index for the current size - const int currentFirst = m_layouter->firstVisibleIndex(); - const int currentLast = m_layouter->lastVisibleIndex(); - - const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize(); - - // Calculate the first visible index and last visible index for the new size - setLayouterSize(size, sizeType); - const int newFirst = m_layouter->firstVisibleIndex(); - const int newLast = m_layouter->lastVisibleIndex(); - - if ((currentFirst != newFirst) || (currentLast != newLast)) { - // At least one index has been changed. Assure that widgets for all possible - // visible items get created so that a move-animation can be started later. - const int maxVisibleItems = m_layouter->maximumVisibleItems(); - int minFirst = qMin(newFirst, currentFirst); - const int maxLast = qMax(newLast, currentLast); - - if (maxLast - minFirst + 1 < maxVisibleItems) { - // Increasing the size might result in a smaller KItemListView::offset(). - // Decrease the first visible index in a way that at least the maximum - // visible items are shown. - minFirst = qMax(0, maxLast - maxVisibleItems + 1); - } - - if (maxLast - minFirst > maxVisibleItems + maxVisibleItems / 2) { - // The creating of widgets is quite expensive. Assure that never more - // than 50 % of the maximum visible items get created for the animations. - return; - } - - setLayouterSize(currentSize, sizeType); - for (int i = minFirst; i <= maxLast; ++i) { - if (!m_visibleItems.contains(i)) { - KItemListWidget* widget = createWidget(i); - const QRectF itemRect = m_layouter->itemRect(i); - widget->setPos(itemRect.topLeft()); - widget->resize(itemRect.size()); - } - } - setLayouterSize(size, sizeType); - } -} - void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType) { switch (sizeType) { @@ -1478,12 +1615,15 @@ void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) widget->setSelected(selectionManager->isSelected(index)); widget->setHovered(false); widget->setAlternatingBackgroundColors(false); + widget->setEnabledSelectionToggle(enabledSelectionToggles()); widget->setIndex(index); widget->setData(m_model->data(index)); } void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) { + Q_ASSERT(m_grouped); + const int index = widget->index(); if (!m_layouter->isFirstGroupItem(index)) { // The widget does not represent the first item of a group @@ -1497,13 +1637,13 @@ 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); } - 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 @@ -1519,18 +1659,18 @@ 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()); - 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); @@ -1540,8 +1680,8 @@ void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) // group header position to the relative position. const QPointF groupHeaderPos(groupHeaderRect.x() - itemRect.x(), - groupHeaderRect.height()); - header->setPos(groupHeaderPos); - header->resize(groupHeaderRect.size()); + groupHeader->setPos(groupHeaderPos); + groupHeader->resize(groupHeaderRect.size()); } void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) @@ -1639,6 +1779,10 @@ void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges) void KItemListView::updateVisibleRolesSizes() { + if (!m_model) { + return; + } + const int itemCount = m_model->count(); if (itemCount > 0) { updateVisibleRolesSizes(KItemRangeList() << KItemRange(0, itemCount)); @@ -1647,7 +1791,7 @@ void KItemListView::updateVisibleRolesSizes() void KItemListView::updateStretchedVisibleRolesSizes() { - if (!m_itemSize.isEmpty() || m_useHeaderWidths) { + if (!m_itemSize.isEmpty() || m_useHeaderWidths || m_visibleRoles.isEmpty()) { return; } @@ -1656,7 +1800,7 @@ void KItemListView::updateStretchedVisibleRolesSizes() // size does not use the available view-size it the size of the // first role will get stretched. m_stretchedVisibleRolesSizes = m_visibleRolesSizes; - const QByteArray role = visibleRoles().first(); + const QByteArray role = m_visibleRoles.first(); QSizeF firstRoleSize = m_stretchedVisibleRolesSizes.value(role); QSizeF dynamicItemSize = m_itemSize; @@ -1723,6 +1867,81 @@ QRectF KItemListView::headerBoundaries() const return m_header ? m_header->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_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(); +} + +void KItemListView::updateGroupHeaderHeight() +{ + qreal groupHeaderHeight = m_styleOption.fontMetrics.height(); + groupHeaderHeight += (scrollOrientation() == Qt::Vertical) + ? m_styleOption.padding * 4 : m_styleOption.padding * 2; + m_layouter->setGroupHeaderHeight(groupHeaderHeight); + + updateVisibleGroupHeaders(); +} + int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) { int inc = 0; @@ -1749,6 +1968,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()