+ if (animate) {
+ if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
+ m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
+ applyNewPos = false;
+ }
+
+ const bool itemsRemoved = (changedCount < 0);
+ const bool itemsInserted = (changedCount > 0);
+ if (itemsRemoved && (i >= changedIndex)) {
+ // The item is located after the removed items. Animate the moving of the position.
+ applyNewPos = !moveWidget(widget, newPos);
+ } else if (itemsInserted && i >= changedIndex) {
+ // The item is located after the first inserted item
+ if (i <= changedIndex + changedCount - 1) {
+ // The item is an inserted item. Animate the appearing of the item.
+ // For performance reasons no animation is done when changedCount is equal
+ // to all available items.
+ if (changedCount < m_model->count()) {
+ m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
+ }
+ } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
+ // 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.
+ applyNewPos = !moveWidget(widget, newPos);
+ }
+ } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
+ // The size of the view might have been changed. Animate the moving of the position.
+ applyNewPos = !moveWidget(widget, newPos);
+ }
+ } else {
+ m_animation->stop(widget);
+ }
+
+ if (applyNewPos) {
+ widget->setPos(newPos);
+ }
+
+ Q_ASSERT(widget->index() == i);
+ widget->setVisible(true);
+
+ if (widget->size() != 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());
+ }
+ }
+
+ // Updating the cell-information must be done as last step: The decision whether the
+ // moving-animation should be started at all is based on the previous cell-information.
+ const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i));
+ m_visibleCells.insert(i, cell);
+ }
+
+ // Delete invisible KItemListWidget instances that have not been reused
+ foreach (int index, reusableItems) {
+ 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<KItemListWidget*, KItemListGroupHeader*> it(m_visibleGroups);
+ while (it.hasNext()) {
+ it.next();
+ updateGroupHeaderLayout(it.key());
+ }
+ }
+
+ emitOffsetChanges();
+}
+
+QList<int> 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<int> items;
+
+ QHashIterator<int, KItemListWidget*> 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 QPointF& newPos)
+{
+ if (widget->pos() == newPos) {
+ return false;
+ }
+
+ bool startMovingAnim = false;
+
+ if (m_itemSize.isEmpty()) {
+ // The items are not aligned in a grid but either as columns or rows.
+ startMovingAnim = true;
+ } else {
+ // 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 int index = widget->index();
+ const Cell cell = m_visibleCells.value(index);
+ if (cell.column >= 0 && cell.row >= 0) {
+ if (scrollOrientation() == Qt::Vertical) {
+ startMovingAnim = (cell.row == m_layouter->itemRow(index));
+ } else {
+ startMovingAnim = (cell.column == m_layouter->itemColumn(index));
+ }
+ }
+ }
+
+ 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();
+ if (m_oldScrollOffset != newScrollOffset) {
+ emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
+ m_oldScrollOffset = newScrollOffset;
+ }
+
+ const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset();
+ if (m_oldMaximumScrollOffset != newMaximumScrollOffset) {
+ emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
+ m_oldMaximumScrollOffset = newMaximumScrollOffset;
+ }
+
+ const qreal newItemOffset = m_layouter->itemOffset();
+ if (m_oldItemOffset != newItemOffset) {
+ emit itemOffsetChanged(newItemOffset, m_oldItemOffset);
+ m_oldItemOffset = newItemOffset;
+ }
+
+ const qreal newMaximumItemOffset = m_layouter->maximumItemOffset();
+ if (m_oldMaximumItemOffset != newMaximumItemOffset) {
+ emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
+ m_oldMaximumItemOffset = newMaximumItemOffset;
+ }
+}
+
+KItemListWidget* KItemListView::createWidget(int index)
+{
+ KItemListWidget* widget = widgetCreator()->create(this);
+ widget->setFlag(QGraphicsItem::ItemStacksBehindParent);
+
+ m_visibleItems.insert(index, widget);
+ m_visibleCells.insert(index, Cell());
+ updateWidgetProperties(widget, index);
+ initializeItemListWidget(widget);
+ return widget;
+}
+
+void KItemListView::recycleWidget(KItemListWidget* widget)
+{
+ if (m_grouped) {
+ recycleGroupHeaderForWidget(widget);
+ }
+
+ const int index = widget->index();
+ m_visibleItems.remove(index);
+ m_visibleCells.remove(index);
+
+ widgetCreator()->recycle(widget);
+}
+
+void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
+{
+ const int oldIndex = widget->index();
+ m_visibleItems.remove(oldIndex);
+ m_visibleCells.remove(oldIndex);
+
+ m_visibleItems.insert(index, widget);
+ m_visibleCells.insert(index, Cell());
+
+ widget->setIndex(index);
+}
+
+void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int index)
+{
+ const int oldIndex = widget->index();
+ const Cell oldCell = m_visibleCells.value(oldIndex);
+
+ setWidgetIndex(widget, index);
+
+ const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index));
+ const bool vertical = (scrollOrientation() == Qt::Vertical);
+ const bool updateCell = (vertical && oldCell.row == newCell.row) ||
+ (!vertical && oldCell.column == newCell.column);
+ if (updateCell) {
+ m_visibleCells.insert(index, newCell);
+ }
+}
+
+void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
+{
+ switch (sizeType) {
+ case LayouterSize: m_layouter->setSize(size); break;
+ case ItemSize: m_layouter->setItemSize(size); break;
+ default: break;
+ }
+}
+
+void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
+{
+ widget->setVisibleRoles(m_visibleRoles);
+ updateWidgetColumnWidths(widget);
+ widget->setStyleOption(m_styleOption);
+
+ const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
+
+ // In SingleSelection mode (e.g., in the Places Panel), the current item is
+ // always the selected item. It is not necessary to highlight the current item then.
+ if (m_controller->selectionBehavior() != KItemListController::SingleSelection) {
+ widget->setCurrent(index == selectionManager->currentItem());
+ }
+ widget->setSelected(selectionManager->isSelected(index));
+ widget->setHovered(false);
+ widget->setEnabledSelectionToggle(enabledSelectionToggles());
+ widget->setIndex(index);
+ widget->setData(m_model->data(index));
+ widget->setSiblingsInformation(QBitArray());
+ updateAlternateBackgroundForWidget(widget);
+
+ if (m_grouped) {
+ updateGroupHeaderForWidget(widget);
+ }
+}
+
+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
+ // and hence requires no header
+ recycleGroupHeaderForWidget(widget);
+ return;
+ }
+
+ const QList<QPair<int, QVariant> > groups = model()->groups();
+ if (groups.isEmpty() || !groupHeaderCreator()) {
+ return;
+ }
+
+ KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
+ if (!groupHeader) {
+ groupHeader = groupHeaderCreator()->create(this);
+ groupHeader->setParentItem(widget);
+ m_visibleGroups.insert(widget, groupHeader);
+ connect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged);
+ }
+ Q_ASSERT(groupHeader->parentItem() == widget);
+
+ const int groupIndex = groupIndexForItem(index);
+ Q_ASSERT(groupIndex >= 0);
+ groupHeader->setData(groups.at(groupIndex).second);
+ groupHeader->setRole(model()->sortRole());
+ groupHeader->setStyleOption(m_styleOption);
+ groupHeader->setScrollOrientation(scrollOrientation());
+ groupHeader->setItemIndex(index);
+
+ groupHeader->show();
+}
+
+void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget)
+{
+ KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
+ Q_ASSERT(groupHeader);
+
+ const int index = widget->index();
+ const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index);
+ const QRectF itemRect = m_layouter->itemRect(index);
+
+ // The group-header is a child of the itemlist widget. Translate the
+ // group header position to the relative position.
+ if (scrollOrientation() == Qt::Vertical) {
+ // In the vertical scroll orientation the group header should always span
+ // the whole width no matter which temporary position the parent widget
+ // has. In this case the x-position and width will be adjusted manually.
+ const qreal x = -widget->x() - itemOffset();
+ const qreal width = maximumItemOffset();
+ groupHeader->setPos(x, -groupHeaderRect.height());
+ groupHeader->resize(width, groupHeaderRect.size().height());
+ } else {
+ groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y());
+ groupHeader->resize(groupHeaderRect.size());
+ }
+}
+
+void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget)
+{
+ KItemListGroupHeader* header = m_visibleGroups.value(widget);
+ if (header) {
+ header->setParentItem(0);
+ groupHeaderCreator()->recycle(header);
+ m_visibleGroups.remove(widget);
+ disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged);
+ }
+}
+
+void KItemListView::updateVisibleGroupHeaders()
+{
+ Q_ASSERT(m_grouped);
+ m_layouter->markAsDirty();
+
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ updateGroupHeaderForWidget(it.value());