]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
Move drawing of textbackground to KItemListWidget
[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 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
260 while (it.hasNext()) {
261 it.next();
262
263 const KItemListWidget* widget = it.value();
264 const QPointF mappedPos = widget->mapFromItem(this, pos);
265 if (widget->contains(mappedPos)) {
266 return it.key();
267 }
268 }
269
270 return -1;
271 }
272
273 bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
274 {
275 Q_UNUSED(index);
276 Q_UNUSED(pos);
277 return false;
278 }
279
280 bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
281 {
282 const KItemListWidget* widget = m_visibleItems.value(index);
283 if (widget) {
284 const QRectF expansionToggleRect = widget->expansionToggleRect();
285 if (!expansionToggleRect.isEmpty()) {
286 const QPointF mappedPos = widget->mapFromItem(this, pos);
287 return expansionToggleRect.contains(mappedPos);
288 }
289 }
290 return false;
291 }
292
293 int KItemListView::firstVisibleIndex() const
294 {
295 return m_layouter->firstVisibleIndex();
296 }
297
298 int KItemListView::lastVisibleIndex() const
299 {
300 return m_layouter->lastVisibleIndex();
301 }
302
303 QSizeF KItemListView::itemSizeHint(int index) const
304 {
305 Q_UNUSED(index);
306 return itemSize();
307 }
308
309 QHash<QByteArray, QSizeF> KItemListView::visibleRoleSizes() const
310 {
311 return QHash<QByteArray, QSizeF>();
312 }
313
314 QRectF KItemListView::itemBoundingRect(int index) const
315 {
316 return m_layouter->itemBoundingRect(index);
317 }
318
319 void KItemListView::beginTransaction()
320 {
321 ++m_activeTransactions;
322 if (m_activeTransactions == 1) {
323 onTransactionBegin();
324 }
325 }
326
327 void KItemListView::endTransaction()
328 {
329 --m_activeTransactions;
330 if (m_activeTransactions < 0) {
331 m_activeTransactions = 0;
332 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
333 }
334
335 if (m_activeTransactions == 0) {
336 onTransactionEnd();
337 updateLayout();
338 }
339 }
340
341 bool KItemListView::isTransactionActive() const
342 {
343 return m_activeTransactions > 0;
344 }
345
346 void KItemListView::initializeItemListWidget(KItemListWidget* item)
347 {
348 Q_UNUSED(item);
349 }
350
351 void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
352 {
353 Q_UNUSED(current);
354 Q_UNUSED(previous);
355 }
356
357 void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
358 {
359 Q_UNUSED(current);
360 Q_UNUSED(previous);
361 }
362
363 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
364 {
365 Q_UNUSED(current);
366 Q_UNUSED(previous);
367 }
368
369 void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
370 {
371 Q_UNUSED(current);
372 Q_UNUSED(previous);
373 }
374
375 void KItemListView::onOffsetChanged(qreal current, qreal previous)
376 {
377 Q_UNUSED(current);
378 Q_UNUSED(previous);
379 }
380
381 void KItemListView::onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous)
382 {
383 Q_UNUSED(current);
384 Q_UNUSED(previous);
385 }
386
387 void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
388 {
389 Q_UNUSED(current);
390 Q_UNUSED(previous);
391 }
392
393 void KItemListView::onTransactionBegin()
394 {
395 }
396
397 void KItemListView::onTransactionEnd()
398 {
399 }
400
401 bool KItemListView::event(QEvent* event)
402 {
403 // Forward all events to the controller and handle them there
404 if (m_controller && m_controller->processEvent(event, transform())) {
405 event->accept();
406 return true;
407 }
408 return QGraphicsWidget::event(event);
409 }
410
411 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
412 {
413 event->accept();
414 }
415
416 QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
417 {
418 return m_visibleItems.values();
419 }
420
421 void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
422 {
423 markVisibleRolesSizesAsDirty();
424
425 const bool hasMultipleRanges = (itemRanges.count() > 1);
426 if (hasMultipleRanges) {
427 beginTransaction();
428 }
429
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 << ")";
439 continue;
440 }
441 previouslyInsertedCount += count;
442
443 m_sizeHintResolver->itemsInserted(index, count);
444
445 // Determine which visible items must be moved
446 QList<int> itemsToMove;
447 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
448 while (it.hasNext()) {
449 it.next();
450 const int visibleItemIndex = it.key();
451 if (visibleItemIndex >= index) {
452 itemsToMove.append(visibleItemIndex);
453 }
454 }
455
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.
459 qSort(itemsToMove);
460 for (int i = itemsToMove.count() - 1; i >= 0; --i) {
461 KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
462 Q_ASSERT(widget);
463 setWidgetIndex(widget, widget->index() + count);
464 }
465
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;
473 } else {
474 layouterSize.rheight() -= scrollBarExtent;
475 }
476 m_layouter->setSize(layouterSize);
477 }
478
479 if (!hasMultipleRanges) {
480 doLayout(Animation, index, count);
481 update();
482 }
483 }
484
485 if (m_controller) {
486 m_controller->selectionManager()->itemsInserted(itemRanges);
487 }
488
489 if (hasMultipleRanges) {
490 endTransaction();
491 }
492 }
493
494 void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
495 {
496 markVisibleRolesSizesAsDirty();
497
498 const bool hasMultipleRanges = (itemRanges.count() > 1);
499 if (hasMultipleRanges) {
500 beginTransaction();
501 }
502
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 << ")";
509 continue;
510 }
511
512 m_sizeHintResolver->itemsRemoved(index, count);
513
514 const int firstRemovedIndex = index;
515 const int lastRemovedIndex = index + count - 1;
516 const int lastIndex = m_model->count() + count - 1;
517
518 // Remove all KItemListWidget instances that got deleted
519 for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
520 KItemListWidget* widget = m_visibleItems.value(i);
521 if (!widget) {
522 continue;
523 }
524
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)) {
530 continue;
531 }
532
533 if (m_model->count() == 0) {
534 // For performance reasons no animation is done when all items have
535 // been removed.
536 recycleWidget(widget);
537 } else {
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);
546 }
547 }
548
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);
553 if (widget) {
554 const int newIndex = i - count;
555 setWidgetIndex(widget, newIndex);
556 }
557 }
558
559 m_layouter->markAsDirty();
560 if (!hasMultipleRanges) {
561 doLayout(Animation, index, -count);
562 update();
563 }
564 }
565
566 if (m_controller) {
567 m_controller->selectionManager()->itemsRemoved(itemRanges);
568 }
569
570 if (hasMultipleRanges) {
571 endTransaction();
572 }
573 }
574
575 void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
576 const QSet<QByteArray>& roles)
577 {
578 foreach (const KItemRange& itemRange, itemRanges) {
579 const int index = itemRange.index;
580 const int count = itemRange.count;
581
582 m_sizeHintResolver->itemsChanged(index, count, roles);
583
584 const int lastIndex = index + count - 1;
585 for (int i = index; i <= lastIndex; ++i) {
586 KItemListWidget* widget = m_visibleItems.value(i);
587 if (widget) {
588 widget->setData(m_model->data(i), roles);
589 }
590 }
591 }
592 }
593
594 void KItemListView::slotCurrentChanged(int current, int previous)
595 {
596 Q_UNUSED(previous);
597
598 KItemListWidget* previousWidget = m_visibleItems.value(previous, 0);
599 if (previousWidget) {
600 Q_ASSERT(previousWidget->isCurrent());
601 previousWidget->setCurrent(false);
602 }
603
604 KItemListWidget* currentWidget = m_visibleItems.value(current, 0);
605 if (currentWidget) {
606 Q_ASSERT(!currentWidget->isCurrent());
607 currentWidget->setCurrent(true);
608 }
609 }
610
611 void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
612 {
613 Q_UNUSED(previous);
614
615 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
616 while (it.hasNext()) {
617 it.next();
618 const int index = it.key();
619 KItemListWidget* widget = it.value();
620 widget->setSelected(current.contains(index));
621 }
622 }
623
624 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
625 KItemListViewAnimation::AnimationType type)
626 {
627 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
628 Q_ASSERT(itemListWidget);
629
630 switch (type) {
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));
636
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
639 // been finished.
640 KItemListGroupHeader* header = m_visibleGroups.value(itemListWidget);
641 if (header) {
642 m_groupHeaderCreator->recycle(header);
643 m_visibleGroups.remove(itemListWidget);
644 }
645 m_widgetCreator->recycle(itemListWidget);
646 break;
647 }
648
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);
657 }
658 break;
659 }
660
661 default: break;
662 }
663 }
664
665 void KItemListView::slotLayoutTimerFinished()
666 {
667 m_layouter->setSize(geometry().size());
668 doLayout(Animation, 0, 0);
669 }
670
671 void KItemListView::setController(KItemListController* controller)
672 {
673 if (m_controller != controller) {
674 KItemListController* previous = m_controller;
675 if (previous) {
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>)));
679 }
680
681 m_controller = controller;
682
683 if (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>)));
687 }
688
689 onControllerChanged(controller, previous);
690 }
691 }
692
693 void KItemListView::setModel(KItemModelBase* model)
694 {
695 if (m_model == model) {
696 return;
697 }
698
699 KItemModelBase* previous = m_model;
700
701 if (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)));
708 }
709
710 m_model = model;
711 m_layouter->setModel(model);
712 m_grouped = !model->groupRole().isEmpty();
713
714 if (m_model) {
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)));
721 }
722
723 onModelChanged(model, previous);
724 }
725
726 void KItemListView::updateLayout()
727 {
728 doLayout(Animation, 0, 0);
729 update();
730 }
731
732 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
733 {
734 if (m_layoutTimer->isActive()) {
735 kDebug() << "Stopping layout timer, synchronous layout requested";
736 m_layoutTimer->stop();
737 }
738
739 if (m_model->count() < 0 || m_activeTransactions > 0) {
740 return;
741 }
742
743 applyDynamicItemSize();
744
745 const int firstVisibleIndex = m_layouter->firstVisibleIndex();
746 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
747 if (firstVisibleIndex < 0) {
748 emitOffsetChanges();
749 return;
750 }
751
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));
759 }
760
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()) {
769 it.next();
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);
776 }
777 }
778
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;
786
787 const QRectF itemBounds = m_layouter->itemBoundingRect(i);
788 const QPointF newPos = itemBounds.topLeft();
789 KItemListWidget* widget = m_visibleItems.value(i);
790 if (!widget) {
791 wasHidden = true;
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);
797 } else {
798 // No reusable KItemListWidget instance is available, create a new one
799 widget = createWidget(i);
800 }
801 widget->resize(itemBounds.size());
802
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);
811 } else {
812 widget->setPos(itemBoundingRect.topLeft());
813 }
814 applyNewPos = false;
815 }
816 } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
817 applyNewPos = false;
818 }
819
820 if (animate) {
821 const bool itemsRemoved = (changedCount < 0);
822 const bool itemsInserted = (changedCount > 0);
823
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);
827 applyNewPos = false;
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);
836 }
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);
842 applyNewPos = false;
843 }
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);
847 applyNewPos = false;
848 }
849 }
850
851 if (applyNewPos) {
852 widget->setPos(newPos);
853 }
854
855 Q_ASSERT(widget->index() == i);
856 widget->setVisible(true);
857
858 if (widget->size() != itemBounds.size()) {
859 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
860 }
861 }
862
863 // Delete invisible KItemListWidget instances that have not been reused
864 foreach (int index, reusableItems) {
865 recycleWidget(m_visibleItems.value(index));
866 }
867
868 emitOffsetChanges();
869 }
870
871 void KItemListView::emitOffsetChanges()
872 {
873 const int newOffset = m_layouter->offset();
874 if (m_oldOffset != newOffset) {
875 emit offsetChanged(newOffset, m_oldOffset);
876 m_oldOffset = newOffset;
877 }
878
879 const int newMaximumOffset = m_layouter->maximumOffset();
880 if (m_oldMaximumOffset != newMaximumOffset) {
881 emit maximumOffsetChanged(newMaximumOffset, m_oldMaximumOffset);
882 m_oldMaximumOffset = newMaximumOffset;
883 }
884 }
885
886 KItemListWidget* KItemListView::createWidget(int index)
887 {
888 KItemListWidget* widget = m_widgetCreator->create(this);
889 updateWidgetProperties(widget, index);
890 m_visibleItems.insert(index, widget);
891
892 if (m_grouped) {
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);
898 }
899 }
900
901 initializeItemListWidget(widget);
902 return widget;
903 }
904
905 void KItemListView::recycleWidget(KItemListWidget* widget)
906 {
907 if (m_grouped) {
908 KItemListGroupHeader* header = m_visibleGroups.value(widget);
909 if (header) {
910 m_groupHeaderCreator->recycle(header);
911 m_visibleGroups.remove(widget);
912 }
913 }
914
915 m_visibleItems.remove(widget->index());
916 m_widgetCreator->recycle(widget);
917 }
918
919 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
920 {
921 if (m_grouped) {
922 bool createHeader = m_layouter->isFirstGroupItem(index);
923 KItemListGroupHeader* header = m_visibleGroups.value(widget);
924 if (header) {
925 if (createHeader) {
926 createHeader = false;
927 } else {
928 m_groupHeaderCreator->recycle(header);
929 m_visibleGroups.remove(widget);
930 }
931 }
932
933 if (createHeader) {
934 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
935 header->setPos(0, -50);
936 header->resize(50, 50);
937 m_visibleGroups.insert(widget, header);
938 }
939 }
940
941 const int oldIndex = widget->index();
942 m_visibleItems.remove(oldIndex);
943 updateWidgetProperties(widget, index);
944 m_visibleItems.insert(index, widget);
945
946 initializeItemListWidget(widget);
947 }
948
949 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType)
950 {
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();
954
955 const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize();
956
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();
961
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);
968
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);
974 }
975
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.
979 return;
980 }
981
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();
987 widget->setPos(pos);
988 }
989 }
990 setLayouterSize(size, sizeType);
991 }
992 }
993
994 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
995 {
996 switch (sizeType) {
997 case LayouterSize: m_layouter->setSize(size); break;
998 case ItemSize: m_layouter->setItemSize(size); break;
999 default: break;
1000 }
1001 }
1002
1003 bool KItemListView::markVisibleRolesSizesAsDirty()
1004 {
1005 const bool dirty = m_itemSize.isEmpty();
1006 if (dirty) {
1007 m_visibleRolesSizes.clear();
1008 m_layouter->setItemSize(QSizeF());
1009 }
1010 return dirty;
1011 }
1012
1013 void KItemListView::applyDynamicItemSize()
1014 {
1015 if (!m_itemSize.isEmpty()) {
1016 return;
1017 }
1018
1019 if (m_visibleRolesSizes.isEmpty()) {
1020 m_visibleRolesSizes = visibleRoleSizes();
1021 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
1022 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1023 }
1024 }
1025
1026 if (m_layouter->itemSize().isEmpty()) {
1027 qreal requiredWidth = 0;
1028 qreal requiredHeight = 0;
1029
1030 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
1031 while (it.hasNext()) {
1032 it.next();
1033 const QSizeF& visibleRoleSize = it.value();
1034 requiredWidth += visibleRoleSize.width();
1035 requiredHeight += visibleRoleSize.height();
1036 }
1037
1038 QSizeF dynamicItemSize = m_itemSize;
1039 if (dynamicItemSize.width() <= 0) {
1040 dynamicItemSize.setWidth(qMax(requiredWidth, size().width()));
1041 }
1042 if (dynamicItemSize.height() <= 0) {
1043 dynamicItemSize.setHeight(qMax(requiredHeight, size().height()));
1044 }
1045
1046 m_layouter->setItemSize(dynamicItemSize);
1047 }
1048 }
1049
1050 void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
1051 {
1052 widget->setVisibleRoles(m_visibleRoles);
1053 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1054 widget->setStyleOption(m_styleOption);
1055
1056 const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
1057 widget->setCurrent(index == selectionManager->currentItem());
1058
1059 if (selectionManager->hasSelection()) {
1060 const QSet<int> selectedItems = selectionManager->selectedItems();
1061 widget->setSelected(selectedItems.contains(index));
1062 } else {
1063 widget->setSelected(false);
1064 }
1065
1066 widget->setHovered(false);
1067
1068 widget->setIndex(index);
1069 widget->setData(m_model->data(index));
1070 }
1071
1072 KItemListCreatorBase::~KItemListCreatorBase()
1073 {
1074 qDeleteAll(m_recycleableWidgets);
1075 qDeleteAll(m_createdWidgets);
1076 }
1077
1078 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
1079 {
1080 m_createdWidgets.insert(widget);
1081 }
1082
1083 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
1084 {
1085 Q_ASSERT(m_createdWidgets.contains(widget));
1086 m_createdWidgets.remove(widget);
1087
1088 if (m_recycleableWidgets.count() < 100) {
1089 m_recycleableWidgets.append(widget);
1090 widget->setVisible(false);
1091 } else {
1092 delete widget;
1093 }
1094 }
1095
1096 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
1097 {
1098 if (m_recycleableWidgets.isEmpty()) {
1099 return 0;
1100 }
1101
1102 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
1103 m_createdWidgets.insert(widget);
1104 return widget;
1105 }
1106
1107 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1108 {
1109 }
1110
1111 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
1112 {
1113 widget->setOpacity(1.0);
1114 pushRecycleableWidget(widget);
1115 }
1116
1117 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1118 {
1119 }
1120
1121 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
1122 {
1123 header->setOpacity(1.0);
1124 pushRecycleableWidget(header);
1125 }
1126
1127 #include "kitemlistview.moc"