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 void KItemListView::beginTransaction()
321 ++m_activeTransactions
;
322 if (m_activeTransactions
== 1) {
323 onTransactionBegin();
327 void KItemListView::endTransaction()
329 --m_activeTransactions
;
330 if (m_activeTransactions
< 0) {
331 m_activeTransactions
= 0;
332 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
335 if (m_activeTransactions
== 0) {
341 bool KItemListView::isTransactionActive() const
343 return m_activeTransactions
> 0;
346 void KItemListView::initializeItemListWidget(KItemListWidget
* item
)
351 void KItemListView::onControllerChanged(KItemListController
* current
, KItemListController
* previous
)
357 void KItemListView::onModelChanged(KItemModelBase
* current
, KItemModelBase
* previous
)
363 void KItemListView::onScrollOrientationChanged(Qt::Orientation current
, Qt::Orientation previous
)
369 void KItemListView::onItemSizeChanged(const QSizeF
& current
, const QSizeF
& previous
)
375 void KItemListView::onOffsetChanged(qreal current
, qreal previous
)
381 void KItemListView::onVisibleRolesChanged(const QHash
<QByteArray
, int>& current
, const QHash
<QByteArray
, int>& previous
)
387 void KItemListView::onStyleOptionChanged(const KItemListStyleOption
& current
, const KItemListStyleOption
& previous
)
393 void KItemListView::onTransactionBegin()
397 void KItemListView::onTransactionEnd()
401 bool KItemListView::event(QEvent
* event
)
403 // Forward all events to the controller and handle them there
404 if (m_controller
&& m_controller
->processEvent(event
, transform())) {
408 return QGraphicsWidget::event(event
);
411 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent
* event
)
416 QList
<KItemListWidget
*> KItemListView::visibleItemListWidgets() const
418 return m_visibleItems
.values();
421 void KItemListView::slotItemsInserted(const KItemRangeList
& itemRanges
)
423 markVisibleRolesSizesAsDirty();
425 const bool hasMultipleRanges
= (itemRanges
.count() > 1);
426 if (hasMultipleRanges
) {
430 int previouslyInsertedCount
= 0;
431 foreach (const KItemRange
& range
, itemRanges
) {
432 // range.index is related to the model before anything has been inserted.
433 // As in each loop the current item-range gets inserted the index must
434 // be increased by the already previoulsy inserted items.
435 const int index
= range
.index
+ previouslyInsertedCount
;
436 const int count
= range
.count
;
437 if (index
< 0 || count
<= 0) {
438 kWarning() << "Invalid item range (index:" << index
<< ", count:" << count
<< ")";
441 previouslyInsertedCount
+= count
;
443 m_sizeHintResolver
->itemsInserted(index
, count
);
445 // Determine which visible items must be moved
446 QList
<int> itemsToMove
;
447 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
448 while (it
.hasNext()) {
450 const int visibleItemIndex
= it
.key();
451 if (visibleItemIndex
>= index
) {
452 itemsToMove
.append(visibleItemIndex
);
456 // Update the indexes of all KItemListWidget instances that are located
457 // after the inserted items. It is important to adjust the indexes in the order
458 // from the highest index to the lowest index to prevent overlaps when setting the new index.
460 for (int i
= itemsToMove
.count() - 1; i
>= 0; --i
) {
461 KItemListWidget
* widget
= m_visibleItems
.value(itemsToMove
[i
]);
463 setWidgetIndex(widget
, widget
->index() + count
);
466 m_layouter
->markAsDirty();
467 if (m_model
->count() == count
&& maximumOffset() > size().height()) {
468 kDebug() << "Scrollbar required, skipping layout";
469 const int scrollBarExtent
= style()->pixelMetric(QStyle::PM_ScrollBarExtent
);
470 QSizeF layouterSize
= m_layouter
->size();
471 if (scrollOrientation() == Qt::Vertical
) {
472 layouterSize
.rwidth() -= scrollBarExtent
;
474 layouterSize
.rheight() -= scrollBarExtent
;
476 m_layouter
->setSize(layouterSize
);
479 if (!hasMultipleRanges
) {
480 doLayout(Animation
, index
, count
);
486 m_controller
->selectionManager()->itemsInserted(itemRanges
);
489 if (hasMultipleRanges
) {
494 void KItemListView::slotItemsRemoved(const KItemRangeList
& itemRanges
)
496 markVisibleRolesSizesAsDirty();
498 const bool hasMultipleRanges
= (itemRanges
.count() > 1);
499 if (hasMultipleRanges
) {
503 for (int i
= itemRanges
.count() - 1; i
>= 0; --i
) {
504 const KItemRange
& range
= itemRanges
.at(i
);
505 const int index
= range
.index
;
506 const int count
= range
.count
;
507 if (index
< 0 || count
<= 0) {
508 kWarning() << "Invalid item range (index:" << index
<< ", count:" << count
<< ")";
512 m_sizeHintResolver
->itemsRemoved(index
, count
);
514 const int firstRemovedIndex
= index
;
515 const int lastRemovedIndex
= index
+ count
- 1;
516 const int lastIndex
= m_model
->count() + count
- 1;
518 // Remove all KItemListWidget instances that got deleted
519 for (int i
= firstRemovedIndex
; i
<= lastRemovedIndex
; ++i
) {
520 KItemListWidget
* widget
= m_visibleItems
.value(i
);
525 m_animation
->stop(widget
);
526 // Stopping the animation might lead to recycling the widget if
527 // it is invisible (see slotAnimationFinished()).
528 // Check again whether it is still visible:
529 if (!m_visibleItems
.contains(i
)) {
533 if (m_model
->count() == 0) {
534 // For performance reasons no animation is done when all items have
536 recycleWidget(widget
);
538 // Animate the removing of the items. Special case: When removing an item there
539 // is no valid model index available anymore. For the
540 // remove-animation the item gets removed from m_visibleItems but the widget
541 // will stay alive until the animation has been finished and will
542 // be recycled (deleted) in KItemListView::slotAnimationFinished().
543 m_visibleItems
.remove(i
);
544 widget
->setIndex(-1);
545 m_animation
->start(widget
, KItemListViewAnimation::DeleteAnimation
);
549 // Update the indexes of all KItemListWidget instances that are located
550 // after the deleted items
551 for (int i
= lastRemovedIndex
+ 1; i
<= lastIndex
; ++i
) {
552 KItemListWidget
* widget
= m_visibleItems
.value(i
);
554 const int newIndex
= i
- count
;
555 setWidgetIndex(widget
, newIndex
);
559 m_layouter
->markAsDirty();
560 if (!hasMultipleRanges
) {
561 doLayout(Animation
, index
, -count
);
567 m_controller
->selectionManager()->itemsRemoved(itemRanges
);
570 if (hasMultipleRanges
) {
575 void KItemListView::slotItemsChanged(const KItemRangeList
& itemRanges
,
576 const QSet
<QByteArray
>& roles
)
578 foreach (const KItemRange
& itemRange
, itemRanges
) {
579 const int index
= itemRange
.index
;
580 const int count
= itemRange
.count
;
582 m_sizeHintResolver
->itemsChanged(index
, count
, roles
);
584 const int lastIndex
= index
+ count
- 1;
585 for (int i
= index
; i
<= lastIndex
; ++i
) {
586 KItemListWidget
* widget
= m_visibleItems
.value(i
);
588 widget
->setData(m_model
->data(i
), roles
);
594 void KItemListView::slotCurrentChanged(int current
, int previous
)
598 KItemListWidget
* previousWidget
= m_visibleItems
.value(previous
, 0);
599 if (previousWidget
) {
600 Q_ASSERT(previousWidget
->isCurrent());
601 previousWidget
->setCurrent(false);
604 KItemListWidget
* currentWidget
= m_visibleItems
.value(current
, 0);
606 Q_ASSERT(!currentWidget
->isCurrent());
607 currentWidget
->setCurrent(true);
611 void KItemListView::slotSelectionChanged(const QSet
<int>& current
, const QSet
<int>& previous
)
615 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
616 while (it
.hasNext()) {
618 const int index
= it
.key();
619 KItemListWidget
* widget
= it
.value();
620 widget
->setSelected(current
.contains(index
));
624 void KItemListView::slotAnimationFinished(QGraphicsWidget
* widget
,
625 KItemListViewAnimation::AnimationType type
)
627 KItemListWidget
* itemListWidget
= qobject_cast
<KItemListWidget
*>(widget
);
628 Q_ASSERT(itemListWidget
);
631 case KItemListViewAnimation::DeleteAnimation
: {
632 // As we recycle the widget in this case it is important to assure that no
633 // other animation has been started. This is a convention in KItemListView and
634 // not a requirement defined by KItemListViewAnimation.
635 Q_ASSERT(!m_animation
->isStarted(itemListWidget
));
637 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
638 // by m_visibleWidgets and must be deleted manually after the animation has
640 KItemListGroupHeader
* header
= m_visibleGroups
.value(itemListWidget
);
642 m_groupHeaderCreator
->recycle(header
);
643 m_visibleGroups
.remove(itemListWidget
);
645 m_widgetCreator
->recycle(itemListWidget
);
649 case KItemListViewAnimation::CreateAnimation
:
650 case KItemListViewAnimation::MovingAnimation
:
651 case KItemListViewAnimation::ResizeAnimation
: {
652 const int index
= itemListWidget
->index();
653 const bool invisible
= (index
< m_layouter
->firstVisibleIndex()) ||
654 (index
> m_layouter
->lastVisibleIndex());
655 if (invisible
&& !m_animation
->isStarted(itemListWidget
)) {
656 recycleWidget(itemListWidget
);
665 void KItemListView::slotLayoutTimerFinished()
667 m_layouter
->setSize(geometry().size());
668 doLayout(Animation
, 0, 0);
671 void KItemListView::setController(KItemListController
* controller
)
673 if (m_controller
!= controller
) {
674 KItemListController
* previous
= m_controller
;
676 KItemListSelectionManager
* selectionManager
= previous
->selectionManager();
677 disconnect(selectionManager
, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
678 disconnect(selectionManager
, SIGNAL(selectionChanged(QSet
<int>,QSet
<int>)), this, SLOT(slotSelectionChanged(QSet
<int>,QSet
<int>)));
681 m_controller
= controller
;
684 KItemListSelectionManager
* selectionManager
= controller
->selectionManager();
685 connect(selectionManager
, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
686 connect(selectionManager
, SIGNAL(selectionChanged(QSet
<int>,QSet
<int>)), this, SLOT(slotSelectionChanged(QSet
<int>,QSet
<int>)));
689 onControllerChanged(controller
, previous
);
693 void KItemListView::setModel(KItemModelBase
* model
)
695 if (m_model
== model
) {
699 KItemModelBase
* previous
= m_model
;
702 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
703 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
704 disconnect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
705 this, SLOT(slotItemsInserted(KItemRangeList
)));
706 disconnect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
707 this, SLOT(slotItemsRemoved(KItemRangeList
)));
711 m_layouter
->setModel(model
);
712 m_grouped
= !model
->groupRole().isEmpty();
715 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
716 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
717 connect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
718 this, SLOT(slotItemsInserted(KItemRangeList
)));
719 connect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
720 this, SLOT(slotItemsRemoved(KItemRangeList
)));
723 onModelChanged(model
, previous
);
726 void KItemListView::updateLayout()
728 doLayout(Animation
, 0, 0);
732 void KItemListView::doLayout(LayoutAnimationHint hint
, int changedIndex
, int changedCount
)
734 if (m_layoutTimer
->isActive()) {
735 kDebug() << "Stopping layout timer, synchronous layout requested";
736 m_layoutTimer
->stop();
739 if (m_model
->count() < 0 || m_activeTransactions
> 0) {
743 applyDynamicItemSize();
745 const int firstVisibleIndex
= m_layouter
->firstVisibleIndex();
746 const int lastVisibleIndex
= m_layouter
->lastVisibleIndex();
747 if (firstVisibleIndex
< 0) {
752 // Do a sanity check of the offset-property: When properties of the itemlist-view have been changed
753 // it might be possible that the maximum offset got changed too. Assure that the full visible range
754 // is still shown if the maximum offset got decreased.
755 const qreal visibleOffsetRange
= (scrollOrientation() == Qt::Horizontal
) ? size().width() : size().height();
756 const qreal maxOffsetToShowFullRange
= maximumOffset() - visibleOffsetRange
;
757 if (offset() > maxOffsetToShowFullRange
) {
758 m_layouter
->setOffset(qMax(qreal(0), maxOffsetToShowFullRange
));
761 // Determine all items that are completely invisible and might be
762 // reused for items that just got (at least partly) visible.
763 // Items that do e.g. an animated moving of their position are not
764 // marked as invisible: This assures that a scrolling inside the view
765 // can be done without breaking an animation.
766 QList
<int> reusableItems
;
767 QHashIterator
<int, KItemListWidget
*> it(m_visibleItems
);
768 while (it
.hasNext()) {
770 KItemListWidget
* widget
= it
.value();
771 const int index
= widget
->index();
772 const bool invisible
= (index
< firstVisibleIndex
) || (index
> lastVisibleIndex
);
773 if (invisible
&& !m_animation
->isStarted(widget
)) {
774 widget
->setVisible(false);
775 reusableItems
.append(index
);
779 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
780 // instances from invisible items are reused. If no reusable items are
781 // found then new KItemListWidget instances get created.
782 const bool animate
= (hint
== Animation
);
783 for (int i
= firstVisibleIndex
; i
<= lastVisibleIndex
; ++i
) {
784 bool applyNewPos
= true;
785 bool wasHidden
= false;
787 const QRectF itemBounds
= m_layouter
->itemBoundingRect(i
);
788 const QPointF newPos
= itemBounds
.topLeft();
789 KItemListWidget
* widget
= m_visibleItems
.value(i
);
792 if (!reusableItems
.isEmpty()) {
793 // Reuse a KItemListWidget instance from an invisible item
794 const int oldIndex
= reusableItems
.takeLast();
795 widget
= m_visibleItems
.value(oldIndex
);
796 setWidgetIndex(widget
, i
);
798 // No reusable KItemListWidget instance is available, create a new one
799 widget
= createWidget(i
);
801 widget
->resize(itemBounds
.size());
803 if (animate
&& changedCount
< 0) {
804 // Items have been deleted, move the created item to the
805 // imaginary old position.
806 const QRectF itemBoundingRect
= m_layouter
->itemBoundingRect(i
- changedCount
);
807 if (itemBoundingRect
.isEmpty()) {
808 const QPointF invisibleOldPos
= (scrollOrientation() == Qt::Vertical
)
809 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
810 widget
->setPos(invisibleOldPos
);
812 widget
->setPos(itemBoundingRect
.topLeft());
816 } else if (m_animation
->isStarted(widget
, KItemListViewAnimation::MovingAnimation
)) {
821 const bool itemsRemoved
= (changedCount
< 0);
822 const bool itemsInserted
= (changedCount
> 0);
824 if (itemsRemoved
&& (i
>= changedIndex
+ changedCount
+ 1)) {
825 // The item is located after the removed items. Animate the moving of the position.
826 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
828 } else if (itemsInserted
&& i
>= changedIndex
) {
829 // The item is located after the first inserted item
830 if (i
<= changedIndex
+ changedCount
- 1) {
831 // The item is an inserted item. Animate the appearing of the item.
832 // For performance reasons no animation is done when changedCount is equal
833 // to all available items.
834 if (changedCount
< m_model
->count()) {
835 m_animation
->start(widget
, KItemListViewAnimation::CreateAnimation
);
837 } else if (!m_animation
->isStarted(widget
, KItemListViewAnimation::CreateAnimation
)) {
838 // The item was already there before, so animate the moving of the position.
839 // No moving animation is done if the item is animated by a create animation: This
840 // prevents a "move animation mess" when inserting several ranges in parallel.
841 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
844 } else if (!itemsRemoved
&& !itemsInserted
&& !wasHidden
) {
845 // The size of the view might have been changed. Animate the moving of the position.
846 m_animation
->start(widget
, KItemListViewAnimation::MovingAnimation
, newPos
);
852 widget
->setPos(newPos
);
855 Q_ASSERT(widget
->index() == i
);
856 widget
->setVisible(true);
858 if (widget
->size() != itemBounds
.size()) {
859 m_animation
->start(widget
, KItemListViewAnimation::ResizeAnimation
, itemBounds
.size());
863 // Delete invisible KItemListWidget instances that have not been reused
864 foreach (int index
, reusableItems
) {
865 recycleWidget(m_visibleItems
.value(index
));
871 void KItemListView::emitOffsetChanges()
873 const int newOffset
= m_layouter
->offset();
874 if (m_oldOffset
!= newOffset
) {
875 emit
offsetChanged(newOffset
, m_oldOffset
);
876 m_oldOffset
= newOffset
;
879 const int newMaximumOffset
= m_layouter
->maximumOffset();
880 if (m_oldMaximumOffset
!= newMaximumOffset
) {
881 emit
maximumOffsetChanged(newMaximumOffset
, m_oldMaximumOffset
);
882 m_oldMaximumOffset
= newMaximumOffset
;
886 KItemListWidget
* KItemListView::createWidget(int index
)
888 KItemListWidget
* widget
= m_widgetCreator
->create(this);
889 updateWidgetProperties(widget
, index
);
890 m_visibleItems
.insert(index
, widget
);
893 if (m_layouter
->isFirstGroupItem(index
)) {
894 KItemListGroupHeader
* header
= m_groupHeaderCreator
->create(widget
);
895 header
->setPos(0, -50);
896 header
->resize(50, 50);
897 m_visibleGroups
.insert(widget
, header
);
901 initializeItemListWidget(widget
);
905 void KItemListView::recycleWidget(KItemListWidget
* widget
)
908 KItemListGroupHeader
* header
= m_visibleGroups
.value(widget
);
910 m_groupHeaderCreator
->recycle(header
);
911 m_visibleGroups
.remove(widget
);
915 m_visibleItems
.remove(widget
->index());
916 m_widgetCreator
->recycle(widget
);
919 void KItemListView::setWidgetIndex(KItemListWidget
* widget
, int index
)
922 bool createHeader
= m_layouter
->isFirstGroupItem(index
);
923 KItemListGroupHeader
* header
= m_visibleGroups
.value(widget
);
926 createHeader
= false;
928 m_groupHeaderCreator
->recycle(header
);
929 m_visibleGroups
.remove(widget
);
934 KItemListGroupHeader
* header
= m_groupHeaderCreator
->create(widget
);
935 header
->setPos(0, -50);
936 header
->resize(50, 50);
937 m_visibleGroups
.insert(widget
, header
);
941 const int oldIndex
= widget
->index();
942 m_visibleItems
.remove(oldIndex
);
943 updateWidgetProperties(widget
, index
);
944 m_visibleItems
.insert(index
, widget
);
946 initializeItemListWidget(widget
);
949 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF
& size
, SizeType sizeType
)
951 // Calculate the first visible index and last visible index for the current size
952 const int currentFirst
= m_layouter
->firstVisibleIndex();
953 const int currentLast
= m_layouter
->lastVisibleIndex();
955 const QSizeF currentSize
= (sizeType
== LayouterSize
) ? m_layouter
->size() : m_layouter
->itemSize();
957 // Calculate the first visible index and last visible index for the new size
958 setLayouterSize(size
, sizeType
);
959 const int newFirst
= m_layouter
->firstVisibleIndex();
960 const int newLast
= m_layouter
->lastVisibleIndex();
962 if ((currentFirst
!= newFirst
) || (currentLast
!= newLast
)) {
963 // At least one index has been changed. Assure that widgets for all possible
964 // visible items get created so that a move-animation can be started later.
965 const int maxVisibleItems
= m_layouter
->maximumVisibleItems();
966 int minFirst
= qMin(newFirst
, currentFirst
);
967 const int maxLast
= qMax(newLast
, currentLast
);
969 if (maxLast
- minFirst
+ 1 < maxVisibleItems
) {
970 // Increasing the size might result in a smaller KItemListView::offset().
971 // Decrease the first visible index in a way that at least the maximum
972 // visible items are shown.
973 minFirst
= qMax(0, maxLast
- maxVisibleItems
+ 1);
976 if (maxLast
- minFirst
> maxVisibleItems
+ maxVisibleItems
/ 2) {
977 // The creating of widgets is quite expensive. Assure that never more
978 // than 50 % of the maximum visible items get created for the animations.
982 setLayouterSize(currentSize
, sizeType
);
983 for (int i
= minFirst
; i
<= maxLast
; ++i
) {
984 if (!m_visibleItems
.contains(i
)) {
985 KItemListWidget
* widget
= createWidget(i
);
986 const QPointF pos
= m_layouter
->itemBoundingRect(i
).topLeft();
990 setLayouterSize(size
, sizeType
);
994 void KItemListView::setLayouterSize(const QSizeF
& size
, SizeType sizeType
)
997 case LayouterSize
: m_layouter
->setSize(size
); break;
998 case ItemSize
: m_layouter
->setItemSize(size
); break;
1003 bool KItemListView::markVisibleRolesSizesAsDirty()
1005 const bool dirty
= m_itemSize
.isEmpty();
1007 m_visibleRolesSizes
.clear();
1008 m_layouter
->setItemSize(QSizeF());
1013 void KItemListView::applyDynamicItemSize()
1015 if (!m_itemSize
.isEmpty()) {
1019 if (m_visibleRolesSizes
.isEmpty()) {
1020 m_visibleRolesSizes
= visibleRoleSizes();
1021 foreach (KItemListWidget
* widget
, visibleItemListWidgets()) {
1022 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
1026 if (m_layouter
->itemSize().isEmpty()) {
1027 qreal requiredWidth
= 0;
1028 qreal requiredHeight
= 0;
1030 QHashIterator
<QByteArray
, QSizeF
> it(m_visibleRolesSizes
);
1031 while (it
.hasNext()) {
1033 const QSizeF
& visibleRoleSize
= it
.value();
1034 requiredWidth
+= visibleRoleSize
.width();
1035 requiredHeight
+= visibleRoleSize
.height();
1038 QSizeF dynamicItemSize
= m_itemSize
;
1039 if (dynamicItemSize
.width() <= 0) {
1040 dynamicItemSize
.setWidth(qMax(requiredWidth
, size().width()));
1042 if (dynamicItemSize
.height() <= 0) {
1043 dynamicItemSize
.setHeight(qMax(requiredHeight
, size().height()));
1046 m_layouter
->setItemSize(dynamicItemSize
);
1050 void KItemListView::updateWidgetProperties(KItemListWidget
* widget
, int index
)
1052 widget
->setVisibleRoles(m_visibleRoles
);
1053 widget
->setVisibleRolesSizes(m_visibleRolesSizes
);
1054 widget
->setStyleOption(m_styleOption
);
1056 const KItemListSelectionManager
* selectionManager
= m_controller
->selectionManager();
1057 widget
->setCurrent(index
== selectionManager
->currentItem());
1059 if (selectionManager
->hasSelection()) {
1060 const QSet
<int> selectedItems
= selectionManager
->selectedItems();
1061 widget
->setSelected(selectedItems
.contains(index
));
1063 widget
->setSelected(false);
1066 widget
->setHovered(false);
1068 widget
->setIndex(index
);
1069 widget
->setData(m_model
->data(index
));
1072 KItemListCreatorBase::~KItemListCreatorBase()
1074 qDeleteAll(m_recycleableWidgets
);
1075 qDeleteAll(m_createdWidgets
);
1078 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget
* widget
)
1080 m_createdWidgets
.insert(widget
);
1083 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget
* widget
)
1085 Q_ASSERT(m_createdWidgets
.contains(widget
));
1086 m_createdWidgets
.remove(widget
);
1088 if (m_recycleableWidgets
.count() < 100) {
1089 m_recycleableWidgets
.append(widget
);
1090 widget
->setVisible(false);
1096 QGraphicsWidget
* KItemListCreatorBase::popRecycleableWidget()
1098 if (m_recycleableWidgets
.isEmpty()) {
1102 QGraphicsWidget
* widget
= m_recycleableWidgets
.takeLast();
1103 m_createdWidgets
.insert(widget
);
1107 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1111 void KItemListWidgetCreatorBase::recycle(KItemListWidget
* widget
)
1113 widget
->setOpacity(1.0);
1114 pushRecycleableWidget(widget
);
1117 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1121 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader
* header
)
1123 header
->setOpacity(1.0);
1124 pushRecycleableWidget(header
);
1127 #include "kitemlistview.moc"