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
259 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
260 while (it
.hasNext()) {
263 const KItemListWidget
* widget
= it
.value();
264 const QPointF mappedPos
= widget
->mapFromItem(this, pos
);
265 if (widget
->contains(mappedPos
)) {
273 bool KItemListView::isAboveSelectionToggle(int index
, const QPointF
& pos
) const
280 bool KItemListView::isAboveExpansionToggle(int index
, const QPointF
& pos
) const
282 const KItemListWidget
* widget
= m_visibleItems
.value(index
);
284 const QRectF expansionToggleRect
= widget
->expansionToggleRect();
285 if (!expansionToggleRect
.isEmpty()) {
286 const QPointF mappedPos
= widget
->mapFromItem(this, pos
);
287 return expansionToggleRect
.contains(mappedPos
);
293 int KItemListView::firstVisibleIndex() const
295 return m_layouter
->firstVisibleIndex();
298 int KItemListView::lastVisibleIndex() const
300 return m_layouter
->lastVisibleIndex();
303 QSizeF
KItemListView::itemSizeHint(int index
) const
309 QHash
<QByteArray
, QSizeF
> KItemListView::visibleRoleSizes() const
311 return QHash
<QByteArray
, QSizeF
>();
314 QRectF
KItemListView::itemBoundingRect(int index
) const
316 return m_layouter
->itemBoundingRect(index
);
319 int KItemListView::itemsPerOffset() const
321 return m_layouter
->itemsPerOffset();
324 void KItemListView::beginTransaction()
326 ++m_activeTransactions
;
327 if (m_activeTransactions
== 1) {
328 onTransactionBegin();
332 void KItemListView::endTransaction()
334 --m_activeTransactions
;
335 if (m_activeTransactions
< 0) {
336 m_activeTransactions
= 0;
337 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
340 if (m_activeTransactions
== 0) {
346 bool KItemListView::isTransactionActive() const
348 return m_activeTransactions
> 0;
351 void KItemListView::initializeItemListWidget(KItemListWidget
* item
)
356 void KItemListView::onControllerChanged(KItemListController
* current
, KItemListController
* previous
)
362 void KItemListView::onModelChanged(KItemModelBase
* current
, KItemModelBase
* previous
)
368 void KItemListView::onScrollOrientationChanged(Qt::Orientation current
, Qt::Orientation previous
)
374 void KItemListView::onItemSizeChanged(const QSizeF
& current
, const QSizeF
& previous
)
380 void KItemListView::onOffsetChanged(qreal current
, qreal previous
)
386 void KItemListView::onVisibleRolesChanged(const QHash
<QByteArray
, int>& current
, const QHash
<QByteArray
, int>& previous
)
392 void KItemListView::onStyleOptionChanged(const KItemListStyleOption
& current
, const KItemListStyleOption
& previous
)
398 void KItemListView::onTransactionBegin()
402 void KItemListView::onTransactionEnd()
406 bool KItemListView::event(QEvent
* event
)
408 // Forward all events to the controller and handle them there
409 if (m_controller
&& m_controller
->processEvent(event
, transform())) {
413 return QGraphicsWidget::event(event
);
416 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent
* event
)
421 QList
<KItemListWidget
*> KItemListView::visibleItemListWidgets() const
423 return m_visibleItems
.values();
426 void KItemListView::slotItemsInserted(const KItemRangeList
& itemRanges
)
428 markVisibleRolesSizesAsDirty();
430 const bool hasMultipleRanges
= (itemRanges
.count() > 1);
431 if (hasMultipleRanges
) {
435 int previouslyInsertedCount
= 0;
436 foreach (const KItemRange
& range
, itemRanges
) {
437 // range.index is related to the model before anything has been inserted.
438 // As in each loop the current item-range gets inserted the index must
439 // be increased by the already previoulsy inserted items.
440 const int index
= range
.index
+ previouslyInsertedCount
;
441 const int count
= range
.count
;
442 if (index
< 0 || count
<= 0) {
443 kWarning() << "Invalid item range (index:" << index
<< ", count:" << count
<< ")";
446 previouslyInsertedCount
+= count
;
448 m_sizeHintResolver
->itemsInserted(index
, count
);
450 // Determine which visible items must be moved
451 QList
<int> itemsToMove
;
452 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
453 while (it
.hasNext()) {
455 const int visibleItemIndex
= it
.key();
456 if (visibleItemIndex
>= index
) {
457 itemsToMove
.append(visibleItemIndex
);
461 // Update the indexes of all KItemListWidget instances that are located
462 // after the inserted items. It is important to adjust the indexes in the order
463 // from the highest index to the lowest index to prevent overlaps when setting the new index.
465 for (int i
= itemsToMove
.count() - 1; i
>= 0; --i
) {
466 KItemListWidget
* widget
= m_visibleItems
.value(itemsToMove
[i
]);
468 setWidgetIndex(widget
, widget
->index() + count
);
471 m_layouter
->markAsDirty();
472 if (m_model
->count() == count
&& maximumOffset() > size().height()) {
473 kDebug() << "Scrollbar required, skipping layout";
474 const int scrollBarExtent
= style()->pixelMetric(QStyle::PM_ScrollBarExtent
);
475 QSizeF layouterSize
= m_layouter
->size();
476 if (scrollOrientation() == Qt::Vertical
) {
477 layouterSize
.rwidth() -= scrollBarExtent
;
479 layouterSize
.rheight() -= scrollBarExtent
;
481 m_layouter
->setSize(layouterSize
);
484 if (!hasMultipleRanges
) {
485 doLayout(Animation
, index
, count
);
491 m_controller
->selectionManager()->itemsInserted(itemRanges
);
494 if (hasMultipleRanges
) {
499 void KItemListView::slotItemsRemoved(const KItemRangeList
& itemRanges
)
501 markVisibleRolesSizesAsDirty();
503 const bool hasMultipleRanges
= (itemRanges
.count() > 1);
504 if (hasMultipleRanges
) {
508 for (int i
= itemRanges
.count() - 1; i
>= 0; --i
) {
509 const KItemRange
& range
= itemRanges
.at(i
);
510 const int index
= range
.index
;
511 const int count
= range
.count
;
512 if (index
< 0 || count
<= 0) {
513 kWarning() << "Invalid item range (index:" << index
<< ", count:" << count
<< ")";
517 m_sizeHintResolver
->itemsRemoved(index
, count
);
519 const int firstRemovedIndex
= index
;
520 const int lastRemovedIndex
= index
+ count
- 1;
521 const int lastIndex
= m_model
->count() + count
- 1;
523 // Remove all KItemListWidget instances that got deleted
524 for (int i
= firstRemovedIndex
; i
<= lastRemovedIndex
; ++i
) {
525 KItemListWidget
* widget
= m_visibleItems
.value(i
);
530 m_animation
->stop(widget
);
531 // Stopping the animation might lead to recycling the widget if
532 // it is invisible (see slotAnimationFinished()).
533 // Check again whether it is still visible:
534 if (!m_visibleItems
.contains(i
)) {
538 if (m_model
->count() == 0) {
539 // For performance reasons no animation is done when all items have
541 recycleWidget(widget
);
543 // Animate the removing of the items. Special case: When removing an item there
544 // is no valid model index available anymore. For the
545 // remove-animation the item gets removed from m_visibleItems but the widget
546 // will stay alive until the animation has been finished and will
547 // be recycled (deleted) in KItemListView::slotAnimationFinished().
548 m_visibleItems
.remove(i
);
549 widget
->setIndex(-1);
550 m_animation
->start(widget
, KItemListViewAnimation::DeleteAnimation
);
554 // Update the indexes of all KItemListWidget instances that are located
555 // after the deleted items
556 for (int i
= lastRemovedIndex
+ 1; i
<= lastIndex
; ++i
) {
557 KItemListWidget
* widget
= m_visibleItems
.value(i
);
559 const int newIndex
= i
- count
;
560 setWidgetIndex(widget
, newIndex
);
564 m_layouter
->markAsDirty();
565 if (!hasMultipleRanges
) {
566 doLayout(Animation
, index
, -count
);
572 m_controller
->selectionManager()->itemsRemoved(itemRanges
);
575 if (hasMultipleRanges
) {
580 void KItemListView::slotItemsChanged(const KItemRangeList
& itemRanges
,
581 const QSet
<QByteArray
>& roles
)
583 foreach (const KItemRange
& itemRange
, itemRanges
) {
584 const int index
= itemRange
.index
;
585 const int count
= itemRange
.count
;
587 m_sizeHintResolver
->itemsChanged(index
, count
, roles
);
589 const int lastIndex
= index
+ count
- 1;
590 for (int i
= index
; i
<= lastIndex
; ++i
) {
591 KItemListWidget
* widget
= m_visibleItems
.value(i
);
593 widget
->setData(m_model
->data(i
), roles
);
599 void KItemListView::slotCurrentChanged(int current
, int previous
)
603 KItemListWidget
* previousWidget
= m_visibleItems
.value(previous
, 0);
604 if (previousWidget
) {
605 Q_ASSERT(previousWidget
->isCurrent());
606 previousWidget
->setCurrent(false);
609 KItemListWidget
* currentWidget
= m_visibleItems
.value(current
, 0);
611 Q_ASSERT(!currentWidget
->isCurrent());
612 currentWidget
->setCurrent(true);
615 const QRectF viewGeometry
= geometry();
616 const QRectF currentBoundingRect
= itemBoundingRect(current
);
618 if (!viewGeometry
.contains(currentBoundingRect
)) {
619 // Make sure that the new current item is fully visible in the view.
620 qreal newOffset
= offset();
621 if (currentBoundingRect
.top() < viewGeometry
.top()) {
622 Q_ASSERT(scrollOrientation() == Qt::Vertical
);
623 newOffset
+= currentBoundingRect
.top() - viewGeometry
.top();
625 else if ((currentBoundingRect
.bottom() > viewGeometry
.bottom())) {
626 Q_ASSERT(scrollOrientation() == Qt::Vertical
);
627 newOffset
+= currentBoundingRect
.bottom() - viewGeometry
.bottom();
629 else if (currentBoundingRect
.left() < viewGeometry
.left()) {
630 if (scrollOrientation() == Qt::Horizontal
) {
631 newOffset
+= currentBoundingRect
.left() - viewGeometry
.left();
634 else if ((currentBoundingRect
.right() > viewGeometry
.right())) {
635 if (scrollOrientation() == Qt::Horizontal
) {
636 newOffset
+= currentBoundingRect
.right() - viewGeometry
.right();
640 emit
scrollTo(newOffset
);
644 void KItemListView::slotSelectionChanged(const QSet
<int>& current
, const QSet
<int>& previous
)
648 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
649 while (it
.hasNext()) {
651 const int index
= it
.key();
652 KItemListWidget
* widget
= it
.value();
653 widget
->setSelected(current
.contains(index
));
657 void KItemListView::slotAnimationFinished(QGraphicsWidget
* widget
,
658 KItemListViewAnimation::AnimationType type
)
660 KItemListWidget
* itemListWidget
= qobject_cast
<KItemListWidget
*>(widget
);
661 Q_ASSERT(itemListWidget
);
664 case KItemListViewAnimation::DeleteAnimation
: {
665 // As we recycle the widget in this case it is important to assure that no
666 // other animation has been started. This is a convention in KItemListView and
667 // not a requirement defined by KItemListViewAnimation.
668 Q_ASSERT(!m_animation
->isStarted(itemListWidget
));
670 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
671 // by m_visibleWidgets and must be deleted manually after the animation has
673 KItemListGroupHeader
* header
= m_visibleGroups
.value(itemListWidget
);
675 m_groupHeaderCreator
->recycle(header
);
676 m_visibleGroups
.remove(itemListWidget
);
678 m_widgetCreator
->recycle(itemListWidget
);
682 case KItemListViewAnimation::CreateAnimation
:
683 case KItemListViewAnimation::MovingAnimation
:
684 case KItemListViewAnimation::ResizeAnimation
: {
685 const int index
= itemListWidget
->index();
686 const bool invisible
= (index
< m_layouter
->firstVisibleIndex()) ||
687 (index
> m_layouter
->lastVisibleIndex());
688 if (invisible
&& !m_animation
->isStarted(itemListWidget
)) {
689 recycleWidget(itemListWidget
);
698 void KItemListView::slotLayoutTimerFinished()
700 m_layouter
->setSize(geometry().size());
701 doLayout(Animation
, 0, 0);
704 void KItemListView::setController(KItemListController
* controller
)
706 if (m_controller
!= controller
) {
707 KItemListController
* previous
= m_controller
;
709 KItemListSelectionManager
* selectionManager
= previous
->selectionManager();
710 disconnect(selectionManager
, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
711 disconnect(selectionManager
, SIGNAL(selectionChanged(QSet
<int>,QSet
<int>)), this, SLOT(slotSelectionChanged(QSet
<int>,QSet
<int>)));
714 m_controller
= controller
;
717 KItemListSelectionManager
* selectionManager
= controller
->selectionManager();
718 connect(selectionManager
, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
719 connect(selectionManager
, SIGNAL(selectionChanged(QSet
<int>,QSet
<int>)), this, SLOT(slotSelectionChanged(QSet
<int>,QSet
<int>)));
722 onControllerChanged(controller
, previous
);
726 void KItemListView::setModel(KItemModelBase
* model
)
728 if (m_model
== model
) {
732 KItemModelBase
* previous
= m_model
;
735 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
736 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
737 disconnect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
738 this, SLOT(slotItemsInserted(KItemRangeList
)));
739 disconnect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
740 this, SLOT(slotItemsRemoved(KItemRangeList
)));
744 m_layouter
->setModel(model
);
745 m_grouped
= !model
->groupRole().isEmpty();
748 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
749 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
750 connect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
751 this, SLOT(slotItemsInserted(KItemRangeList
)));
752 connect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
753 this, SLOT(slotItemsRemoved(KItemRangeList
)));
756 onModelChanged(model
, previous
);
759 void KItemListView::updateLayout()
761 doLayout(Animation
, 0, 0);
765 void KItemListView::doLayout(LayoutAnimationHint hint
, int changedIndex
, int changedCount
)
767 if (m_layoutTimer
->isActive()) {
768 kDebug() << "Stopping layout timer, synchronous layout requested";
769 m_layoutTimer
->stop();
772 if (m_model
->count() < 0 || m_activeTransactions
> 0) {
776 applyDynamicItemSize();
778 const int firstVisibleIndex
= m_layouter
->firstVisibleIndex();
779 const int lastVisibleIndex
= m_layouter
->lastVisibleIndex();
780 if (firstVisibleIndex
< 0) {
785 // Do a sanity check of the offset-property: When properties of the itemlist-view have been changed
786 // it might be possible that the maximum offset got changed too. Assure that the full visible range
787 // is still shown if the maximum offset got decreased.
788 const qreal visibleOffsetRange
= (scrollOrientation() == Qt::Horizontal
) ? size().width() : size().height();
789 const qreal maxOffsetToShowFullRange
= maximumOffset() - visibleOffsetRange
;
790 if (offset() > maxOffsetToShowFullRange
) {
791 m_layouter
->setOffset(qMax(qreal(0), maxOffsetToShowFullRange
));
794 // Determine all items that are completely invisible and might be
795 // reused for items that just got (at least partly) visible.
796 // Items that do e.g. an animated moving of their position are not
797 // marked as invisible: This assures that a scrolling inside the view
798 // can be done without breaking an animation.
799 QList
<int> reusableItems
;
800 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
801 while (it
.hasNext()) {
803 KItemListWidget
* widget
= it
.value();
804 const int index
= widget
->index();
805 const bool invisible
= (index
< firstVisibleIndex
) || (index
> lastVisibleIndex
);
806 if (invisible
&& !m_animation
->isStarted(widget
)) {
807 widget
->setVisible(false);
808 reusableItems
.append(index
);
812 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
813 // instances from invisible items are reused. If no reusable items are
814 // found then new KItemListWidget instances get created.
815 const bool animate
= (hint
== Animation
);
816 for (int i
= firstVisibleIndex
; i
<= lastVisibleIndex
; ++i
) {
817 bool applyNewPos
= true;
818 bool wasHidden
= false;
820 const QRectF itemBounds
= m_layouter
->itemBoundingRect(i
);
821 const QPointF newPos
= itemBounds
.topLeft();
822 KItemListWidget
* widget
= m_visibleItems
.value(i
);
825 if (!reusableItems
.isEmpty()) {
826 // Reuse a KItemListWidget instance from an invisible item
827 const int oldIndex
= reusableItems
.takeLast();
828 widget
= m_visibleItems
.value(oldIndex
);
829 setWidgetIndex(widget
, i
);
831 // No reusable KItemListWidget instance is available, create a new one
832 widget
= createWidget(i
);
834 widget
->resize(itemBounds
.size());
836 if (animate
&& changedCount
< 0) {
837 // Items have been deleted, move the created item to the
838 // imaginary old position.
839 const QRectF itemBoundingRect
= m_layouter
->itemBoundingRect(i
- changedCount
);
840 if (itemBoundingRect
.isEmpty()) {
841 const QPointF invisibleOldPos
= (scrollOrientation() == Qt::Vertical
)
842 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
843 widget
->setPos(invisibleOldPos
);
845 widget
->setPos(itemBoundingRect
.topLeft());
849 } else if (m_animation
->isStarted(widget
, KItemListViewAnimation::MovingAnimation
)) {
854 const bool itemsRemoved
= (changedCount
< 0);
855 const bool itemsInserted
= (changedCount
> 0);
857 if (itemsRemoved
&& (i
>= changedIndex
+ changedCount
+ 1)) {
858 // The item is located after the removed items. Animate the moving of the position.
859 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
861 } else if (itemsInserted
&& i
>= changedIndex
) {
862 // The item is located after the first inserted item
863 if (i
<= changedIndex
+ changedCount
- 1) {
864 // The item is an inserted item. Animate the appearing of the item.
865 // For performance reasons no animation is done when changedCount is equal
866 // to all available items.
867 if (changedCount
< m_model
->count()) {
868 m_animation
->start(widget
, KItemListViewAnimation::CreateAnimation
);
870 } else if (!m_animation
->isStarted(widget
, KItemListViewAnimation::CreateAnimation
)) {
871 // The item was already there before, so animate the moving of the position.
872 // No moving animation is done if the item is animated by a create animation: This
873 // prevents a "move animation mess" when inserting several ranges in parallel.
874 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
877 } else if (!itemsRemoved
&& !itemsInserted
&& !wasHidden
) {
878 // The size of the view might have been changed. Animate the moving of the position.
879 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
885 widget
->setPos(newPos
);
888 Q_ASSERT(widget
->index() == i
);
889 widget
->setVisible(true);
891 if (widget
->size() != itemBounds
.size()) {
892 m_animation
->start(widget
, KItemListViewAnimation::ResizeAnimation
, itemBounds
.size());
896 // Delete invisible KItemListWidget instances that have not been reused
897 foreach (int index
, reusableItems
) {
898 recycleWidget(m_visibleItems
.value(index
));
904 void KItemListView::emitOffsetChanges()
906 const int newOffset
= m_layouter
->offset();
907 if (m_oldOffset
!= newOffset
) {
908 emit
offsetChanged(newOffset
, m_oldOffset
);
909 m_oldOffset
= newOffset
;
912 const int newMaximumOffset
= m_layouter
->maximumOffset();
913 if (m_oldMaximumOffset
!= newMaximumOffset
) {
914 emit
maximumOffsetChanged(newMaximumOffset
, m_oldMaximumOffset
);
915 m_oldMaximumOffset
= newMaximumOffset
;
919 KItemListWidget
* KItemListView::createWidget(int index
)
921 KItemListWidget
* widget
= m_widgetCreator
->create(this);
922 updateWidgetProperties(widget
, index
);
923 m_visibleItems
.insert(index
, widget
);
926 if (m_layouter
->isFirstGroupItem(index
)) {
927 KItemListGroupHeader
* header
= m_groupHeaderCreator
->create(widget
);
928 header
->setPos(0, -50);
929 header
->resize(50, 50);
930 m_visibleGroups
.insert(widget
, header
);
934 initializeItemListWidget(widget
);
938 void KItemListView::recycleWidget(KItemListWidget
* widget
)
941 KItemListGroupHeader
* header
= m_visibleGroups
.value(widget
);
943 m_groupHeaderCreator
->recycle(header
);
944 m_visibleGroups
.remove(widget
);
948 m_visibleItems
.remove(widget
->index());
949 m_widgetCreator
->recycle(widget
);
952 void KItemListView::setWidgetIndex(KItemListWidget
* widget
, int index
)
955 bool createHeader
= m_layouter
->isFirstGroupItem(index
);
956 KItemListGroupHeader
* header
= m_visibleGroups
.value(widget
);
959 createHeader
= false;
961 m_groupHeaderCreator
->recycle(header
);
962 m_visibleGroups
.remove(widget
);
967 KItemListGroupHeader
* header
= m_groupHeaderCreator
->create(widget
);
968 header
->setPos(0, -50);
969 header
->resize(50, 50);
970 m_visibleGroups
.insert(widget
, header
);
974 const int oldIndex
= widget
->index();
975 m_visibleItems
.remove(oldIndex
);
976 updateWidgetProperties(widget
, index
);
977 m_visibleItems
.insert(index
, widget
);
979 initializeItemListWidget(widget
);
982 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF
& size
, SizeType sizeType
)
984 // Calculate the first visible index and last visible index for the current size
985 const int currentFirst
= m_layouter
->firstVisibleIndex();
986 const int currentLast
= m_layouter
->lastVisibleIndex();
988 const QSizeF currentSize
= (sizeType
== LayouterSize
) ? m_layouter
->size() : m_layouter
->itemSize();
990 // Calculate the first visible index and last visible index for the new size
991 setLayouterSize(size
, sizeType
);
992 const int newFirst
= m_layouter
->firstVisibleIndex();
993 const int newLast
= m_layouter
->lastVisibleIndex();
995 if ((currentFirst
!= newFirst
) || (currentLast
!= newLast
)) {
996 // At least one index has been changed. Assure that widgets for all possible
997 // visible items get created so that a move-animation can be started later.
998 const int maxVisibleItems
= m_layouter
->maximumVisibleItems();
999 int minFirst
= qMin(newFirst
, currentFirst
);
1000 const int maxLast
= qMax(newLast
, currentLast
);
1002 if (maxLast
- minFirst
+ 1 < maxVisibleItems
) {
1003 // Increasing the size might result in a smaller KItemListView::offset().
1004 // Decrease the first visible index in a way that at least the maximum
1005 // visible items are shown.
1006 minFirst
= qMax(0, maxLast
- maxVisibleItems
+ 1);
1009 if (maxLast
- minFirst
> maxVisibleItems
+ maxVisibleItems
/ 2) {
1010 // The creating of widgets is quite expensive. Assure that never more
1011 // than 50 % of the maximum visible items get created for the animations.
1015 setLayouterSize(currentSize
, sizeType
);
1016 for (int i
= minFirst
; i
<= maxLast
; ++i
) {
1017 if (!m_visibleItems
.contains(i
)) {
1018 KItemListWidget
* widget
= createWidget(i
);
1019 const QPointF pos
= m_layouter
->itemBoundingRect(i
).topLeft();
1020 widget
->setPos(pos
);
1023 setLayouterSize(size
, sizeType
);
1027 void KItemListView::setLayouterSize(const QSizeF
& size
, SizeType sizeType
)
1030 case LayouterSize
: m_layouter
->setSize(size
); break;
1031 case ItemSize
: m_layouter
->setItemSize(size
); break;
1036 bool KItemListView::markVisibleRolesSizesAsDirty()
1038 const bool dirty
= m_itemSize
.isEmpty();
1040 m_visibleRolesSizes
.clear();
1041 m_layouter
->setItemSize(QSizeF());
1046 void KItemListView::applyDynamicItemSize()
1048 if (!m_itemSize
.isEmpty()) {
1052 if (m_visibleRolesSizes
.isEmpty()) {
1053 m_visibleRolesSizes
= visibleRoleSizes();
1054 foreach (KItemListWidget
* widget
, visibleItemListWidgets()) {
1055 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
1059 if (m_layouter
->itemSize().isEmpty()) {
1060 qreal requiredWidth
= 0;
1061 qreal requiredHeight
= 0;
1063 QHashIterator
<QByteArray
, QSizeF
> it(m_visibleRolesSizes
);
1064 while (it
.hasNext()) {
1066 const QSizeF
& visibleRoleSize
= it
.value();
1067 requiredWidth
+= visibleRoleSize
.width();
1068 requiredHeight
+= visibleRoleSize
.height();
1071 QSizeF dynamicItemSize
= m_itemSize
;
1072 if (dynamicItemSize
.width() <= 0) {
1073 dynamicItemSize
.setWidth(qMax(requiredWidth
, size().width()));
1075 if (dynamicItemSize
.height() <= 0) {
1076 dynamicItemSize
.setHeight(qMax(requiredHeight
, size().height()));
1079 m_layouter
->setItemSize(dynamicItemSize
);
1083 void KItemListView::updateWidgetProperties(KItemListWidget
* widget
, int index
)
1085 widget
->setVisibleRoles(m_visibleRoles
);
1086 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
1087 widget
->setStyleOption(m_styleOption
);
1089 const KItemListSelectionManager
* selectionManager
= m_controller
->selectionManager();
1090 widget
->setCurrent(index
== selectionManager
->currentItem());
1092 if (selectionManager
->hasSelection()) {
1093 const QSet
<int> selectedItems
= selectionManager
->selectedItems();
1094 widget
->setSelected(selectedItems
.contains(index
));
1096 widget
->setSelected(false);
1099 widget
->setHovered(false);
1101 widget
->setIndex(index
);
1102 widget
->setData(m_model
->data(index
));
1105 KItemListCreatorBase::~KItemListCreatorBase()
1107 qDeleteAll(m_recycleableWidgets
);
1108 qDeleteAll(m_createdWidgets
);
1111 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget
* widget
)
1113 m_createdWidgets
.insert(widget
);
1116 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget
* widget
)
1118 Q_ASSERT(m_createdWidgets
.contains(widget
));
1119 m_createdWidgets
.remove(widget
);
1121 if (m_recycleableWidgets
.count() < 100) {
1122 m_recycleableWidgets
.append(widget
);
1123 widget
->setVisible(false);
1129 QGraphicsWidget
* KItemListCreatorBase::popRecycleableWidget()
1131 if (m_recycleableWidgets
.isEmpty()) {
1135 QGraphicsWidget
* widget
= m_recycleableWidgets
.takeLast();
1136 m_createdWidgets
.insert(widget
);
1140 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1144 void KItemListWidgetCreatorBase::recycle(KItemListWidget
* widget
)
1146 widget
->setOpacity(1.0);
1147 pushRecycleableWidget(widget
);
1150 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1154 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader
* header
)
1156 header
->setOpacity(1.0);
1157 pushRecycleableWidget(header
);
1160 #include "kitemlistview.moc"