1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
4 * Based on the Itemviews NG project from Trolltech Labs: *
5 * http://qt.gitorious.org/qt-labs/itemviews-ng *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
21 ***************************************************************************/
23 #include "kitemlistview.h"
25 #include "kitemlistcontroller.h"
26 #include "kitemlistgroupheader.h"
27 #include "kitemlistselectionmanager.h"
28 #include "kitemlistsizehintresolver_p.h"
29 #include "kitemlistviewlayouter_p.h"
30 #include "kitemlistviewanimation_p.h"
31 #include "kitemlistwidget.h"
35 #include <QGraphicsSceneMouseEvent>
36 #include <QPropertyAnimation>
40 KItemListView::KItemListView(QGraphicsWidget
* parent
) :
41 QGraphicsWidget(parent
),
43 m_activeTransactions(0),
48 m_visibleRolesSizes(),
50 m_groupHeaderCreator(0),
54 m_sizeHintResolver(0),
61 setAcceptHoverEvents(true);
63 m_sizeHintResolver
= new KItemListSizeHintResolver(this);
65 m_layouter
= new KItemListViewLayouter(this);
66 m_layouter
->setSizeHintResolver(m_sizeHintResolver
);
68 m_animation
= new KItemListViewAnimation(this);
69 connect(m_animation
, SIGNAL(finished(QGraphicsWidget
*, KItemListViewAnimation::AnimationType
)),
70 this, SLOT(slotAnimationFinished(QGraphicsWidget
*, KItemListViewAnimation::AnimationType
)));
72 m_layoutTimer
= new QTimer(this);
73 m_layoutTimer
->setInterval(300);
74 m_layoutTimer
->setSingleShot(true);
75 connect(m_layoutTimer
, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished()));
78 KItemListView::~KItemListView()
80 delete m_sizeHintResolver
;
81 m_sizeHintResolver
= 0;
84 void KItemListView::setScrollOrientation(Qt::Orientation orientation
)
86 const Qt::Orientation previousOrientation
= m_layouter
->scrollOrientation();
87 if (orientation
== previousOrientation
) {
91 m_layouter
->setScrollOrientation(orientation
);
92 m_animation
->setScrollOrientation(orientation
);
93 m_sizeHintResolver
->clearCache();
95 onScrollOrientationChanged(orientation
, previousOrientation
);
98 Qt::Orientation
KItemListView::scrollOrientation() const
100 return m_layouter
->scrollOrientation();
103 void KItemListView::setItemSize(const QSizeF
& itemSize
)
105 const QSizeF previousSize
= m_itemSize
;
106 if (itemSize
== previousSize
) {
110 m_itemSize
= itemSize
;
112 if (!markVisibleRolesSizesAsDirty()) {
113 if (itemSize
.width() < previousSize
.width() || itemSize
.height() < previousSize
.height()) {
114 prepareLayoutForIncreasedItemCount(itemSize
, ItemSize
);
116 m_layouter
->setItemSize(itemSize
);
120 m_sizeHintResolver
->clearCache();
122 onItemSizeChanged(itemSize
, previousSize
);
125 QSizeF
KItemListView::itemSize() const
130 void KItemListView::setOffset(qreal offset
)
136 const qreal previousOffset
= m_layouter
->offset();
137 if (offset
== previousOffset
) {
141 m_layouter
->setOffset(offset
);
142 m_animation
->setOffset(offset
);
143 if (!m_layoutTimer
->isActive()) {
144 doLayout(NoAnimation
, 0, 0);
147 onOffsetChanged(offset
, previousOffset
);
150 qreal
KItemListView::offset() const
152 return m_layouter
->offset();
155 qreal
KItemListView::maximumOffset() const
157 return m_layouter
->maximumOffset();
160 void KItemListView::setVisibleRoles(const QHash
<QByteArray
, int>& roles
)
162 const QHash
<QByteArray
, int> previousRoles
= m_visibleRoles
;
163 m_visibleRoles
= roles
;
165 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
166 while (it
.hasNext()) {
168 KItemListWidget
* widget
= it
.value();
169 widget
->setVisibleRoles(roles
);
170 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
173 m_sizeHintResolver
->clearCache();
174 m_layouter
->markAsDirty();
175 onVisibleRolesChanged(roles
, previousRoles
);
177 markVisibleRolesSizesAsDirty();
181 QHash
<QByteArray
, int> KItemListView::visibleRoles() const
183 return m_visibleRoles
;
186 KItemListController
* KItemListView::controller() const
191 KItemModelBase
* KItemListView::model() const
196 void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase
* widgetCreator
)
198 m_widgetCreator
= widgetCreator
;
201 KItemListWidgetCreatorBase
* KItemListView::widgetCreator() const
203 return m_widgetCreator
;
206 void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase
* groupHeaderCreator
)
208 m_groupHeaderCreator
= groupHeaderCreator
;
211 KItemListGroupHeaderCreatorBase
* KItemListView::groupHeaderCreator() const
213 return m_groupHeaderCreator
;
216 void KItemListView::setStyleOption(const KItemListStyleOption
& option
)
218 const KItemListStyleOption previousOption
= m_styleOption
;
219 m_styleOption
= option
;
221 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
222 while (it
.hasNext()) {
224 it
.value()->setStyleOption(option
);
227 m_sizeHintResolver
->clearCache();
229 onStyleOptionChanged(option
, previousOption
);
232 const KItemListStyleOption
& KItemListView::styleOption() const
234 return m_styleOption
;
237 void KItemListView::setGeometry(const QRectF
& rect
)
239 QGraphicsWidget::setGeometry(rect
);
244 if (m_itemSize
.isEmpty()) {
245 m_layouter
->setItemSize(QSizeF());
248 if (m_model
->count() > 0) {
249 prepareLayoutForIncreasedItemCount(rect
.size(), LayouterSize
);
251 m_layouter
->setSize(rect
.size());
254 m_layoutTimer
->start();
257 int KItemListView::itemAt(const QPointF
& pos
) const
263 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
264 while (it
.hasNext()) {
267 const KItemListWidget
* widget
= it
.value();
268 const QPointF mappedPos
= widget
->mapFromItem(this, pos
);
269 if (widget
->contains(mappedPos
)) {
277 bool KItemListView::isAboveSelectionToggle(int index
, const QPointF
& pos
) const
284 bool KItemListView::isAboveExpansionToggle(int index
, const QPointF
& pos
) const
286 const KItemListWidget
* widget
= m_visibleItems
.value(index
);
288 const QRectF expansionToggleRect
= widget
->expansionToggleRect();
289 if (!expansionToggleRect
.isEmpty()) {
290 const QPointF mappedPos
= widget
->mapFromItem(this, pos
);
291 return expansionToggleRect
.contains(mappedPos
);
297 int KItemListView::firstVisibleIndex() const
299 return m_layouter
->firstVisibleIndex();
302 int KItemListView::lastVisibleIndex() const
304 return m_layouter
->lastVisibleIndex();
307 QSizeF
KItemListView::itemSizeHint(int index
) const
313 QHash
<QByteArray
, QSizeF
> KItemListView::visibleRoleSizes() const
315 return QHash
<QByteArray
, QSizeF
>();
318 void KItemListView::beginTransaction()
320 ++m_activeTransactions
;
321 if (m_activeTransactions
== 1) {
322 onTransactionBegin();
326 void KItemListView::endTransaction()
328 --m_activeTransactions
;
329 if (m_activeTransactions
< 0) {
330 m_activeTransactions
= 0;
331 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
334 if (m_activeTransactions
== 0) {
340 bool KItemListView::isTransactionActive() const
342 return m_activeTransactions
> 0;
345 void KItemListView::initializeItemListWidget(KItemListWidget
* item
)
350 void KItemListView::onControllerChanged(KItemListController
* current
, KItemListController
* previous
)
356 void KItemListView::onModelChanged(KItemModelBase
* current
, KItemModelBase
* previous
)
362 void KItemListView::onScrollOrientationChanged(Qt::Orientation current
, Qt::Orientation previous
)
368 void KItemListView::onItemSizeChanged(const QSizeF
& current
, const QSizeF
& previous
)
374 void KItemListView::onOffsetChanged(qreal current
, qreal previous
)
380 void KItemListView::onVisibleRolesChanged(const QHash
<QByteArray
, int>& current
, const QHash
<QByteArray
, int>& previous
)
386 void KItemListView::onStyleOptionChanged(const KItemListStyleOption
& current
, const KItemListStyleOption
& previous
)
392 void KItemListView::onTransactionBegin()
396 void KItemListView::onTransactionEnd()
400 bool KItemListView::event(QEvent
* event
)
402 // Forward all events to the controller and handle them there
403 if (m_controller
&& m_controller
->processEvent(event
, transform())) {
407 return QGraphicsWidget::event(event
);
410 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent
* event
)
415 void KItemListView::hoverMoveEvent(QGraphicsSceneHoverEvent
* event
)
421 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
422 while (it
.hasNext()) {
425 KItemListWidget
* widget
= it
.value();
426 KItemListStyleOption styleOption
= widget
->styleOption();
427 const QPointF mappedPos
= widget
->mapFromItem(this, event
->pos());
429 const bool hovered
= widget
->contains(mappedPos
) &&
430 !widget
->expansionToggleRect().contains(mappedPos
) &&
431 !widget
->selectionToggleRect().contains(mappedPos
);
433 if (!(styleOption
.state
& QStyle::State_MouseOver
)) {
434 styleOption
.state
|= QStyle::State_MouseOver
;
435 widget
->setStyleOption(styleOption
);
437 } else if (styleOption
.state
& QStyle::State_MouseOver
) {
438 styleOption
.state
&= ~QStyle::State_MouseOver
;
439 widget
->setStyleOption(styleOption
);
444 void KItemListView::hoverLeaveEvent(QGraphicsSceneHoverEvent
* event
)
452 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
453 while (it
.hasNext()) {
456 KItemListWidget
* widget
= it
.value();
457 KItemListStyleOption styleOption
= widget
->styleOption();
458 if (styleOption
.state
& QStyle::State_MouseOver
) {
459 styleOption
.state
&= ~QStyle::State_MouseOver
;
460 widget
->setStyleOption(styleOption
);
465 QList
<KItemListWidget
*> KItemListView::visibleItemListWidgets() const
467 return m_visibleItems
.values();
470 void KItemListView::slotItemsInserted(const KItemRangeList
& itemRanges
)
472 markVisibleRolesSizesAsDirty();
474 const bool hasMultipleRanges
= (itemRanges
.count() > 1);
475 if (hasMultipleRanges
) {
479 foreach (const KItemRange
& range
, itemRanges
) {
480 const int index
= range
.index
;
481 const int count
= range
.count
;
482 if (index
< 0 || count
<= 0) {
483 kWarning() << "Invalid item range (index:" << index
<< ", count:" << count
<< ")";
487 m_sizeHintResolver
->itemsInserted(index
, count
);
489 // Determine which visible items must be moved
490 QList
<int> itemsToMove
;
491 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
492 while (it
.hasNext()) {
494 const int visibleItemIndex
= it
.key();
495 if (visibleItemIndex
>= index
) {
496 itemsToMove
.append(visibleItemIndex
);
500 // Update the indexes of all KItemListWidget instances that are located
501 // after the inserted items. It is important to adjust the indexes in the order
502 // from the highest index to the lowest index to prevent overlaps when setting the new index.
504 for (int i
= itemsToMove
.count() - 1; i
>= 0; --i
) {
505 KItemListWidget
* widget
= m_visibleItems
.value(itemsToMove
[i
]);
507 setWidgetIndex(widget
, widget
->index() + count
);
510 m_layouter
->markAsDirty();
511 if (m_model
->count() == count
&& maximumOffset() > size().height()) {
512 kDebug() << "Scrollbar required, skipping layout";
513 const int scrollBarExtent
= style()->pixelMetric(QStyle::PM_ScrollBarExtent
);
514 QSizeF layouterSize
= m_layouter
->size();
515 if (scrollOrientation() == Qt::Vertical
) {
516 layouterSize
.rwidth() -= scrollBarExtent
;
518 layouterSize
.rheight() -= scrollBarExtent
;
520 m_layouter
->setSize(layouterSize
);
523 if (!hasMultipleRanges
) {
524 doLayout(Animation
, index
, count
);
529 KItemListSelectionManager
* selectionManager
= m_controller
->selectionManager();
530 const int current
= selectionManager
->currentItem();
532 selectionManager
->setCurrentItem(0);
533 } else if (current
>= index
) {
534 selectionManager
->setCurrentItem(current
+ count
);
539 if (hasMultipleRanges
) {
544 void KItemListView::slotItemsRemoved(const KItemRangeList
& itemRanges
)
546 markVisibleRolesSizesAsDirty();
548 const bool hasMultipleRanges
= (itemRanges
.count() > 1);
549 if (hasMultipleRanges
) {
553 for (int i
= itemRanges
.count() - 1; i
>= 0; --i
) {
554 const KItemRange
& range
= itemRanges
.at(i
);
555 const int index
= range
.index
;
556 const int count
= range
.count
;
557 if (index
< 0 || count
<= 0) {
558 kWarning() << "Invalid item range (index:" << index
<< ", count:" << count
<< ")";
562 m_sizeHintResolver
->itemsRemoved(index
, count
);
564 const int firstRemovedIndex
= index
;
565 const int lastRemovedIndex
= index
+ count
- 1;
566 const int lastIndex
= m_model
->count() + count
- 1;
568 // Remove all KItemListWidget instances that got deleted
569 for (int i
= firstRemovedIndex
; i
<= lastRemovedIndex
; ++i
) {
570 KItemListWidget
* widget
= m_visibleItems
.value(i
);
575 m_animation
->stop(widget
);
576 // Stopping the animation might lead to recycling the widget if
577 // it is invisible (see slotAnimationFinished()).
578 // Check again whether it is still visible:
579 if (!m_visibleItems
.contains(i
)) {
583 if (m_model
->count() == 0) {
584 // For performance reasons no animation is done when all items have
586 recycleWidget(widget
);
588 // Animate the removing of the items. Special case: When removing an item there
589 // is no valid model index available anymore. For the
590 // remove-animation the item gets removed from m_visibleItems but the widget
591 // will stay alive until the animation has been finished and will
592 // be recycled (deleted) in KItemListView::slotAnimationFinished().
593 m_visibleItems
.remove(i
);
594 widget
->setIndex(-1);
595 m_animation
->start(widget
, KItemListViewAnimation::DeleteAnimation
);
599 // Update the indexes of all KItemListWidget instances that are located
600 // after the deleted items
601 for (int i
= lastRemovedIndex
+ 1; i
<= lastIndex
; ++i
) {
602 KItemListWidget
* widget
= m_visibleItems
.value(i
);
604 const int newIndex
= i
- count
;
605 setWidgetIndex(widget
, newIndex
);
609 m_layouter
->markAsDirty();
610 if (!hasMultipleRanges
) {
611 doLayout(Animation
, index
, -count
);
615 /*KItemListSelectionManager* selectionManager = m_controller->selectionManager();
616 const int current = selectionManager->currentItem();
618 selectionManager->setCurrentItem(-1);
619 } else if (current >= index) {
620 selectionManager->setCurrentItem(current + count);
624 if (hasMultipleRanges
) {
629 void KItemListView::slotItemsChanged(const KItemRangeList
& itemRanges
,
630 const QSet
<QByteArray
>& roles
)
632 foreach (const KItemRange
& itemRange
, itemRanges
) {
633 const int index
= itemRange
.index
;
634 const int count
= itemRange
.count
;
636 m_sizeHintResolver
->itemsChanged(index
, count
, roles
);
638 const int lastIndex
= index
+ count
- 1;
639 for (int i
= index
; i
<= lastIndex
; ++i
) {
640 KItemListWidget
* widget
= m_visibleItems
.value(i
);
642 widget
->setData(m_model
->data(i
), roles
);
648 void KItemListView::slotAnimationFinished(QGraphicsWidget
* widget
,
649 KItemListViewAnimation::AnimationType type
)
651 KItemListWidget
* itemListWidget
= qobject_cast
<KItemListWidget
*>(widget
);
652 Q_ASSERT(itemListWidget
);
655 case KItemListViewAnimation::DeleteAnimation
: {
656 // As we recycle the widget in this case it is important to assure that no
657 // other animation has been started. This is a convention in KItemListView and
658 // not a requirement defined by KItemListViewAnimation.
659 Q_ASSERT(!m_animation
->isStarted(itemListWidget
));
661 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
662 // by m_visibleWidgets and must be deleted manually after the animation has
664 KItemListGroupHeader
* header
= m_visibleGroups
.value(itemListWidget
);
666 m_groupHeaderCreator
->recycle(header
);
667 m_visibleGroups
.remove(itemListWidget
);
669 m_widgetCreator
->recycle(itemListWidget
);
673 case KItemListViewAnimation::CreateAnimation
:
674 case KItemListViewAnimation::MovingAnimation
:
675 case KItemListViewAnimation::ResizeAnimation
: {
676 const int index
= itemListWidget
->index();
677 const bool invisible
= (index
< m_layouter
->firstVisibleIndex()) ||
678 (index
> m_layouter
->lastVisibleIndex());
679 if (invisible
&& !m_animation
->isStarted(itemListWidget
)) {
680 recycleWidget(itemListWidget
);
689 void KItemListView::slotLayoutTimerFinished()
691 m_layouter
->setSize(geometry().size());
692 doLayout(Animation
, 0, 0);
695 void KItemListView::setController(KItemListController
* controller
)
697 if (m_controller
!= controller
) {
698 KItemListController
* previous
= m_controller
;
699 m_controller
= controller
;
700 onControllerChanged(controller
, previous
);
704 void KItemListView::setModel(KItemModelBase
* model
)
706 if (m_model
== model
) {
710 KItemModelBase
* previous
= m_model
;
713 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
714 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
715 disconnect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
716 this, SLOT(slotItemsInserted(KItemRangeList
)));
717 disconnect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
718 this, SLOT(slotItemsRemoved(KItemRangeList
)));
722 m_layouter
->setModel(model
);
723 m_grouped
= !model
->groupRole().isEmpty();
726 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
727 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
728 connect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
729 this, SLOT(slotItemsInserted(KItemRangeList
)));
730 connect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
731 this, SLOT(slotItemsRemoved(KItemRangeList
)));
734 onModelChanged(model
, previous
);
737 void KItemListView::updateLayout()
739 doLayout(Animation
, 0, 0);
743 void KItemListView::doLayout(LayoutAnimationHint hint
, int changedIndex
, int changedCount
)
745 if (m_layoutTimer
->isActive()) {
746 kDebug() << "Stopping layout timer, synchronous layout requested";
747 m_layoutTimer
->stop();
750 if (m_model
->count() < 0 || m_activeTransactions
> 0) {
754 applyDynamicItemSize();
756 const int firstVisibleIndex
= m_layouter
->firstVisibleIndex();
757 const int lastVisibleIndex
= m_layouter
->lastVisibleIndex();
758 if (firstVisibleIndex
< 0) {
763 // Do a sanity check of the offset-property: When properties of the itemlist-view have been changed
764 // it might be possible that the maximum offset got changed too. Assure that the full visible range
765 // is still shown if the maximum offset got decreased.
766 const qreal visibleOffsetRange
= (scrollOrientation() == Qt::Horizontal
) ? size().width() : size().height();
767 const qreal maxOffsetToShowFullRange
= maximumOffset() - visibleOffsetRange
;
768 if (offset() > maxOffsetToShowFullRange
) {
769 m_layouter
->setOffset(qMax(qreal(0), maxOffsetToShowFullRange
));
772 // Determine all items that are completely invisible and might be
773 // reused for items that just got (at least partly) visible.
774 // Items that do e.g. an animated moving of their position are not
775 // marked as invisible: This assures that a scrolling inside the view
776 // can be done without breaking an animation.
777 QList
<int> reusableItems
;
778 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
779 while (it
.hasNext()) {
781 KItemListWidget
* widget
= it
.value();
782 const int index
= widget
->index();
783 const bool invisible
= (index
< firstVisibleIndex
) || (index
> lastVisibleIndex
);
784 if (invisible
&& !m_animation
->isStarted(widget
)) {
785 widget
->setVisible(false);
786 reusableItems
.append(index
);
790 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
791 // instances from invisible items are reused. If no reusable items are
792 // found then new KItemListWidget instances get created.
793 const bool animate
= (hint
== Animation
);
794 for (int i
= firstVisibleIndex
; i
<= lastVisibleIndex
; ++i
) {
795 bool applyNewPos
= true;
796 bool wasHidden
= false;
798 const QRectF itemBounds
= m_layouter
->itemBoundingRect(i
);
799 const QPointF newPos
= itemBounds
.topLeft();
800 KItemListWidget
* widget
= m_visibleItems
.value(i
);
803 if (!reusableItems
.isEmpty()) {
804 // Reuse a KItemListWidget instance from an invisible item
805 const int oldIndex
= reusableItems
.takeLast();
806 widget
= m_visibleItems
.value(oldIndex
);
807 setWidgetIndex(widget
, i
);
809 // No reusable KItemListWidget instance is available, create a new one
810 widget
= createWidget(i
);
812 widget
->resize(itemBounds
.size());
814 if (animate
&& changedCount
< 0) {
815 // Items have been deleted, move the created item to the
816 // imaginary old position.
817 const QRectF itemBoundingRect
= m_layouter
->itemBoundingRect(i
- changedCount
);
818 if (itemBoundingRect
.isEmpty()) {
819 const QPointF invisibleOldPos
= (scrollOrientation() == Qt::Vertical
)
820 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
821 widget
->setPos(invisibleOldPos
);
823 widget
->setPos(itemBoundingRect
.topLeft());
827 } else if (m_animation
->isStarted(widget
, KItemListViewAnimation::MovingAnimation
)) {
832 const bool itemsRemoved
= (changedCount
< 0);
833 const bool itemsInserted
= (changedCount
> 0);
835 if (itemsRemoved
&& (i
>= changedIndex
+ changedCount
+ 1)) {
836 // The item is located after the removed items. Animate the moving of the position.
837 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
839 } else if (itemsInserted
&& i
>= changedIndex
) {
840 // The item is located after the first inserted item
841 if (i
<= changedIndex
+ changedCount
- 1) {
842 // The item is an inserted item. Animate the appearing of the item.
843 // For performance reasons no animation is done when changedCount is equal
844 // to all available items.
845 if (changedCount
< m_model
->count()) {
846 m_animation
->start(widget
, KItemListViewAnimation::CreateAnimation
);
848 } else if (!m_animation
->isStarted(widget
, KItemListViewAnimation::CreateAnimation
)) {
849 // The item was already there before, so animate the moving of the position.
850 // No moving animation is done if the item is animated by a create animation: This
851 // prevents a "move animation mess" when inserting several ranges in parallel.
852 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
855 } else if (!itemsRemoved
&& !itemsInserted
&& !wasHidden
) {
856 // The size of the view might have been changed. Animate the moving of the position.
857 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
863 widget
->setPos(newPos
);
866 Q_ASSERT(widget
->index() == i
);
867 widget
->setVisible(true);
869 if (widget
->size() != itemBounds
.size()) {
870 m_animation
->start(widget
, KItemListViewAnimation::ResizeAnimation
, itemBounds
.size());
874 // Delete invisible KItemListWidget instances that have not been reused
875 foreach (int index
, reusableItems
) {
876 recycleWidget(m_visibleItems
.value(index
));
882 void KItemListView::emitOffsetChanges()
884 const int newOffset
= m_layouter
->offset();
885 if (m_oldOffset
!= newOffset
) {
886 emit
offsetChanged(newOffset
, m_oldOffset
);
887 m_oldOffset
= newOffset
;
890 const int newMaximumOffset
= m_layouter
->maximumOffset();
891 if (m_oldMaximumOffset
!= newMaximumOffset
) {
892 emit
maximumOffsetChanged(newMaximumOffset
, m_oldMaximumOffset
);
893 m_oldMaximumOffset
= newMaximumOffset
;
897 KItemListWidget
* KItemListView::createWidget(int index
)
899 KItemListWidget
* widget
= m_widgetCreator
->create(this);
900 widget
->setVisibleRoles(m_visibleRoles
);
901 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
902 widget
->setStyleOption(m_styleOption
);
903 widget
->setIndex(index
);
904 widget
->setData(m_model
->data(index
));
905 m_visibleItems
.insert(index
, widget
);
908 if (m_layouter
->isFirstGroupItem(index
)) {
909 KItemListGroupHeader
* header
= m_groupHeaderCreator
->create(widget
);
910 header
->setPos(0, -50);
911 header
->resize(50, 50);
912 m_visibleGroups
.insert(widget
, header
);
916 initializeItemListWidget(widget
);
920 void KItemListView::recycleWidget(KItemListWidget
* widget
)
923 KItemListGroupHeader
* header
= m_visibleGroups
.value(widget
);
925 m_groupHeaderCreator
->recycle(header
);
926 m_visibleGroups
.remove(widget
);
930 m_visibleItems
.remove(widget
->index());
931 m_widgetCreator
->recycle(widget
);
934 void KItemListView::setWidgetIndex(KItemListWidget
* widget
, int index
)
937 bool createHeader
= m_layouter
->isFirstGroupItem(index
);
938 KItemListGroupHeader
* header
= m_visibleGroups
.value(widget
);
941 createHeader
= false;
943 m_groupHeaderCreator
->recycle(header
);
944 m_visibleGroups
.remove(widget
);
949 KItemListGroupHeader
* header
= m_groupHeaderCreator
->create(widget
);
950 header
->setPos(0, -50);
951 header
->resize(50, 50);
952 m_visibleGroups
.insert(widget
, header
);
956 const int oldIndex
= widget
->index();
957 m_visibleItems
.remove(oldIndex
);
958 widget
->setVisibleRoles(m_visibleRoles
);
959 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
960 widget
->setStyleOption(m_styleOption
);
961 widget
->setIndex(index
);
962 widget
->setData(m_model
->data(index
));
963 m_visibleItems
.insert(index
, widget
);
965 initializeItemListWidget(widget
);
968 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF
& size
, SizeType sizeType
)
970 // Calculate the first visible index and last visible index for the current size
971 const int currentFirst
= m_layouter
->firstVisibleIndex();
972 const int currentLast
= m_layouter
->lastVisibleIndex();
974 const QSizeF currentSize
= (sizeType
== LayouterSize
) ? m_layouter
->size() : m_layouter
->itemSize();
976 // Calculate the first visible index and last visible index for the new size
977 setLayouterSize(size
, sizeType
);
978 const int newFirst
= m_layouter
->firstVisibleIndex();
979 const int newLast
= m_layouter
->lastVisibleIndex();
981 if ((currentFirst
!= newFirst
) || (currentLast
!= newLast
)) {
982 // At least one index has been changed. Assure that widgets for all possible
983 // visible items get created so that a move-animation can be started later.
984 const int maxVisibleItems
= m_layouter
->maximumVisibleItems();
985 int minFirst
= qMin(newFirst
, currentFirst
);
986 const int maxLast
= qMax(newLast
, currentLast
);
988 if (maxLast
- minFirst
+ 1 < maxVisibleItems
) {
989 // Increasing the size might result in a smaller KItemListView::offset().
990 // Decrease the first visible index in a way that at least the maximum
991 // visible items are shown.
992 minFirst
= qMax(0, maxLast
- maxVisibleItems
+ 1);
995 if (maxLast
- minFirst
> maxVisibleItems
+ maxVisibleItems
/ 2) {
996 // The creating of widgets is quite expensive. Assure that never more
997 // than 50 % of the maximum visible items get created for the animations.
1001 setLayouterSize(currentSize
, sizeType
);
1002 for (int i
= minFirst
; i
<= maxLast
; ++i
) {
1003 if (!m_visibleItems
.contains(i
)) {
1004 KItemListWidget
* widget
= createWidget(i
);
1005 const QPointF pos
= m_layouter
->itemBoundingRect(i
).topLeft();
1006 widget
->setPos(pos
);
1009 setLayouterSize(size
, sizeType
);
1013 void KItemListView::setLayouterSize(const QSizeF
& size
, SizeType sizeType
)
1016 case LayouterSize
: m_layouter
->setSize(size
); break;
1017 case ItemSize
: m_layouter
->setItemSize(size
); break;
1022 bool KItemListView::markVisibleRolesSizesAsDirty()
1024 const bool dirty
= m_itemSize
.isEmpty();
1026 m_visibleRolesSizes
.clear();
1027 m_layouter
->setItemSize(QSizeF());
1032 void KItemListView::applyDynamicItemSize()
1034 if (!m_itemSize
.isEmpty()) {
1038 if (m_visibleRolesSizes
.isEmpty()) {
1039 m_visibleRolesSizes
= visibleRoleSizes();
1040 foreach (KItemListWidget
* widget
, visibleItemListWidgets()) {
1041 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
1045 if (m_layouter
->itemSize().isEmpty()) {
1046 qreal requiredWidth
= 0;
1047 qreal requiredHeight
= 0;
1049 QHashIterator
<QByteArray
, QSizeF
> it(m_visibleRolesSizes
);
1050 while (it
.hasNext()) {
1052 const QSizeF
& visibleRoleSize
= it
.value();
1053 requiredWidth
+= visibleRoleSize
.width();
1054 requiredHeight
+= visibleRoleSize
.height();
1057 QSizeF dynamicItemSize
= m_itemSize
;
1058 if (dynamicItemSize
.width() <= 0) {
1059 dynamicItemSize
.setWidth(qMax(requiredWidth
, size().width()));
1061 if (dynamicItemSize
.height() <= 0) {
1062 dynamicItemSize
.setHeight(qMax(requiredHeight
, size().height()));
1065 m_layouter
->setItemSize(dynamicItemSize
);
1069 KItemListCreatorBase::~KItemListCreatorBase()
1071 qDeleteAll(m_recycleableWidgets
);
1072 qDeleteAll(m_createdWidgets
);
1075 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget
* widget
)
1077 m_createdWidgets
.insert(widget
);
1080 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget
* widget
)
1082 Q_ASSERT(m_createdWidgets
.contains(widget
));
1083 m_createdWidgets
.remove(widget
);
1085 if (m_recycleableWidgets
.count() < 100) {
1086 m_recycleableWidgets
.append(widget
);
1087 widget
->setVisible(false);
1093 QGraphicsWidget
* KItemListCreatorBase::popRecycleableWidget()
1095 if (m_recycleableWidgets
.isEmpty()) {
1099 QGraphicsWidget
* widget
= m_recycleableWidgets
.takeLast();
1100 m_createdWidgets
.insert(widget
);
1104 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1108 void KItemListWidgetCreatorBase::recycle(KItemListWidget
* widget
)
1110 widget
->setOpacity(1.0);
1111 pushRecycleableWidget(widget
);
1114 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1118 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader
* header
)
1120 header
->setOpacity(1.0);
1121 pushRecycleableWidget(header
);
1124 #include "kitemlistview.moc"