]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
Remove incorrect asserts
[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 int KItemListView::itemsPerOffset() const
320 {
321 return m_layouter->itemsPerOffset();
322 }
323
324 void KItemListView::beginTransaction()
325 {
326 ++m_activeTransactions;
327 if (m_activeTransactions == 1) {
328 onTransactionBegin();
329 }
330 }
331
332 void KItemListView::endTransaction()
333 {
334 --m_activeTransactions;
335 if (m_activeTransactions < 0) {
336 m_activeTransactions = 0;
337 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
338 }
339
340 if (m_activeTransactions == 0) {
341 onTransactionEnd();
342 updateLayout();
343 }
344 }
345
346 bool KItemListView::isTransactionActive() const
347 {
348 return m_activeTransactions > 0;
349 }
350
351 void KItemListView::initializeItemListWidget(KItemListWidget* item)
352 {
353 Q_UNUSED(item);
354 }
355
356 void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
357 {
358 Q_UNUSED(current);
359 Q_UNUSED(previous);
360 }
361
362 void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
363 {
364 Q_UNUSED(current);
365 Q_UNUSED(previous);
366 }
367
368 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
369 {
370 Q_UNUSED(current);
371 Q_UNUSED(previous);
372 }
373
374 void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
375 {
376 Q_UNUSED(current);
377 Q_UNUSED(previous);
378 }
379
380 void KItemListView::onOffsetChanged(qreal current, qreal previous)
381 {
382 Q_UNUSED(current);
383 Q_UNUSED(previous);
384 }
385
386 void KItemListView::onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous)
387 {
388 Q_UNUSED(current);
389 Q_UNUSED(previous);
390 }
391
392 void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
393 {
394 Q_UNUSED(current);
395 Q_UNUSED(previous);
396 }
397
398 void KItemListView::onTransactionBegin()
399 {
400 }
401
402 void KItemListView::onTransactionEnd()
403 {
404 }
405
406 bool KItemListView::event(QEvent* event)
407 {
408 // Forward all events to the controller and handle them there
409 if (m_controller && m_controller->processEvent(event, transform())) {
410 event->accept();
411 return true;
412 }
413 return QGraphicsWidget::event(event);
414 }
415
416 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
417 {
418 event->accept();
419 }
420
421 QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
422 {
423 return m_visibleItems.values();
424 }
425
426 void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
427 {
428 markVisibleRolesSizesAsDirty();
429
430 const bool hasMultipleRanges = (itemRanges.count() > 1);
431 if (hasMultipleRanges) {
432 beginTransaction();
433 }
434
435 int previouslyInsertedCount = 0;
436 foreach (const KItemRange& range, itemRanges) {
437 // range.index is related to the model before anything has been inserted.
438 // As in each loop the current item-range gets inserted the index must
439 // be increased by the already previoulsy inserted items.
440 const int index = range.index + previouslyInsertedCount;
441 const int count = range.count;
442 if (index < 0 || count <= 0) {
443 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
444 continue;
445 }
446 previouslyInsertedCount += count;
447
448 m_sizeHintResolver->itemsInserted(index, count);
449
450 // Determine which visible items must be moved
451 QList<int> itemsToMove;
452 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
453 while (it.hasNext()) {
454 it.next();
455 const int visibleItemIndex = it.key();
456 if (visibleItemIndex >= index) {
457 itemsToMove.append(visibleItemIndex);
458 }
459 }
460
461 // Update the indexes of all KItemListWidget instances that are located
462 // after the inserted items. It is important to adjust the indexes in the order
463 // from the highest index to the lowest index to prevent overlaps when setting the new index.
464 qSort(itemsToMove);
465 for (int i = itemsToMove.count() - 1; i >= 0; --i) {
466 KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
467 Q_ASSERT(widget);
468 setWidgetIndex(widget, widget->index() + count);
469 }
470
471 m_layouter->markAsDirty();
472 if (m_model->count() == count && maximumOffset() > size().height()) {
473 kDebug() << "Scrollbar required, skipping layout";
474 const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
475 QSizeF layouterSize = m_layouter->size();
476 if (scrollOrientation() == Qt::Vertical) {
477 layouterSize.rwidth() -= scrollBarExtent;
478 } else {
479 layouterSize.rheight() -= scrollBarExtent;
480 }
481 m_layouter->setSize(layouterSize);
482 }
483
484 if (!hasMultipleRanges) {
485 doLayout(Animation, index, count);
486 update();
487 }
488 }
489
490 if (m_controller) {
491 m_controller->selectionManager()->itemsInserted(itemRanges);
492 }
493
494 if (hasMultipleRanges) {
495 endTransaction();
496 }
497 }
498
499 void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
500 {
501 markVisibleRolesSizesAsDirty();
502
503 const bool hasMultipleRanges = (itemRanges.count() > 1);
504 if (hasMultipleRanges) {
505 beginTransaction();
506 }
507
508 for (int i = itemRanges.count() - 1; i >= 0; --i) {
509 const KItemRange& range = itemRanges.at(i);
510 const int index = range.index;
511 const int count = range.count;
512 if (index < 0 || count <= 0) {
513 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
514 continue;
515 }
516
517 m_sizeHintResolver->itemsRemoved(index, count);
518
519 const int firstRemovedIndex = index;
520 const int lastRemovedIndex = index + count - 1;
521 const int lastIndex = m_model->count() + count - 1;
522
523 // Remove all KItemListWidget instances that got deleted
524 for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
525 KItemListWidget* widget = m_visibleItems.value(i);
526 if (!widget) {
527 continue;
528 }
529
530 m_animation->stop(widget);
531 // Stopping the animation might lead to recycling the widget if
532 // it is invisible (see slotAnimationFinished()).
533 // Check again whether it is still visible:
534 if (!m_visibleItems.contains(i)) {
535 continue;
536 }
537
538 if (m_model->count() == 0) {
539 // For performance reasons no animation is done when all items have
540 // been removed.
541 recycleWidget(widget);
542 } else {
543 // Animate the removing of the items. Special case: When removing an item there
544 // is no valid model index available anymore. For the
545 // remove-animation the item gets removed from m_visibleItems but the widget
546 // will stay alive until the animation has been finished and will
547 // be recycled (deleted) in KItemListView::slotAnimationFinished().
548 m_visibleItems.remove(i);
549 widget->setIndex(-1);
550 m_animation->start(widget, KItemListViewAnimation::DeleteAnimation);
551 }
552 }
553
554 // Update the indexes of all KItemListWidget instances that are located
555 // after the deleted items
556 for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) {
557 KItemListWidget* widget = m_visibleItems.value(i);
558 if (widget) {
559 const int newIndex = i - count;
560 setWidgetIndex(widget, newIndex);
561 }
562 }
563
564 m_layouter->markAsDirty();
565 if (!hasMultipleRanges) {
566 doLayout(Animation, index, -count);
567 update();
568 }
569 }
570
571 if (m_controller) {
572 m_controller->selectionManager()->itemsRemoved(itemRanges);
573 }
574
575 if (hasMultipleRanges) {
576 endTransaction();
577 }
578 }
579
580 void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
581 const QSet<QByteArray>& roles)
582 {
583 foreach (const KItemRange& itemRange, itemRanges) {
584 const int index = itemRange.index;
585 const int count = itemRange.count;
586
587 m_sizeHintResolver->itemsChanged(index, count, roles);
588
589 const int lastIndex = index + count - 1;
590 for (int i = index; i <= lastIndex; ++i) {
591 KItemListWidget* widget = m_visibleItems.value(i);
592 if (widget) {
593 widget->setData(m_model->data(i), roles);
594 }
595 }
596 }
597 }
598
599 void KItemListView::slotCurrentChanged(int current, int previous)
600 {
601 Q_UNUSED(previous);
602
603 KItemListWidget* previousWidget = m_visibleItems.value(previous, 0);
604 if (previousWidget) {
605 Q_ASSERT(previousWidget->isCurrent());
606 previousWidget->setCurrent(false);
607 }
608
609 KItemListWidget* currentWidget = m_visibleItems.value(current, 0);
610 if (currentWidget) {
611 Q_ASSERT(!currentWidget->isCurrent());
612 currentWidget->setCurrent(true);
613 }
614
615 const QRectF viewGeometry = geometry();
616 const QRectF currentBoundingRect = itemBoundingRect(current);
617
618 if (!viewGeometry.contains(currentBoundingRect)) {
619 // Make sure that the new current item is fully visible in the view.
620 qreal newOffset = offset();
621 if (currentBoundingRect.top() < viewGeometry.top()) {
622 Q_ASSERT(scrollOrientation() == Qt::Vertical);
623 newOffset += currentBoundingRect.top() - viewGeometry.top();
624 }
625 else if ((currentBoundingRect.bottom() > viewGeometry.bottom())) {
626 Q_ASSERT(scrollOrientation() == Qt::Vertical);
627 newOffset += currentBoundingRect.bottom() - viewGeometry.bottom();
628 }
629 else if (currentBoundingRect.left() < viewGeometry.left()) {
630 if (scrollOrientation() == Qt::Horizontal) {
631 newOffset += currentBoundingRect.left() - viewGeometry.left();
632 }
633 }
634 else if ((currentBoundingRect.right() > viewGeometry.right())) {
635 if (scrollOrientation() == Qt::Horizontal) {
636 newOffset += currentBoundingRect.right() - viewGeometry.right();
637 }
638 }
639
640 emit scrollTo(newOffset);
641 }
642 }
643
644 void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
645 {
646 Q_UNUSED(previous);
647
648 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
649 while (it.hasNext()) {
650 it.next();
651 const int index = it.key();
652 KItemListWidget* widget = it.value();
653 widget->setSelected(current.contains(index));
654 }
655 }
656
657 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
658 KItemListViewAnimation::AnimationType type)
659 {
660 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
661 Q_ASSERT(itemListWidget);
662
663 switch (type) {
664 case KItemListViewAnimation::DeleteAnimation: {
665 // As we recycle the widget in this case it is important to assure that no
666 // other animation has been started. This is a convention in KItemListView and
667 // not a requirement defined by KItemListViewAnimation.
668 Q_ASSERT(!m_animation->isStarted(itemListWidget));
669
670 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
671 // by m_visibleWidgets and must be deleted manually after the animation has
672 // been finished.
673 KItemListGroupHeader* header = m_visibleGroups.value(itemListWidget);
674 if (header) {
675 m_groupHeaderCreator->recycle(header);
676 m_visibleGroups.remove(itemListWidget);
677 }
678 m_widgetCreator->recycle(itemListWidget);
679 break;
680 }
681
682 case KItemListViewAnimation::CreateAnimation:
683 case KItemListViewAnimation::MovingAnimation:
684 case KItemListViewAnimation::ResizeAnimation: {
685 const int index = itemListWidget->index();
686 const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
687 (index > m_layouter->lastVisibleIndex());
688 if (invisible && !m_animation->isStarted(itemListWidget)) {
689 recycleWidget(itemListWidget);
690 }
691 break;
692 }
693
694 default: break;
695 }
696 }
697
698 void KItemListView::slotLayoutTimerFinished()
699 {
700 m_layouter->setSize(geometry().size());
701 doLayout(Animation, 0, 0);
702 }
703
704 void KItemListView::setController(KItemListController* controller)
705 {
706 if (m_controller != controller) {
707 KItemListController* previous = m_controller;
708 if (previous) {
709 KItemListSelectionManager* selectionManager = previous->selectionManager();
710 disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
711 disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
712 }
713
714 m_controller = controller;
715
716 if (controller) {
717 KItemListSelectionManager* selectionManager = controller->selectionManager();
718 connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
719 connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
720 }
721
722 onControllerChanged(controller, previous);
723 }
724 }
725
726 void KItemListView::setModel(KItemModelBase* model)
727 {
728 if (m_model == model) {
729 return;
730 }
731
732 KItemModelBase* previous = m_model;
733
734 if (m_model) {
735 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
736 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
737 disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
738 this, SLOT(slotItemsInserted(KItemRangeList)));
739 disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
740 this, SLOT(slotItemsRemoved(KItemRangeList)));
741 }
742
743 m_model = model;
744 m_layouter->setModel(model);
745 m_grouped = !model->groupRole().isEmpty();
746
747 if (m_model) {
748 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
749 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
750 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
751 this, SLOT(slotItemsInserted(KItemRangeList)));
752 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
753 this, SLOT(slotItemsRemoved(KItemRangeList)));
754 }
755
756 onModelChanged(model, previous);
757 }
758
759 void KItemListView::updateLayout()
760 {
761 doLayout(Animation, 0, 0);
762 update();
763 }
764
765 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
766 {
767 if (m_layoutTimer->isActive()) {
768 kDebug() << "Stopping layout timer, synchronous layout requested";
769 m_layoutTimer->stop();
770 }
771
772 if (m_model->count() < 0 || m_activeTransactions > 0) {
773 return;
774 }
775
776 applyDynamicItemSize();
777
778 const int firstVisibleIndex = m_layouter->firstVisibleIndex();
779 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
780 if (firstVisibleIndex < 0) {
781 emitOffsetChanges();
782 return;
783 }
784
785 // Do a sanity check of the offset-property: When properties of the itemlist-view have been changed
786 // it might be possible that the maximum offset got changed too. Assure that the full visible range
787 // is still shown if the maximum offset got decreased.
788 const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
789 const qreal maxOffsetToShowFullRange = maximumOffset() - visibleOffsetRange;
790 if (offset() > maxOffsetToShowFullRange) {
791 m_layouter->setOffset(qMax(qreal(0), maxOffsetToShowFullRange));
792 }
793
794 // Determine all items that are completely invisible and might be
795 // reused for items that just got (at least partly) visible.
796 // Items that do e.g. an animated moving of their position are not
797 // marked as invisible: This assures that a scrolling inside the view
798 // can be done without breaking an animation.
799 QList<int> reusableItems;
800 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
801 while (it.hasNext()) {
802 it.next();
803 KItemListWidget* widget = it.value();
804 const int index = widget->index();
805 const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
806 if (invisible && !m_animation->isStarted(widget)) {
807 widget->setVisible(false);
808 reusableItems.append(index);
809 }
810 }
811
812 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
813 // instances from invisible items are reused. If no reusable items are
814 // found then new KItemListWidget instances get created.
815 const bool animate = (hint == Animation);
816 for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
817 bool applyNewPos = true;
818 bool wasHidden = false;
819
820 const QRectF itemBounds = m_layouter->itemBoundingRect(i);
821 const QPointF newPos = itemBounds.topLeft();
822 KItemListWidget* widget = m_visibleItems.value(i);
823 if (!widget) {
824 wasHidden = true;
825 if (!reusableItems.isEmpty()) {
826 // Reuse a KItemListWidget instance from an invisible item
827 const int oldIndex = reusableItems.takeLast();
828 widget = m_visibleItems.value(oldIndex);
829 setWidgetIndex(widget, i);
830 } else {
831 // No reusable KItemListWidget instance is available, create a new one
832 widget = createWidget(i);
833 }
834 widget->resize(itemBounds.size());
835
836 if (animate && changedCount < 0) {
837 // Items have been deleted, move the created item to the
838 // imaginary old position.
839 const QRectF itemBoundingRect = m_layouter->itemBoundingRect(i - changedCount);
840 if (itemBoundingRect.isEmpty()) {
841 const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical)
842 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
843 widget->setPos(invisibleOldPos);
844 } else {
845 widget->setPos(itemBoundingRect.topLeft());
846 }
847 applyNewPos = false;
848 }
849 } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
850 applyNewPos = false;
851 }
852
853 if (animate) {
854 const bool itemsRemoved = (changedCount < 0);
855 const bool itemsInserted = (changedCount > 0);
856
857 if (itemsRemoved && (i >= changedIndex + changedCount + 1)) {
858 // The item is located after the removed items. Animate the moving of the position.
859 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
860 applyNewPos = false;
861 } else if (itemsInserted && i >= changedIndex) {
862 // The item is located after the first inserted item
863 if (i <= changedIndex + changedCount - 1) {
864 // The item is an inserted item. Animate the appearing of the item.
865 // For performance reasons no animation is done when changedCount is equal
866 // to all available items.
867 if (changedCount < m_model->count()) {
868 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
869 }
870 } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
871 // The item was already there before, so animate the moving of the position.
872 // No moving animation is done if the item is animated by a create animation: This
873 // prevents a "move animation mess" when inserting several ranges in parallel.
874 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
875 applyNewPos = false;
876 }
877 } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
878 // The size of the view might have been changed. Animate the moving of the position.
879 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
880 applyNewPos = false;
881 }
882 }
883
884 if (applyNewPos) {
885 widget->setPos(newPos);
886 }
887
888 Q_ASSERT(widget->index() == i);
889 widget->setVisible(true);
890
891 if (widget->size() != itemBounds.size()) {
892 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
893 }
894 }
895
896 // Delete invisible KItemListWidget instances that have not been reused
897 foreach (int index, reusableItems) {
898 recycleWidget(m_visibleItems.value(index));
899 }
900
901 emitOffsetChanges();
902 }
903
904 void KItemListView::emitOffsetChanges()
905 {
906 const int newOffset = m_layouter->offset();
907 if (m_oldOffset != newOffset) {
908 emit offsetChanged(newOffset, m_oldOffset);
909 m_oldOffset = newOffset;
910 }
911
912 const int newMaximumOffset = m_layouter->maximumOffset();
913 if (m_oldMaximumOffset != newMaximumOffset) {
914 emit maximumOffsetChanged(newMaximumOffset, m_oldMaximumOffset);
915 m_oldMaximumOffset = newMaximumOffset;
916 }
917 }
918
919 KItemListWidget* KItemListView::createWidget(int index)
920 {
921 KItemListWidget* widget = m_widgetCreator->create(this);
922 updateWidgetProperties(widget, index);
923 m_visibleItems.insert(index, widget);
924
925 if (m_grouped) {
926 if (m_layouter->isFirstGroupItem(index)) {
927 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
928 header->setPos(0, -50);
929 header->resize(50, 50);
930 m_visibleGroups.insert(widget, header);
931 }
932 }
933
934 initializeItemListWidget(widget);
935 return widget;
936 }
937
938 void KItemListView::recycleWidget(KItemListWidget* widget)
939 {
940 if (m_grouped) {
941 KItemListGroupHeader* header = m_visibleGroups.value(widget);
942 if (header) {
943 m_groupHeaderCreator->recycle(header);
944 m_visibleGroups.remove(widget);
945 }
946 }
947
948 m_visibleItems.remove(widget->index());
949 m_widgetCreator->recycle(widget);
950 }
951
952 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
953 {
954 if (m_grouped) {
955 bool createHeader = m_layouter->isFirstGroupItem(index);
956 KItemListGroupHeader* header = m_visibleGroups.value(widget);
957 if (header) {
958 if (createHeader) {
959 createHeader = false;
960 } else {
961 m_groupHeaderCreator->recycle(header);
962 m_visibleGroups.remove(widget);
963 }
964 }
965
966 if (createHeader) {
967 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
968 header->setPos(0, -50);
969 header->resize(50, 50);
970 m_visibleGroups.insert(widget, header);
971 }
972 }
973
974 const int oldIndex = widget->index();
975 m_visibleItems.remove(oldIndex);
976 updateWidgetProperties(widget, index);
977 m_visibleItems.insert(index, widget);
978
979 initializeItemListWidget(widget);
980 }
981
982 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType)
983 {
984 // Calculate the first visible index and last visible index for the current size
985 const int currentFirst = m_layouter->firstVisibleIndex();
986 const int currentLast = m_layouter->lastVisibleIndex();
987
988 const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize();
989
990 // Calculate the first visible index and last visible index for the new size
991 setLayouterSize(size, sizeType);
992 const int newFirst = m_layouter->firstVisibleIndex();
993 const int newLast = m_layouter->lastVisibleIndex();
994
995 if ((currentFirst != newFirst) || (currentLast != newLast)) {
996 // At least one index has been changed. Assure that widgets for all possible
997 // visible items get created so that a move-animation can be started later.
998 const int maxVisibleItems = m_layouter->maximumVisibleItems();
999 int minFirst = qMin(newFirst, currentFirst);
1000 const int maxLast = qMax(newLast, currentLast);
1001
1002 if (maxLast - minFirst + 1 < maxVisibleItems) {
1003 // Increasing the size might result in a smaller KItemListView::offset().
1004 // Decrease the first visible index in a way that at least the maximum
1005 // visible items are shown.
1006 minFirst = qMax(0, maxLast - maxVisibleItems + 1);
1007 }
1008
1009 if (maxLast - minFirst > maxVisibleItems + maxVisibleItems / 2) {
1010 // The creating of widgets is quite expensive. Assure that never more
1011 // than 50 % of the maximum visible items get created for the animations.
1012 return;
1013 }
1014
1015 setLayouterSize(currentSize, sizeType);
1016 for (int i = minFirst; i <= maxLast; ++i) {
1017 if (!m_visibleItems.contains(i)) {
1018 KItemListWidget* widget = createWidget(i);
1019 const QPointF pos = m_layouter->itemBoundingRect(i).topLeft();
1020 widget->setPos(pos);
1021 }
1022 }
1023 setLayouterSize(size, sizeType);
1024 }
1025 }
1026
1027 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
1028 {
1029 switch (sizeType) {
1030 case LayouterSize: m_layouter->setSize(size); break;
1031 case ItemSize: m_layouter->setItemSize(size); break;
1032 default: break;
1033 }
1034 }
1035
1036 bool KItemListView::markVisibleRolesSizesAsDirty()
1037 {
1038 const bool dirty = m_itemSize.isEmpty();
1039 if (dirty) {
1040 m_visibleRolesSizes.clear();
1041 m_layouter->setItemSize(QSizeF());
1042 }
1043 return dirty;
1044 }
1045
1046 void KItemListView::applyDynamicItemSize()
1047 {
1048 if (!m_itemSize.isEmpty()) {
1049 return;
1050 }
1051
1052 if (m_visibleRolesSizes.isEmpty()) {
1053 m_visibleRolesSizes = visibleRoleSizes();
1054 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
1055 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1056 }
1057 }
1058
1059 if (m_layouter->itemSize().isEmpty()) {
1060 qreal requiredWidth = 0;
1061 qreal requiredHeight = 0;
1062
1063 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
1064 while (it.hasNext()) {
1065 it.next();
1066 const QSizeF& visibleRoleSize = it.value();
1067 requiredWidth += visibleRoleSize.width();
1068 requiredHeight += visibleRoleSize.height();
1069 }
1070
1071 QSizeF dynamicItemSize = m_itemSize;
1072 if (dynamicItemSize.width() <= 0) {
1073 dynamicItemSize.setWidth(qMax(requiredWidth, size().width()));
1074 }
1075 if (dynamicItemSize.height() <= 0) {
1076 dynamicItemSize.setHeight(qMax(requiredHeight, size().height()));
1077 }
1078
1079 m_layouter->setItemSize(dynamicItemSize);
1080 }
1081 }
1082
1083 void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
1084 {
1085 widget->setVisibleRoles(m_visibleRoles);
1086 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1087 widget->setStyleOption(m_styleOption);
1088
1089 const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
1090 widget->setCurrent(index == selectionManager->currentItem());
1091
1092 if (selectionManager->hasSelection()) {
1093 const QSet<int> selectedItems = selectionManager->selectedItems();
1094 widget->setSelected(selectedItems.contains(index));
1095 } else {
1096 widget->setSelected(false);
1097 }
1098
1099 widget->setHovered(false);
1100
1101 widget->setIndex(index);
1102 widget->setData(m_model->data(index));
1103 }
1104
1105 KItemListCreatorBase::~KItemListCreatorBase()
1106 {
1107 qDeleteAll(m_recycleableWidgets);
1108 qDeleteAll(m_createdWidgets);
1109 }
1110
1111 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
1112 {
1113 m_createdWidgets.insert(widget);
1114 }
1115
1116 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
1117 {
1118 Q_ASSERT(m_createdWidgets.contains(widget));
1119 m_createdWidgets.remove(widget);
1120
1121 if (m_recycleableWidgets.count() < 100) {
1122 m_recycleableWidgets.append(widget);
1123 widget->setVisible(false);
1124 } else {
1125 delete widget;
1126 }
1127 }
1128
1129 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
1130 {
1131 if (m_recycleableWidgets.isEmpty()) {
1132 return 0;
1133 }
1134
1135 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
1136 m_createdWidgets.insert(widget);
1137 return widget;
1138 }
1139
1140 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1141 {
1142 }
1143
1144 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
1145 {
1146 widget->setOpacity(1.0);
1147 pushRecycleableWidget(widget);
1148 }
1149
1150 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1151 {
1152 }
1153
1154 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
1155 {
1156 header->setOpacity(1.0);
1157 pushRecycleableWidget(header);
1158 }
1159
1160 #include "kitemlistview.moc"