X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/ec38f1cdb641f0b277100edd92b268ec856e2ece..c8d8556950005dfd96ebdb41d2f43ad90356367c:/src/kitemviews/kitemlistview.cpp diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 1224ff6bd..5dbc128b5 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -23,7 +23,6 @@ #include "kitemlistview.h" #include "kitemlistcontroller.h" -#include "kitemlistgroupheader.h" #include "kitemlistheader_p.h" #include "kitemlistrubberband_p.h" #include "kitemlistselectionmanager.h" @@ -53,6 +52,7 @@ namespace { KItemListView::KItemListView(QGraphicsWidget* parent) : QGraphicsWidget(parent), + m_enabledSelectionToggles(false), m_grouped(false), m_activeTransactions(0), m_itemSize(), @@ -60,6 +60,7 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_model(0), m_visibleRoles(), m_visibleRolesSizes(), + m_stretchedVisibleRolesSizes(), m_widgetCreator(0), m_groupHeaderCreator(0), m_styleOption(), @@ -117,8 +118,19 @@ void KItemListView::setScrollOrientation(Qt::Orientation orientation) m_layouter->setScrollOrientation(orientation); m_animation->setScrollOrientation(orientation); m_sizeHintResolver->clearCache(); - updateLayout(); + + if (m_grouped) { + QMutableHashIterator it (m_visibleGroups); + while (it.hasNext()) { + it.next(); + it.value()->setScrollOrientation(orientation); + } + } + + doLayout(Animation); + onScrollOrientationChanged(orientation, previousOrientation); + emit scrollOrientationChanged(orientation, previousOrientation); } Qt::Orientation KItemListView::scrollOrientation() const @@ -135,18 +147,19 @@ void KItemListView::setItemSize(const QSizeF& itemSize) m_itemSize = itemSize; - if (itemSize.isEmpty()) { - updateVisibleRoleSizes(); - } - - if (itemSize.width() < previousSize.width() || itemSize.height() < previousSize.height()) { - prepareLayoutForIncreasedItemCount(itemSize, ItemSize); + const bool emptySize = itemSize.isEmpty(); + if (emptySize) { + updateVisibleRolesSizes(); } else { - m_layouter->setItemSize(itemSize); + if (itemSize.width() < previousSize.width() || itemSize.height() < previousSize.height()) { + prepareLayoutForIncreasedItemCount(itemSize, ItemSize); + } else { + m_layouter->setItemSize(itemSize); + } } m_sizeHintResolver->clearCache(); - updateLayout(); + doLayout(Animation); onItemSizeChanged(itemSize, previousSize); } @@ -168,10 +181,11 @@ void KItemListView::setScrollOffset(qreal offset) m_layouter->setScrollOffset(offset); m_animation->setScrollOffset(offset); - if (!m_layoutTimer->isActive()) { - doLayout(NoAnimation, 0, 0); - update(); - } + + // 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); } @@ -187,7 +201,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); + } + + // 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 @@ -210,21 +236,22 @@ void KItemListView::setVisibleRoles(const QList& roles) it.next(); KItemListWidget* widget = it.value(); widget->setVisibleRoles(roles); - widget->setVisibleRolesSizes(m_visibleRolesSizes); + widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); } m_sizeHintResolver->clearCache(); m_layouter->markAsDirty(); - onVisibleRolesChanged(roles, previousRoles); - - updateVisibleRoleSizes(); - updateLayout(); if (m_header) { m_header->setVisibleRoles(roles); m_header->setVisibleRolesWidths(headerRolesWidths()); m_useHeaderWidths = false; } + + updateVisibleRolesSizes(); + doLayout(Animation); + + onVisibleRolesChanged(roles, previousRoles); } QList KItemListView::visibleRoles() const @@ -251,34 +278,22 @@ bool KItemListView::autoScroll() const return m_autoScrollTimer != 0; } -void KItemListView::setHeaderShown(bool show) +void KItemListView::setEnabledSelectionToggles(bool enabled) { - if (show && !m_header) { - m_header = new KItemListHeader(this); - m_header->setPos(0, 0); - m_header->setModel(m_model); - m_header->setVisibleRoles(m_visibleRoles); - m_header->setVisibleRolesWidths(headerRolesWidths()); - m_header->setZValue(1); - - m_useHeaderWidths = false; - updateHeaderWidth(); - - connect(m_header, SIGNAL(visibleRoleWidthChanged(QByteArray,qreal,qreal)), - this, SLOT(slotVisibleRoleWidthChanged(QByteArray,qreal,qreal))); + if (m_enabledSelectionToggles != enabled) { + m_enabledSelectionToggles = enabled; - m_layouter->setHeaderHeight(m_header->size().height()); - } else if (!show && m_header) { - delete m_header; - m_header = 0; - m_useHeaderWidths = false; - m_layouter->setHeaderHeight(0); + QHashIterator it(m_visibleItems); + while (it.hasNext()) { + it.next(); + it.value()->setEnabledSelectionToggle(enabled); + } } } -bool KItemListView::isHeaderShown() const +bool KItemListView::enabledSelectionToggles() const { - return m_header != 0; + return m_enabledSelectionToggles; } KItemListController* KItemListView::controller() const @@ -323,7 +338,7 @@ void KItemListView::setStyleOption(const KItemListStyleOption& option) } m_sizeHintResolver->clearCache(); - updateLayout(); + doLayout(Animation); onStyleOptionChanged(option, previousOption); } @@ -335,6 +350,7 @@ const KItemListStyleOption& KItemListView::styleOption() const void KItemListView::setGeometry(const QRectF& rect) { QGraphicsWidget::setGeometry(rect); + if (!m_model) { return; } @@ -349,7 +365,10 @@ void KItemListView::setGeometry(const QRectF& rect) m_layoutTimer->start(); } - updateVisibleRoleSizes(); + // 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 @@ -370,8 +389,14 @@ int KItemListView::itemAt(const QPointF& pos) const bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const { - Q_UNUSED(index); - Q_UNUSED(pos); + 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; } @@ -410,14 +435,60 @@ QHash KItemListView::visibleRolesSizes(const KItemRangeList& return QHash(); } -QRectF KItemListView::itemBoundingRect(int index) const +bool KItemListView::supportsItemExpanding() const +{ + return false; +} + +QRectF KItemListView::itemRect(int index) const +{ + return m_layouter->itemRect(index); +} + +QRectF KItemListView::itemContextRect(int index) const { - return m_layouter->itemBoundingRect(index); + QRectF contextRect; + + const KItemListWidget* widget = m_visibleItems.value(index); + if (widget) { + contextRect = widget->iconRect() | widget->textRect(); + contextRect.translate(itemRect(index).topLeft()); + } + + return contextRect; } -int KItemListView::itemsPerOffset() const +void KItemListView::scrollToItem(int index) { - return m_layouter->itemsPerOffset(); + 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 (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::beginTransaction() @@ -438,7 +509,7 @@ void KItemListView::endTransaction() if (m_activeTransactions == 0) { onTransactionEnd(); - updateLayout(); + doLayout(Animation); } } @@ -447,6 +518,41 @@ bool KItemListView::isTransactionActive() const return m_activeTransactions > 0; } + +void KItemListView::setHeaderShown(bool show) +{ + + if (show && !m_header) { + m_header = new KItemListHeader(this); + m_header->setPos(0, 0); + m_header->setModel(m_model); + m_header->setVisibleRoles(m_visibleRoles); + m_header->setVisibleRolesWidths(headerRolesWidths()); + m_header->setZValue(1); + + 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) { + delete m_header; + m_header = 0; + m_useHeaderWidths = false; + m_layouter->setHeaderHeight(0); + } +} + +bool KItemListView::isHeaderShown() const +{ + return m_header != 0; +} + QPixmap KItemListView::createDragPixmap(const QSet& indexes) const { Q_UNUSED(indexes); @@ -600,12 +706,34 @@ QList KItemListView::visibleItemListWidgets() const void KItemListView::resizeEvent(QGraphicsSceneResizeEvent* event) { QGraphicsWidget::resizeEvent(event); - updateHeaderWidth(); + 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) { - updateVisibleRoleSizes(itemRanges); + updateVisibleRolesSizes(itemRanges); const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { @@ -650,7 +778,6 @@ 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) { @@ -663,7 +790,6 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) if (!hasMultipleRanges) { doLayout(Animation, index, count); - update(); } } @@ -678,7 +804,7 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) { - updateVisibleRoleSizes(); + updateVisibleRolesSizes(); const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { @@ -744,7 +870,6 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) m_layouter->markAsDirty(); if (!hasMultipleRanges) { doLayout(Animation, index, -count); - update(); } } @@ -757,12 +882,38 @@ 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); + + for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) { + KItemListWidget* widget = m_visibleItems.value(index); + if (widget) { + updateWidgetProperties(widget, index); + if (m_grouped) { + updateGroupHeaderForWidget(widget); + } + initializeItemListWidget(widget); + } + } + + doLayout(NoAnimation); +} + void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles) { const bool updateSizeHints = itemSizeHintUpdateRequired(roles); if (updateSizeHints) { - updateVisibleRoleSizes(itemRanges); + updateVisibleRolesSizes(itemRanges); } foreach (const KItemRange& itemRange, itemRanges) { @@ -772,6 +923,7 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, if (updateSizeHints) { m_sizeHintResolver->itemsChanged(index, count, roles); m_layouter->markAsDirty(); + if (!m_layoutTimer->isActive()) { m_layoutTimer->start(); } @@ -786,6 +938,57 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, } } + if (m_grouped && roles.contains(m_model->sortRole())) { + // The sort-role has been changed which might result + // in modified group headers + updateVisibleGroupHeaders(); + doLayout(NoAnimation); + } + } +} + +void KItemListView::slotGroupedSortingChanged(bool current) +{ + m_grouped = 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(); + } else { + // Clear all visible headers + QMutableHashIterator it (m_visibleGroups); + while (it.hasNext()) { + it.next(); + recycleGroupHeaderForWidget(it.key()); + } + Q_ASSERT(m_visibleGroups.isEmpty()); + } + + doLayout(Animation); +} + +void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + if (m_grouped) { + updateVisibleGroupHeaders(); + doLayout(Animation); + } +} + +void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + if (m_grouped) { + updateVisibleGroupHeaders(); + doLayout(Animation); } } @@ -804,33 +1007,6 @@ void KItemListView::slotCurrentChanged(int current, int previous) Q_ASSERT(!currentWidget->isCurrent()); currentWidget->setCurrent(true); } - - const QRectF viewGeometry = geometry(); - const QRectF currentBoundingRect = itemBoundingRect(current); - - if (!viewGeometry.contains(currentBoundingRect)) { - // Make sure that the new current item is fully visible in the view. - qreal newOffset = scrollOffset(); - if (currentBoundingRect.top() < viewGeometry.top()) { - Q_ASSERT(scrollOrientation() == Qt::Vertical); - newOffset += currentBoundingRect.top() - viewGeometry.top(); - } else if ((currentBoundingRect.bottom() > viewGeometry.bottom())) { - Q_ASSERT(scrollOrientation() == Qt::Vertical); - newOffset += currentBoundingRect.bottom() - viewGeometry.bottom(); - } else if (currentBoundingRect.left() < viewGeometry.left()) { - if (scrollOrientation() == Qt::Horizontal) { - newOffset += currentBoundingRect.left() - viewGeometry.left(); - } - } else if ((currentBoundingRect.right() > viewGeometry.right())) { - if (scrollOrientation() == Qt::Horizontal) { - newOffset += currentBoundingRect.right() - viewGeometry.right(); - } - } - - if (newOffset != scrollOffset()) { - emit scrollTo(newOffset); - } - } } void KItemListView::slotSelectionChanged(const QSet& current, const QSet& previous) @@ -862,11 +1038,7 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, // All KItemListWidgets that are animated by the DeleteAnimation are not maintained // by m_visibleWidgets and must be deleted manually after the animation has // been finished. - KItemListGroupHeader* header = m_visibleGroups.value(itemListWidget); - if (header) { - m_groupHeaderCreator->recycle(header); - m_visibleGroups.remove(itemListWidget); - } + recycleGroupHeaderForWidget(itemListWidget); m_widgetCreator->recycle(itemListWidget); break; } @@ -890,7 +1062,7 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, void KItemListView::slotLayoutTimerFinished() { m_layouter->setSize(geometry().size()); - doLayout(Animation, 0, 0); + doLayout(Animation); } void KItemListView::slotRubberBandPosChanged() @@ -925,9 +1097,27 @@ void KItemListView::slotVisibleRoleWidthChanged(const QByteArray& role, QSizeF roleSize = m_visibleRolesSizes.value(role); roleSize.setWidth(currentWidth); m_visibleRolesSizes.insert(role, roleSize); + m_stretchedVisibleRolesSizes.insert(role, roleSize); + + // Apply the new size to the layouter + QSizeF dynamicItemSize = m_itemSize; + if (dynamicItemSize.width() < 0) { + const qreal requiredWidth = visibleRolesSizesWidthSum(); + dynamicItemSize.setWidth(qMax(size().width(), requiredWidth)); + } + if (dynamicItemSize.height() < 0) { + const qreal requiredHeight = visibleRolesSizesHeightSum(); + dynamicItemSize.setHeight(qMax(size().height(), requiredHeight)); + } - updateVisibleRoleSizes(); - updateLayout(); + m_layouter->setItemSize(dynamicItemSize); + + // Update the role sizes for all visible widgets + foreach (KItemListWidget* widget, visibleItemListWidgets()) { + widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); + } + + doLayout(Animation); } } @@ -981,7 +1171,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() @@ -1025,11 +1217,19 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsInserted(KItemRangeList))); disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), this, SLOT(slotItemsRemoved(KItemRangeList))); + disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), + this, SLOT(slotItemsMoved(KItemRange,QList))); + disconnect(m_model, SIGNAL(groupedSortingChanged(bool)), + this, SLOT(slotGroupedSortingChanged(bool))); + disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); } m_model = model; - m_layouter->setModel(model); - m_grouped = !model->groupRole().isEmpty(); + m_layouter->setModel(model); + m_grouped = model->groupedSorting(); if (m_model) { connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), @@ -1038,6 +1238,14 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsInserted(KItemRangeList))); connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), this, SLOT(slotItemsRemoved(KItemRangeList))); + connect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), + this, SLOT(slotItemsMoved(KItemRange,QList))); + connect(m_model, SIGNAL(groupedSortingChanged(bool)), + this, SLOT(slotGroupedSortingChanged(bool))); + connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); } onModelChanged(model, previous); @@ -1048,25 +1256,16 @@ KItemListRubberBand* KItemListView::rubberBand() const return m_rubberBand; } -void KItemListView::updateLayout() -{ - doLayout(Animation, 0, 0); - update(); -} - 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_model || m_model->count() < 0 || m_activeTransactions > 0) { return; } - //markVisibleRolesSizesAsDirty(); - const int firstVisibleIndex = m_layouter->firstVisibleIndex(); const int lastVisibleIndex = m_layouter->lastVisibleIndex(); if (firstVisibleIndex < 0) { @@ -1098,6 +1297,10 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha if (invisible && !m_animation->isStarted(widget)) { widget->setVisible(false); reusableItems.append(index); + + if (m_grouped) { + recycleGroupHeaderForWidget(widget); + } } } @@ -1109,7 +1312,7 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha bool applyNewPos = true; bool wasHidden = false; - const QRectF itemBounds = m_layouter->itemBoundingRect(i); + const QRectF itemBounds = m_layouter->itemRect(i); const QPointF newPos = itemBounds.topLeft(); KItemListWidget* widget = m_visibleItems.value(i); if (!widget) { @@ -1119,6 +1322,10 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha const int oldIndex = reusableItems.takeLast(); widget = m_visibleItems.value(oldIndex); setWidgetIndex(widget, i); + + if (m_grouped) { + updateGroupHeaderForWidget(widget); + } } else { // No reusable KItemListWidget instance is available, create a new one widget = createWidget(i); @@ -1127,14 +1334,15 @@ 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. - const QRectF itemBoundingRect = m_layouter->itemBoundingRect(i - changedCount); - if (itemBoundingRect.isEmpty()) { + // 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) ? QPointF(0, size().height()) : QPointF(size().width(), 0); widget->setPos(invisibleOldPos); } else { - widget->setPos(itemBoundingRect.topLeft()); + widget->setPos(itemRect.topLeft()); } applyNewPos = false; } @@ -1145,7 +1353,6 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha 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); @@ -1181,7 +1388,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()); + } } } @@ -1190,6 +1411,15 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha recycleWidget(m_visibleItems.value(index)); } + if (m_grouped) { + // Update the layout of all visible group headers + QHashIterator it(m_visibleGroups); + while (it.hasNext()) { + it.next(); + updateGroupHeaderLayout(it.key()); + } + } + emitOffsetChanges(); } @@ -1223,16 +1453,13 @@ void KItemListView::emitOffsetChanges() KItemListWidget* KItemListView::createWidget(int index) { KItemListWidget* widget = m_widgetCreator->create(this); + widget->setFlag(QGraphicsItem::ItemStacksBehindParent); + updateWidgetProperties(widget, index); m_visibleItems.insert(index, widget); if (m_grouped) { - if (m_layouter->isFirstGroupItem(index)) { - KItemListGroupHeader* header = m_groupHeaderCreator->create(widget); - header->setPos(0, -50); - header->resize(50, 50); - m_visibleGroups.insert(widget, header); - } + updateGroupHeaderForWidget(widget); } initializeItemListWidget(widget); @@ -1242,11 +1469,7 @@ KItemListWidget* KItemListView::createWidget(int index) void KItemListView::recycleWidget(KItemListWidget* widget) { if (m_grouped) { - KItemListGroupHeader* header = m_visibleGroups.value(widget); - if (header) { - m_groupHeaderCreator->recycle(header); - m_visibleGroups.remove(widget); - } + recycleGroupHeaderForWidget(widget); } m_visibleItems.remove(widget->index()); @@ -1255,26 +1478,6 @@ void KItemListView::recycleWidget(KItemListWidget* widget) void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) { - if (m_grouped) { - bool createHeader = m_layouter->isFirstGroupItem(index); - KItemListGroupHeader* header = m_visibleGroups.value(widget); - if (header) { - if (createHeader) { - createHeader = false; - } else { - m_groupHeaderCreator->recycle(header); - m_visibleGroups.remove(widget); - } - } - - if (createHeader) { - KItemListGroupHeader* header = m_groupHeaderCreator->create(widget); - header->setPos(0, -50); - header->resize(50, 50); - m_visibleGroups.insert(widget, header); - } - } - const int oldIndex = widget->index(); m_visibleItems.remove(oldIndex); updateWidgetProperties(widget, index); @@ -1320,8 +1523,9 @@ void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeT for (int i = minFirst; i <= maxLast; ++i) { if (!m_visibleItems.contains(i)) { KItemListWidget* widget = createWidget(i); - const QPointF pos = m_layouter->itemBoundingRect(i).topLeft(); - widget->setPos(pos); + const QRectF itemRect = m_layouter->itemRect(i); + widget->setPos(itemRect.topLeft()); + widget->resize(itemRect.size()); } } setLayouterSize(size, sizeType); @@ -1340,40 +1544,110 @@ void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType) void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) { widget->setVisibleRoles(m_visibleRoles); - widget->setVisibleRolesSizes(m_visibleRolesSizes); + widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); widget->setStyleOption(m_styleOption); const KItemListSelectionManager* selectionManager = m_controller->selectionManager(); widget->setCurrent(index == selectionManager->currentItem()); - - if (selectionManager->hasSelection()) { - const QSet selectedItems = selectionManager->selectedItems(); - widget->setSelected(selectedItems.contains(index)); - } else { - widget->setSelected(false); - } - + 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::updateHeaderWidth() +void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) { - if (!m_header) { + 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 + // and hence requires no header + recycleGroupHeaderForWidget(widget); + return; + } + + const QList > groups = model()->groups(); + if (groups.isEmpty()) { return; } - // TODO 1: Use the required width of all roles - m_header->resize(size().width(), m_header->size().height()); + KItemListGroupHeader* header = m_visibleGroups.value(widget); + if (!header) { + header = m_groupHeaderCreator->create(this); + header->setParentItem(widget); + m_visibleGroups.insert(widget, header); + } + Q_ASSERT(header->parentItem() == widget); + + // Determine the shown data for the header by doing a binary + // search in the groups-list + int min = 0; + int max = groups.count() - 1; + int mid = 0; + do { + mid = (min + max) / 2; + if (index > groups.at(mid).first) { + min = mid + 1; + } else { + max = mid - 1; + } + } 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()); + + header->show(); +} + +void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) +{ + KItemListGroupHeader* header = m_visibleGroups.value(widget); + Q_ASSERT(header); + + 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. + const QPointF groupHeaderPos(groupHeaderRect.x() - itemRect.x(), + - groupHeaderRect.height()); + header->setPos(groupHeaderPos); + header->resize(groupHeaderRect.size()); +} + +void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) +{ + KItemListGroupHeader* header = m_visibleGroups.value(widget); + if (header) { + header->setParentItem(0); + m_groupHeaderCreator->recycle(header); + m_visibleGroups.remove(widget); + } +} + +void KItemListView::updateVisibleGroupHeaders() +{ + Q_ASSERT(m_grouped); + m_layouter->markAsDirty(); + + QHashIterator it(m_visibleItems); + while (it.hasNext()) { + it.next(); + updateGroupHeaderForWidget(it.value()); + } } QHash KItemListView::headerRolesWidths() const { QHash rolesWidths; - QHashIterator it(m_visibleRolesSizes); + QHashIterator it(m_stretchedVisibleRolesSizes); while (it.hasNext()) { it.next(); rolesWidths.insert(it.key(), it.value().width()); @@ -1382,7 +1656,7 @@ QHash KItemListView::headerRolesWidths() const return rolesWidths; } -void KItemListView::updateVisibleRoleSizes(const KItemRangeList& itemRanges) +void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges) { if (!m_itemSize.isEmpty() || m_useHeaderWidths) { return; @@ -1395,8 +1669,21 @@ void KItemListView::updateVisibleRoleSizes(const KItemRangeList& itemRanges) } if (itemCount == rangesItemCount) { - // The sizes of all roles need to be determined m_visibleRolesSizes = visibleRolesSizes(itemRanges); + if (m_header) { + // Assure the the sizes are not smaller than the minimum defined by the header + // TODO: Currently only implemented for a top-aligned header + const qreal minHeaderRoleWidth = m_header->minimumRoleWidth(); + QMutableHashIterator it (m_visibleRolesSizes); + while (it.hasNext()) { + it.next(); + const QSizeF& size = it.value(); + if (size.width() < minHeaderRoleWidth) { + const QSizeF newSize(minHeaderRoleWidth, size.height()); + m_visibleRolesSizes.insert(it.key(), newSize); + } + } + } } else { // Only a sub range of the roles need to be determined. // The chances are good that the sizes of the sub ranges @@ -1419,47 +1706,102 @@ void KItemListView::updateVisibleRoleSizes(const KItemRangeList& itemRanges) if (!updateRequired) { // All the updated sizes are smaller than the current sizes and no change - // of the roles-widths is required + // of the stretched roles-widths is required return; } } - if (m_header) { - m_header->setVisibleRolesWidths(headerRolesWidths()); + updateStretchedVisibleRolesSizes(); +} + +void KItemListView::updateVisibleRolesSizes() +{ + if (!m_model) { + return; } - // Calculate the maximum size of an item by considering the - // visible role sizes and apply them to the layouter. - qreal requiredWidth = 0; - qreal requiredHeight = 0; + const int itemCount = m_model->count(); + if (itemCount > 0) { + updateVisibleRolesSizes(KItemRangeList() << KItemRange(0, itemCount)); + } +} - QHashIterator it(m_visibleRolesSizes); - while (it.hasNext()) { - it.next(); - const QSizeF& visibleRoleSize = it.value(); - requiredWidth += visibleRoleSize.width(); - requiredHeight += visibleRoleSize.height(); +void KItemListView::updateStretchedVisibleRolesSizes() +{ + if (!m_itemSize.isEmpty() || m_useHeaderWidths || m_visibleRoles.isEmpty()) { + return; } + // Calculate the maximum size of an item by considering the + // visible role sizes and apply them to the layouter. If the + // 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 = m_visibleRoles.first(); + QSizeF firstRoleSize = m_stretchedVisibleRolesSizes.value(role); + QSizeF dynamicItemSize = m_itemSize; + if (dynamicItemSize.width() <= 0) { - dynamicItemSize.setWidth(qMax(requiredWidth, size().width())); + const qreal requiredWidth = visibleRolesSizesWidthSum(); + const qreal availableWidth = size().width(); + if (requiredWidth < availableWidth) { + // Stretch the first role to use the whole width for the item + firstRoleSize.rwidth() += availableWidth - requiredWidth; + m_stretchedVisibleRolesSizes.insert(role, firstRoleSize); + } + dynamicItemSize.setWidth(qMax(requiredWidth, availableWidth)); } + if (dynamicItemSize.height() <= 0) { - dynamicItemSize.setHeight(qMax(requiredHeight, size().height())); + const qreal requiredHeight = visibleRolesSizesHeightSum(); + const qreal availableHeight = size().height(); + if (requiredHeight < availableHeight) { + // Stretch the first role to use the whole height for the item + firstRoleSize.rheight() += availableHeight - requiredHeight; + m_stretchedVisibleRolesSizes.insert(role, firstRoleSize); + } + dynamicItemSize.setHeight(qMax(requiredHeight, availableHeight)); } m_layouter->setItemSize(dynamicItemSize); + if (m_header) { + m_header->setVisibleRolesWidths(headerRolesWidths()); + m_header->resize(dynamicItemSize.width(), m_header->size().height()); + } + // Update the role sizes for all visible widgets foreach (KItemListWidget* widget, visibleItemListWidgets()) { - widget->setVisibleRolesSizes(m_visibleRolesSizes); + widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); + } +} + +qreal KItemListView::visibleRolesSizesWidthSum() const +{ + qreal widthSum = 0; + QHashIterator it(m_visibleRolesSizes); + while (it.hasNext()) { + it.next(); + widthSum += it.value().width(); + } + return widthSum; +} + +qreal KItemListView::visibleRolesSizesHeightSum() const +{ + qreal heightSum = 0; + QHashIterator it(m_visibleRolesSizes); + while (it.hasNext()) { + it.next(); + heightSum += it.value().height(); } + return heightSum; } -void KItemListView::updateVisibleRoleSizes() +QRectF KItemListView::headerBoundaries() const { - updateVisibleRoleSizes(KItemRangeList() << KItemRange(0, m_model->count())); + return m_header ? m_header->geometry() : QRectF(); } int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) @@ -1531,6 +1873,7 @@ KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase() void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget) { + widget->setParentItem(0); widget->setOpacity(1.0); pushRecycleableWidget(widget); }