]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
Merged very early alpha-version of Dolphin 2.0
[dolphin.git] / src / kitemviews / kitemlistview.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * Based on the Itemviews NG project from Trolltech Labs: *
5 * http://qt.gitorious.org/qt-labs/itemviews-ng *
6 * *
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. *
11 * *
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. *
16 * *
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 ***************************************************************************/
22
23 #include "kitemlistview.h"
24
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"
32
33 #include <KDebug>
34
35 #include <QGraphicsSceneMouseEvent>
36 #include <QPropertyAnimation>
37 #include <QStyle>
38 #include <QTimer>
39
40 KItemListView::KItemListView(QGraphicsWidget* parent) :
41 QGraphicsWidget(parent),
42 m_grouped(false),
43 m_activeTransactions(0),
44 m_itemSize(),
45 m_controller(0),
46 m_model(0),
47 m_visibleRoles(),
48 m_visibleRolesSizes(),
49 m_widgetCreator(0),
50 m_groupHeaderCreator(0),
51 m_styleOption(),
52 m_visibleItems(),
53 m_visibleGroups(),
54 m_sizeHintResolver(0),
55 m_layouter(0),
56 m_animation(0),
57 m_layoutTimer(0),
58 m_oldOffset(0),
59 m_oldMaximumOffset(0)
60 {
61 setAcceptHoverEvents(true);
62
63 m_sizeHintResolver = new KItemListSizeHintResolver(this);
64
65 m_layouter = new KItemListViewLayouter(this);
66 m_layouter->setSizeHintResolver(m_sizeHintResolver);
67
68 m_animation = new KItemListViewAnimation(this);
69 connect(m_animation, SIGNAL(finished(QGraphicsWidget*, KItemListViewAnimation::AnimationType)),
70 this, SLOT(slotAnimationFinished(QGraphicsWidget*, KItemListViewAnimation::AnimationType)));
71
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()));
76 }
77
78 KItemListView::~KItemListView()
79 {
80 delete m_sizeHintResolver;
81 m_sizeHintResolver = 0;
82 }
83
84 void KItemListView::setScrollOrientation(Qt::Orientation orientation)
85 {
86 const Qt::Orientation previousOrientation = m_layouter->scrollOrientation();
87 if (orientation == previousOrientation) {
88 return;
89 }
90
91 m_layouter->setScrollOrientation(orientation);
92 m_animation->setScrollOrientation(orientation);
93 m_sizeHintResolver->clearCache();
94 updateLayout();
95 onScrollOrientationChanged(orientation, previousOrientation);
96 }
97
98 Qt::Orientation KItemListView::scrollOrientation() const
99 {
100 return m_layouter->scrollOrientation();
101 }
102
103 void KItemListView::setItemSize(const QSizeF& itemSize)
104 {
105 const QSizeF previousSize = m_itemSize;
106 if (itemSize == previousSize) {
107 return;
108 }
109
110 m_itemSize = itemSize;
111
112 if (!markVisibleRolesSizesAsDirty()) {
113 if (itemSize.width() < previousSize.width() || itemSize.height() < previousSize.height()) {
114 prepareLayoutForIncreasedItemCount(itemSize, ItemSize);
115 } else {
116 m_layouter->setItemSize(itemSize);
117 }
118 }
119
120 m_sizeHintResolver->clearCache();
121 updateLayout();
122 onItemSizeChanged(itemSize, previousSize);
123 }
124
125 QSizeF KItemListView::itemSize() const
126 {
127 return m_itemSize;
128 }
129
130 void KItemListView::setOffset(qreal offset)
131 {
132 if (offset < 0) {
133 offset = 0;
134 }
135
136 const qreal previousOffset = m_layouter->offset();
137 if (offset == previousOffset) {
138 return;
139 }
140
141 m_layouter->setOffset(offset);
142 m_animation->setOffset(offset);
143 if (!m_layoutTimer->isActive()) {
144 doLayout(NoAnimation, 0, 0);
145 update();
146 }
147 onOffsetChanged(offset, previousOffset);
148 }
149
150 qreal KItemListView::offset() const
151 {
152 return m_layouter->offset();
153 }
154
155 qreal KItemListView::maximumOffset() const
156 {
157 return m_layouter->maximumOffset();
158 }
159
160 void KItemListView::setVisibleRoles(const QHash<QByteArray, int>& roles)
161 {
162 const QHash<QByteArray, int> previousRoles = m_visibleRoles;
163 m_visibleRoles = roles;
164
165 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
166 while (it.hasNext()) {
167 it.next();
168 KItemListWidget* widget = it.value();
169 widget->setVisibleRoles(roles);
170 widget->setVisibleRolesSizes(m_visibleRolesSizes);
171 }
172
173 m_sizeHintResolver->clearCache();
174 m_layouter->markAsDirty();
175 onVisibleRolesChanged(roles, previousRoles);
176
177 markVisibleRolesSizesAsDirty();
178 updateLayout();
179 }
180
181 QHash<QByteArray, int> KItemListView::visibleRoles() const
182 {
183 return m_visibleRoles;
184 }
185
186 KItemListController* KItemListView::controller() const
187 {
188 return m_controller;
189 }
190
191 KItemModelBase* KItemListView::model() const
192 {
193 return m_model;
194 }
195
196 void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator)
197 {
198 m_widgetCreator = widgetCreator;
199 }
200
201 KItemListWidgetCreatorBase* KItemListView::widgetCreator() const
202 {
203 return m_widgetCreator;
204 }
205
206 void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator)
207 {
208 m_groupHeaderCreator = groupHeaderCreator;
209 }
210
211 KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const
212 {
213 return m_groupHeaderCreator;
214 }
215
216 void KItemListView::setStyleOption(const KItemListStyleOption& option)
217 {
218 const KItemListStyleOption previousOption = m_styleOption;
219 m_styleOption = option;
220
221 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
222 while (it.hasNext()) {
223 it.next();
224 it.value()->setStyleOption(option);
225 }
226
227 m_sizeHintResolver->clearCache();
228 updateLayout();
229 onStyleOptionChanged(option, previousOption);
230 }
231
232 const KItemListStyleOption& KItemListView::styleOption() const
233 {
234 return m_styleOption;
235 }
236
237 void KItemListView::setGeometry(const QRectF& rect)
238 {
239 QGraphicsWidget::setGeometry(rect);
240 if (!m_model) {
241 return;
242 }
243
244 if (m_itemSize.isEmpty()) {
245 m_layouter->setItemSize(QSizeF());
246 }
247
248 if (m_model->count() > 0) {
249 prepareLayoutForIncreasedItemCount(rect.size(), LayouterSize);
250 } else {
251 m_layouter->setSize(rect.size());
252 }
253
254 m_layoutTimer->start();
255 }
256
257 int KItemListView::itemAt(const QPointF& pos) const
258 {
259 if (!m_model) {
260 return -1;
261 }
262
263 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
264 while (it.hasNext()) {
265 it.next();
266
267 const KItemListWidget* widget = it.value();
268 const QPointF mappedPos = widget->mapFromItem(this, pos);
269 if (widget->contains(mappedPos)) {
270 return it.key();
271 }
272 }
273
274 return -1;
275 }
276
277 bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
278 {
279 Q_UNUSED(index);
280 Q_UNUSED(pos);
281 return false;
282 }
283
284 bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
285 {
286 const KItemListWidget* widget = m_visibleItems.value(index);
287 if (widget) {
288 const QRectF expansionToggleRect = widget->expansionToggleRect();
289 if (!expansionToggleRect.isEmpty()) {
290 const QPointF mappedPos = widget->mapFromItem(this, pos);
291 return expansionToggleRect.contains(mappedPos);
292 }
293 }
294 return false;
295 }
296
297 int KItemListView::firstVisibleIndex() const
298 {
299 return m_layouter->firstVisibleIndex();
300 }
301
302 int KItemListView::lastVisibleIndex() const
303 {
304 return m_layouter->lastVisibleIndex();
305 }
306
307 QSizeF KItemListView::itemSizeHint(int index) const
308 {
309 Q_UNUSED(index);
310 return itemSize();
311 }
312
313 QHash<QByteArray, QSizeF> KItemListView::visibleRoleSizes() const
314 {
315 return QHash<QByteArray, QSizeF>();
316 }
317
318 void KItemListView::beginTransaction()
319 {
320 ++m_activeTransactions;
321 if (m_activeTransactions == 1) {
322 onTransactionBegin();
323 }
324 }
325
326 void KItemListView::endTransaction()
327 {
328 --m_activeTransactions;
329 if (m_activeTransactions < 0) {
330 m_activeTransactions = 0;
331 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
332 }
333
334 if (m_activeTransactions == 0) {
335 onTransactionEnd();
336 updateLayout();
337 }
338 }
339
340 bool KItemListView::isTransactionActive() const
341 {
342 return m_activeTransactions > 0;
343 }
344
345 void KItemListView::initializeItemListWidget(KItemListWidget* item)
346 {
347 Q_UNUSED(item);
348 }
349
350 void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
351 {
352 Q_UNUSED(current);
353 Q_UNUSED(previous);
354 }
355
356 void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
357 {
358 Q_UNUSED(current);
359 Q_UNUSED(previous);
360 }
361
362 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
363 {
364 Q_UNUSED(current);
365 Q_UNUSED(previous);
366 }
367
368 void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
369 {
370 Q_UNUSED(current);
371 Q_UNUSED(previous);
372 }
373
374 void KItemListView::onOffsetChanged(qreal current, qreal previous)
375 {
376 Q_UNUSED(current);
377 Q_UNUSED(previous);
378 }
379
380 void KItemListView::onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous)
381 {
382 Q_UNUSED(current);
383 Q_UNUSED(previous);
384 }
385
386 void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
387 {
388 Q_UNUSED(current);
389 Q_UNUSED(previous);
390 }
391
392 void KItemListView::onTransactionBegin()
393 {
394 }
395
396 void KItemListView::onTransactionEnd()
397 {
398 }
399
400 bool KItemListView::event(QEvent* event)
401 {
402 // Forward all events to the controller and handle them there
403 if (m_controller && m_controller->processEvent(event, transform())) {
404 event->accept();
405 return true;
406 }
407 return QGraphicsWidget::event(event);
408 }
409
410 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
411 {
412 event->accept();
413 }
414
415 void KItemListView::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
416 {
417 if (!m_model) {
418 return;
419 }
420
421 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
422 while (it.hasNext()) {
423 it.next();
424
425 KItemListWidget* widget = it.value();
426 KItemListStyleOption styleOption = widget->styleOption();
427 const QPointF mappedPos = widget->mapFromItem(this, event->pos());
428
429 const bool hovered = widget->contains(mappedPos) &&
430 !widget->expansionToggleRect().contains(mappedPos) &&
431 !widget->selectionToggleRect().contains(mappedPos);
432 if (hovered) {
433 if (!(styleOption.state & QStyle::State_MouseOver)) {
434 styleOption.state |= QStyle::State_MouseOver;
435 widget->setStyleOption(styleOption);
436 }
437 } else if (styleOption.state & QStyle::State_MouseOver) {
438 styleOption.state &= ~QStyle::State_MouseOver;
439 widget->setStyleOption(styleOption);
440 }
441 }
442 }
443
444 void KItemListView::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
445 {
446 Q_UNUSED(event);
447
448 if (!m_model) {
449 return;
450 }
451
452 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
453 while (it.hasNext()) {
454 it.next();
455
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);
461 }
462 }
463 }
464
465 QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
466 {
467 return m_visibleItems.values();
468 }
469
470 void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
471 {
472 markVisibleRolesSizesAsDirty();
473
474 const bool hasMultipleRanges = (itemRanges.count() > 1);
475 if (hasMultipleRanges) {
476 beginTransaction();
477 }
478
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 << ")";
484 continue;
485 }
486
487 m_sizeHintResolver->itemsInserted(index, count);
488
489 // Determine which visible items must be moved
490 QList<int> itemsToMove;
491 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
492 while (it.hasNext()) {
493 it.next();
494 const int visibleItemIndex = it.key();
495 if (visibleItemIndex >= index) {
496 itemsToMove.append(visibleItemIndex);
497 }
498 }
499
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.
503 qSort(itemsToMove);
504 for (int i = itemsToMove.count() - 1; i >= 0; --i) {
505 KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
506 Q_ASSERT(widget);
507 setWidgetIndex(widget, widget->index() + count);
508 }
509
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;
517 } else {
518 layouterSize.rheight() -= scrollBarExtent;
519 }
520 m_layouter->setSize(layouterSize);
521 }
522
523 if (!hasMultipleRanges) {
524 doLayout(Animation, index, count);
525 update();
526 }
527
528 if (m_controller) {
529 KItemListSelectionManager* selectionManager = m_controller->selectionManager();
530 const int current = selectionManager->currentItem();
531 if (current < 0) {
532 selectionManager->setCurrentItem(0);
533 } else if (current >= index) {
534 selectionManager->setCurrentItem(current + count);
535 }
536 }
537 }
538
539 if (hasMultipleRanges) {
540 endTransaction();
541 }
542 }
543
544 void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
545 {
546 markVisibleRolesSizesAsDirty();
547
548 const bool hasMultipleRanges = (itemRanges.count() > 1);
549 if (hasMultipleRanges) {
550 beginTransaction();
551 }
552
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 << ")";
559 continue;
560 }
561
562 m_sizeHintResolver->itemsRemoved(index, count);
563
564 const int firstRemovedIndex = index;
565 const int lastRemovedIndex = index + count - 1;
566 const int lastIndex = m_model->count() + count - 1;
567
568 // Remove all KItemListWidget instances that got deleted
569 for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
570 KItemListWidget* widget = m_visibleItems.value(i);
571 if (!widget) {
572 continue;
573 }
574
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)) {
580 continue;
581 }
582
583 if (m_model->count() == 0) {
584 // For performance reasons no animation is done when all items have
585 // been removed.
586 recycleWidget(widget);
587 } else {
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);
596 }
597 }
598
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);
603 if (widget) {
604 const int newIndex = i - count;
605 setWidgetIndex(widget, newIndex);
606 }
607 }
608
609 m_layouter->markAsDirty();
610 if (!hasMultipleRanges) {
611 doLayout(Animation, index, -count);
612 update();
613 }
614
615 /*KItemListSelectionManager* selectionManager = m_controller->selectionManager();
616 const int current = selectionManager->currentItem();
617 if (count() <= 0) {
618 selectionManager->setCurrentItem(-1);
619 } else if (current >= index) {
620 selectionManager->setCurrentItem(current + count);
621 }*/
622 }
623
624 if (hasMultipleRanges) {
625 endTransaction();
626 }
627 }
628
629 void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
630 const QSet<QByteArray>& roles)
631 {
632 foreach (const KItemRange& itemRange, itemRanges) {
633 const int index = itemRange.index;
634 const int count = itemRange.count;
635
636 m_sizeHintResolver->itemsChanged(index, count, roles);
637
638 const int lastIndex = index + count - 1;
639 for (int i = index; i <= lastIndex; ++i) {
640 KItemListWidget* widget = m_visibleItems.value(i);
641 if (widget) {
642 widget->setData(m_model->data(i), roles);
643 }
644 }
645 }
646 }
647
648 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
649 KItemListViewAnimation::AnimationType type)
650 {
651 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
652 Q_ASSERT(itemListWidget);
653
654 switch (type) {
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));
660
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
663 // been finished.
664 KItemListGroupHeader* header = m_visibleGroups.value(itemListWidget);
665 if (header) {
666 m_groupHeaderCreator->recycle(header);
667 m_visibleGroups.remove(itemListWidget);
668 }
669 m_widgetCreator->recycle(itemListWidget);
670 break;
671 }
672
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);
681 }
682 break;
683 }
684
685 default: break;
686 }
687 }
688
689 void KItemListView::slotLayoutTimerFinished()
690 {
691 m_layouter->setSize(geometry().size());
692 doLayout(Animation, 0, 0);
693 }
694
695 void KItemListView::setController(KItemListController* controller)
696 {
697 if (m_controller != controller) {
698 KItemListController* previous = m_controller;
699 m_controller = controller;
700 onControllerChanged(controller, previous);
701 }
702 }
703
704 void KItemListView::setModel(KItemModelBase* model)
705 {
706 if (m_model == model) {
707 return;
708 }
709
710 KItemModelBase* previous = m_model;
711
712 if (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)));
719 }
720
721 m_model = model;
722 m_layouter->setModel(model);
723 m_grouped = !model->groupRole().isEmpty();
724
725 if (m_model) {
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)));
732 }
733
734 onModelChanged(model, previous);
735 }
736
737 void KItemListView::updateLayout()
738 {
739 doLayout(Animation, 0, 0);
740 update();
741 }
742
743 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
744 {
745 if (m_layoutTimer->isActive()) {
746 kDebug() << "Stopping layout timer, synchronous layout requested";
747 m_layoutTimer->stop();
748 }
749
750 if (m_model->count() < 0 || m_activeTransactions > 0) {
751 return;
752 }
753
754 applyDynamicItemSize();
755
756 const int firstVisibleIndex = m_layouter->firstVisibleIndex();
757 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
758 if (firstVisibleIndex < 0) {
759 emitOffsetChanges();
760 return;
761 }
762
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));
770 }
771
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()) {
780 it.next();
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);
787 }
788 }
789
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;
797
798 const QRectF itemBounds = m_layouter->itemBoundingRect(i);
799 const QPointF newPos = itemBounds.topLeft();
800 KItemListWidget* widget = m_visibleItems.value(i);
801 if (!widget) {
802 wasHidden = true;
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);
808 } else {
809 // No reusable KItemListWidget instance is available, create a new one
810 widget = createWidget(i);
811 }
812 widget->resize(itemBounds.size());
813
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);
822 } else {
823 widget->setPos(itemBoundingRect.topLeft());
824 }
825 applyNewPos = false;
826 }
827 } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
828 applyNewPos = false;
829 }
830
831 if (animate) {
832 const bool itemsRemoved = (changedCount < 0);
833 const bool itemsInserted = (changedCount > 0);
834
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);
838 applyNewPos = false;
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);
847 }
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);
853 applyNewPos = false;
854 }
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);
858 applyNewPos = false;
859 }
860 }
861
862 if (applyNewPos) {
863 widget->setPos(newPos);
864 }
865
866 Q_ASSERT(widget->index() == i);
867 widget->setVisible(true);
868
869 if (widget->size() != itemBounds.size()) {
870 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
871 }
872 }
873
874 // Delete invisible KItemListWidget instances that have not been reused
875 foreach (int index, reusableItems) {
876 recycleWidget(m_visibleItems.value(index));
877 }
878
879 emitOffsetChanges();
880 }
881
882 void KItemListView::emitOffsetChanges()
883 {
884 const int newOffset = m_layouter->offset();
885 if (m_oldOffset != newOffset) {
886 emit offsetChanged(newOffset, m_oldOffset);
887 m_oldOffset = newOffset;
888 }
889
890 const int newMaximumOffset = m_layouter->maximumOffset();
891 if (m_oldMaximumOffset != newMaximumOffset) {
892 emit maximumOffsetChanged(newMaximumOffset, m_oldMaximumOffset);
893 m_oldMaximumOffset = newMaximumOffset;
894 }
895 }
896
897 KItemListWidget* KItemListView::createWidget(int index)
898 {
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);
906
907 if (m_grouped) {
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);
913 }
914 }
915
916 initializeItemListWidget(widget);
917 return widget;
918 }
919
920 void KItemListView::recycleWidget(KItemListWidget* widget)
921 {
922 if (m_grouped) {
923 KItemListGroupHeader* header = m_visibleGroups.value(widget);
924 if (header) {
925 m_groupHeaderCreator->recycle(header);
926 m_visibleGroups.remove(widget);
927 }
928 }
929
930 m_visibleItems.remove(widget->index());
931 m_widgetCreator->recycle(widget);
932 }
933
934 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
935 {
936 if (m_grouped) {
937 bool createHeader = m_layouter->isFirstGroupItem(index);
938 KItemListGroupHeader* header = m_visibleGroups.value(widget);
939 if (header) {
940 if (createHeader) {
941 createHeader = false;
942 } else {
943 m_groupHeaderCreator->recycle(header);
944 m_visibleGroups.remove(widget);
945 }
946 }
947
948 if (createHeader) {
949 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
950 header->setPos(0, -50);
951 header->resize(50, 50);
952 m_visibleGroups.insert(widget, header);
953 }
954 }
955
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);
964
965 initializeItemListWidget(widget);
966 }
967
968 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType)
969 {
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();
973
974 const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize();
975
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();
980
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);
987
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);
993 }
994
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.
998 return;
999 }
1000
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);
1007 }
1008 }
1009 setLayouterSize(size, sizeType);
1010 }
1011 }
1012
1013 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
1014 {
1015 switch (sizeType) {
1016 case LayouterSize: m_layouter->setSize(size); break;
1017 case ItemSize: m_layouter->setItemSize(size); break;
1018 default: break;
1019 }
1020 }
1021
1022 bool KItemListView::markVisibleRolesSizesAsDirty()
1023 {
1024 const bool dirty = m_itemSize.isEmpty();
1025 if (dirty) {
1026 m_visibleRolesSizes.clear();
1027 m_layouter->setItemSize(QSizeF());
1028 }
1029 return dirty;
1030 }
1031
1032 void KItemListView::applyDynamicItemSize()
1033 {
1034 if (!m_itemSize.isEmpty()) {
1035 return;
1036 }
1037
1038 if (m_visibleRolesSizes.isEmpty()) {
1039 m_visibleRolesSizes = visibleRoleSizes();
1040 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
1041 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1042 }
1043 }
1044
1045 if (m_layouter->itemSize().isEmpty()) {
1046 qreal requiredWidth = 0;
1047 qreal requiredHeight = 0;
1048
1049 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
1050 while (it.hasNext()) {
1051 it.next();
1052 const QSizeF& visibleRoleSize = it.value();
1053 requiredWidth += visibleRoleSize.width();
1054 requiredHeight += visibleRoleSize.height();
1055 }
1056
1057 QSizeF dynamicItemSize = m_itemSize;
1058 if (dynamicItemSize.width() <= 0) {
1059 dynamicItemSize.setWidth(qMax(requiredWidth, size().width()));
1060 }
1061 if (dynamicItemSize.height() <= 0) {
1062 dynamicItemSize.setHeight(qMax(requiredHeight, size().height()));
1063 }
1064
1065 m_layouter->setItemSize(dynamicItemSize);
1066 }
1067 }
1068
1069 KItemListCreatorBase::~KItemListCreatorBase()
1070 {
1071 qDeleteAll(m_recycleableWidgets);
1072 qDeleteAll(m_createdWidgets);
1073 }
1074
1075 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
1076 {
1077 m_createdWidgets.insert(widget);
1078 }
1079
1080 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
1081 {
1082 Q_ASSERT(m_createdWidgets.contains(widget));
1083 m_createdWidgets.remove(widget);
1084
1085 if (m_recycleableWidgets.count() < 100) {
1086 m_recycleableWidgets.append(widget);
1087 widget->setVisible(false);
1088 } else {
1089 delete widget;
1090 }
1091 }
1092
1093 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
1094 {
1095 if (m_recycleableWidgets.isEmpty()) {
1096 return 0;
1097 }
1098
1099 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
1100 m_createdWidgets.insert(widget);
1101 return widget;
1102 }
1103
1104 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1105 {
1106 }
1107
1108 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
1109 {
1110 widget->setOpacity(1.0);
1111 pushRecycleableWidget(widget);
1112 }
1113
1114 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1115 {
1116 }
1117
1118 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
1119 {
1120 header->setOpacity(1.0);
1121 pushRecycleableWidget(header);
1122 }
1123
1124 #include "kitemlistview.moc"