]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
Provide basic rubberband functionality
[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 "kitemlistrubberband_p.h"
28 #include "kitemlistselectionmanager.h"
29 #include "kitemlistsizehintresolver_p.h"
30 #include "kitemlistviewlayouter_p.h"
31 #include "kitemlistviewanimation_p.h"
32 #include "kitemlistwidget.h"
33
34 #include <KDebug>
35
36 #include <QCursor>
37 #include <QGraphicsSceneMouseEvent>
38 #include <QPainter>
39 #include <QPropertyAnimation>
40 #include <QStyle>
41 #include <QStyleOptionRubberBand>
42 #include <QTimer>
43
44 KItemListView::KItemListView(QGraphicsWidget* parent) :
45 QGraphicsWidget(parent),
46 m_autoScrollMarginEnabled(false),
47 m_grouped(false),
48 m_activeTransactions(0),
49 m_itemSize(),
50 m_controller(0),
51 m_model(0),
52 m_visibleRoles(),
53 m_visibleRolesSizes(),
54 m_widgetCreator(0),
55 m_groupHeaderCreator(0),
56 m_styleOption(),
57 m_visibleItems(),
58 m_visibleGroups(),
59 m_sizeHintResolver(0),
60 m_layouter(0),
61 m_animation(0),
62 m_layoutTimer(0),
63 m_oldOffset(0),
64 m_oldMaximumOffset(0),
65 m_rubberBand(0),
66 m_mousePos()
67 {
68 setAcceptHoverEvents(true);
69
70 m_sizeHintResolver = new KItemListSizeHintResolver(this);
71
72 m_layouter = new KItemListViewLayouter(this);
73 m_layouter->setSizeHintResolver(m_sizeHintResolver);
74
75 m_animation = new KItemListViewAnimation(this);
76 connect(m_animation, SIGNAL(finished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)),
77 this, SLOT(slotAnimationFinished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)));
78
79 m_layoutTimer = new QTimer(this);
80 m_layoutTimer->setInterval(300);
81 m_layoutTimer->setSingleShot(true);
82 connect(m_layoutTimer, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished()));
83
84 m_rubberBand = new KItemListRubberBand(this);
85 connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool)));
86 }
87
88 KItemListView::~KItemListView()
89 {
90 delete m_sizeHintResolver;
91 m_sizeHintResolver = 0;
92 }
93
94 void KItemListView::setScrollOrientation(Qt::Orientation orientation)
95 {
96 const Qt::Orientation previousOrientation = m_layouter->scrollOrientation();
97 if (orientation == previousOrientation) {
98 return;
99 }
100
101 m_layouter->setScrollOrientation(orientation);
102 m_animation->setScrollOrientation(orientation);
103 m_sizeHintResolver->clearCache();
104 updateLayout();
105 onScrollOrientationChanged(orientation, previousOrientation);
106 }
107
108 Qt::Orientation KItemListView::scrollOrientation() const
109 {
110 return m_layouter->scrollOrientation();
111 }
112
113 void KItemListView::setItemSize(const QSizeF& itemSize)
114 {
115 const QSizeF previousSize = m_itemSize;
116 if (itemSize == previousSize) {
117 return;
118 }
119
120 m_itemSize = itemSize;
121
122 if (!markVisibleRolesSizesAsDirty()) {
123 if (itemSize.width() < previousSize.width() || itemSize.height() < previousSize.height()) {
124 prepareLayoutForIncreasedItemCount(itemSize, ItemSize);
125 } else {
126 m_layouter->setItemSize(itemSize);
127 }
128 }
129
130 m_sizeHintResolver->clearCache();
131 updateLayout();
132 onItemSizeChanged(itemSize, previousSize);
133 }
134
135 QSizeF KItemListView::itemSize() const
136 {
137 return m_itemSize;
138 }
139
140 void KItemListView::setOffset(qreal offset)
141 {
142 if (offset < 0) {
143 offset = 0;
144 }
145
146 const qreal previousOffset = m_layouter->offset();
147 if (offset == previousOffset) {
148 return;
149 }
150
151 m_layouter->setOffset(offset);
152 m_animation->setOffset(offset);
153 if (!m_layoutTimer->isActive()) {
154 doLayout(NoAnimation, 0, 0);
155 update();
156 }
157 onOffsetChanged(offset, previousOffset);
158 }
159
160 qreal KItemListView::offset() const
161 {
162 return m_layouter->offset();
163 }
164
165 qreal KItemListView::maximumOffset() const
166 {
167 return m_layouter->maximumOffset();
168 }
169
170 void KItemListView::setVisibleRoles(const QHash<QByteArray, int>& roles)
171 {
172 const QHash<QByteArray, int> previousRoles = m_visibleRoles;
173 m_visibleRoles = roles;
174
175 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
176 while (it.hasNext()) {
177 it.next();
178 KItemListWidget* widget = it.value();
179 widget->setVisibleRoles(roles);
180 widget->setVisibleRolesSizes(m_visibleRolesSizes);
181 }
182
183 m_sizeHintResolver->clearCache();
184 m_layouter->markAsDirty();
185 onVisibleRolesChanged(roles, previousRoles);
186
187 markVisibleRolesSizesAsDirty();
188 updateLayout();
189 }
190
191 QHash<QByteArray, int> KItemListView::visibleRoles() const
192 {
193 return m_visibleRoles;
194 }
195
196 KItemListController* KItemListView::controller() const
197 {
198 return m_controller;
199 }
200
201 KItemModelBase* KItemListView::model() const
202 {
203 return m_model;
204 }
205
206 void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator)
207 {
208 m_widgetCreator = widgetCreator;
209 }
210
211 KItemListWidgetCreatorBase* KItemListView::widgetCreator() const
212 {
213 return m_widgetCreator;
214 }
215
216 void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator)
217 {
218 m_groupHeaderCreator = groupHeaderCreator;
219 }
220
221 KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const
222 {
223 return m_groupHeaderCreator;
224 }
225
226 void KItemListView::setStyleOption(const KItemListStyleOption& option)
227 {
228 const KItemListStyleOption previousOption = m_styleOption;
229 m_styleOption = option;
230
231 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
232 while (it.hasNext()) {
233 it.next();
234 it.value()->setStyleOption(option);
235 }
236
237 m_sizeHintResolver->clearCache();
238 updateLayout();
239 onStyleOptionChanged(option, previousOption);
240 }
241
242 const KItemListStyleOption& KItemListView::styleOption() const
243 {
244 return m_styleOption;
245 }
246
247 void KItemListView::setGeometry(const QRectF& rect)
248 {
249 QGraphicsWidget::setGeometry(rect);
250 if (!m_model) {
251 return;
252 }
253
254 if (m_itemSize.isEmpty()) {
255 m_layouter->setItemSize(QSizeF());
256 }
257
258 if (m_model->count() > 0) {
259 prepareLayoutForIncreasedItemCount(rect.size(), LayouterSize);
260 } else {
261 m_layouter->setSize(rect.size());
262 }
263
264 m_layoutTimer->start();
265 }
266
267 int KItemListView::itemAt(const QPointF& pos) const
268 {
269 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
270 while (it.hasNext()) {
271 it.next();
272
273 const KItemListWidget* widget = it.value();
274 const QPointF mappedPos = widget->mapFromItem(this, pos);
275 if (widget->contains(mappedPos)) {
276 return it.key();
277 }
278 }
279
280 return -1;
281 }
282
283 bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
284 {
285 Q_UNUSED(index);
286 Q_UNUSED(pos);
287 return false;
288 }
289
290 bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
291 {
292 const KItemListWidget* widget = m_visibleItems.value(index);
293 if (widget) {
294 const QRectF expansionToggleRect = widget->expansionToggleRect();
295 if (!expansionToggleRect.isEmpty()) {
296 const QPointF mappedPos = widget->mapFromItem(this, pos);
297 return expansionToggleRect.contains(mappedPos);
298 }
299 }
300 return false;
301 }
302
303 int KItemListView::firstVisibleIndex() const
304 {
305 return m_layouter->firstVisibleIndex();
306 }
307
308 int KItemListView::lastVisibleIndex() const
309 {
310 return m_layouter->lastVisibleIndex();
311 }
312
313 QSizeF KItemListView::itemSizeHint(int index) const
314 {
315 Q_UNUSED(index);
316 return itemSize();
317 }
318
319 QHash<QByteArray, QSizeF> KItemListView::visibleRoleSizes() const
320 {
321 return QHash<QByteArray, QSizeF>();
322 }
323
324 QRectF KItemListView::itemBoundingRect(int index) const
325 {
326 return m_layouter->itemBoundingRect(index);
327 }
328
329 int KItemListView::itemsPerOffset() const
330 {
331 return m_layouter->itemsPerOffset();
332 }
333
334 void KItemListView::beginTransaction()
335 {
336 ++m_activeTransactions;
337 if (m_activeTransactions == 1) {
338 onTransactionBegin();
339 }
340 }
341
342 void KItemListView::endTransaction()
343 {
344 --m_activeTransactions;
345 if (m_activeTransactions < 0) {
346 m_activeTransactions = 0;
347 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
348 }
349
350 if (m_activeTransactions == 0) {
351 onTransactionEnd();
352 updateLayout();
353 }
354 }
355
356 bool KItemListView::isTransactionActive() const
357 {
358 return m_activeTransactions > 0;
359 }
360
361 void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
362 {
363 QGraphicsWidget::paint(painter, option, widget);
364
365 if (m_rubberBand->isActive()) {
366 QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(),
367 m_rubberBand->endPosition()).normalized();
368
369 const QPointF topLeft = rubberBandRect.topLeft();
370 if (scrollOrientation() == Qt::Vertical) {
371 rubberBandRect.moveTo(topLeft.x(), topLeft.y() - offset());
372 } else {
373 rubberBandRect.moveTo(topLeft.x() - offset(), topLeft.y());
374 }
375
376 QStyleOptionRubberBand opt;
377 opt.initFrom(widget);
378 opt.shape = QRubberBand::Rectangle;
379 opt.opaque = false;
380 opt.rect = rubberBandRect.toRect();
381 style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
382 }
383 }
384
385 void KItemListView::initializeItemListWidget(KItemListWidget* item)
386 {
387 Q_UNUSED(item);
388 }
389
390 void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
391 {
392 Q_UNUSED(current);
393 Q_UNUSED(previous);
394 }
395
396 void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
397 {
398 Q_UNUSED(current);
399 Q_UNUSED(previous);
400 }
401
402 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
403 {
404 Q_UNUSED(current);
405 Q_UNUSED(previous);
406 }
407
408 void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
409 {
410 Q_UNUSED(current);
411 Q_UNUSED(previous);
412 }
413
414 void KItemListView::onOffsetChanged(qreal current, qreal previous)
415 {
416 Q_UNUSED(current);
417 Q_UNUSED(previous);
418 }
419
420 void KItemListView::onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous)
421 {
422 Q_UNUSED(current);
423 Q_UNUSED(previous);
424 }
425
426 void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
427 {
428 Q_UNUSED(current);
429 Q_UNUSED(previous);
430 }
431
432 void KItemListView::onTransactionBegin()
433 {
434 }
435
436 void KItemListView::onTransactionEnd()
437 {
438 }
439
440 bool KItemListView::event(QEvent* event)
441 {
442 // Forward all events to the controller and handle them there
443 if (m_controller && m_controller->processEvent(event, transform())) {
444 event->accept();
445 return true;
446 }
447 return QGraphicsWidget::event(event);
448 }
449
450 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
451 {
452 m_mousePos = transform().map(event->pos());
453 event->accept();
454 }
455
456 void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
457 {
458 m_mousePos = transform().map(event->pos());
459 QGraphicsWidget::mouseMoveEvent(event);
460 }
461
462 QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
463 {
464 return m_visibleItems.values();
465 }
466
467 void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
468 {
469 markVisibleRolesSizesAsDirty();
470
471 const bool hasMultipleRanges = (itemRanges.count() > 1);
472 if (hasMultipleRanges) {
473 beginTransaction();
474 }
475
476 int previouslyInsertedCount = 0;
477 foreach (const KItemRange& range, itemRanges) {
478 // range.index is related to the model before anything has been inserted.
479 // As in each loop the current item-range gets inserted the index must
480 // be increased by the already previously inserted items.
481 const int index = range.index + previouslyInsertedCount;
482 const int count = range.count;
483 if (index < 0 || count <= 0) {
484 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
485 continue;
486 }
487 previouslyInsertedCount += count;
488
489 m_sizeHintResolver->itemsInserted(index, count);
490
491 // Determine which visible items must be moved
492 QList<int> itemsToMove;
493 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
494 while (it.hasNext()) {
495 it.next();
496 const int visibleItemIndex = it.key();
497 if (visibleItemIndex >= index) {
498 itemsToMove.append(visibleItemIndex);
499 }
500 }
501
502 // Update the indexes of all KItemListWidget instances that are located
503 // after the inserted items. It is important to adjust the indexes in the order
504 // from the highest index to the lowest index to prevent overlaps when setting the new index.
505 qSort(itemsToMove);
506 for (int i = itemsToMove.count() - 1; i >= 0; --i) {
507 KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
508 Q_ASSERT(widget);
509 setWidgetIndex(widget, widget->index() + count);
510 }
511
512 m_layouter->markAsDirty();
513 if (m_model->count() == count && maximumOffset() > size().height()) {
514 kDebug() << "Scrollbar required, skipping layout";
515 const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
516 QSizeF layouterSize = m_layouter->size();
517 if (scrollOrientation() == Qt::Vertical) {
518 layouterSize.rwidth() -= scrollBarExtent;
519 } else {
520 layouterSize.rheight() -= scrollBarExtent;
521 }
522 m_layouter->setSize(layouterSize);
523 }
524
525 if (!hasMultipleRanges) {
526 doLayout(Animation, index, count);
527 update();
528 }
529 }
530
531 if (m_controller) {
532 m_controller->selectionManager()->itemsInserted(itemRanges);
533 }
534
535 if (hasMultipleRanges) {
536 endTransaction();
537 }
538 }
539
540 void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
541 {
542 markVisibleRolesSizesAsDirty();
543
544 const bool hasMultipleRanges = (itemRanges.count() > 1);
545 if (hasMultipleRanges) {
546 beginTransaction();
547 }
548
549 for (int i = itemRanges.count() - 1; i >= 0; --i) {
550 const KItemRange& range = itemRanges.at(i);
551 const int index = range.index;
552 const int count = range.count;
553 if (index < 0 || count <= 0) {
554 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
555 continue;
556 }
557
558 m_sizeHintResolver->itemsRemoved(index, count);
559
560 const int firstRemovedIndex = index;
561 const int lastRemovedIndex = index + count - 1;
562 const int lastIndex = m_model->count() + count - 1;
563
564 // Remove all KItemListWidget instances that got deleted
565 for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
566 KItemListWidget* widget = m_visibleItems.value(i);
567 if (!widget) {
568 continue;
569 }
570
571 m_animation->stop(widget);
572 // Stopping the animation might lead to recycling the widget if
573 // it is invisible (see slotAnimationFinished()).
574 // Check again whether it is still visible:
575 if (!m_visibleItems.contains(i)) {
576 continue;
577 }
578
579 if (m_model->count() == 0) {
580 // For performance reasons no animation is done when all items have
581 // been removed.
582 recycleWidget(widget);
583 } else {
584 // Animate the removing of the items. Special case: When removing an item there
585 // is no valid model index available anymore. For the
586 // remove-animation the item gets removed from m_visibleItems but the widget
587 // will stay alive until the animation has been finished and will
588 // be recycled (deleted) in KItemListView::slotAnimationFinished().
589 m_visibleItems.remove(i);
590 widget->setIndex(-1);
591 m_animation->start(widget, KItemListViewAnimation::DeleteAnimation);
592 }
593 }
594
595 // Update the indexes of all KItemListWidget instances that are located
596 // after the deleted items
597 for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) {
598 KItemListWidget* widget = m_visibleItems.value(i);
599 if (widget) {
600 const int newIndex = i - count;
601 setWidgetIndex(widget, newIndex);
602 }
603 }
604
605 m_layouter->markAsDirty();
606 if (!hasMultipleRanges) {
607 doLayout(Animation, index, -count);
608 update();
609 }
610 }
611
612 if (m_controller) {
613 m_controller->selectionManager()->itemsRemoved(itemRanges);
614 }
615
616 if (hasMultipleRanges) {
617 endTransaction();
618 }
619 }
620
621 void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
622 const QSet<QByteArray>& roles)
623 {
624 foreach (const KItemRange& itemRange, itemRanges) {
625 const int index = itemRange.index;
626 const int count = itemRange.count;
627
628 m_sizeHintResolver->itemsChanged(index, count, roles);
629
630 const int lastIndex = index + count - 1;
631 for (int i = index; i <= lastIndex; ++i) {
632 KItemListWidget* widget = m_visibleItems.value(i);
633 if (widget) {
634 widget->setData(m_model->data(i), roles);
635 }
636 }
637 }
638 }
639
640 void KItemListView::slotCurrentChanged(int current, int previous)
641 {
642 Q_UNUSED(previous);
643
644 KItemListWidget* previousWidget = m_visibleItems.value(previous, 0);
645 if (previousWidget) {
646 Q_ASSERT(previousWidget->isCurrent());
647 previousWidget->setCurrent(false);
648 }
649
650 KItemListWidget* currentWidget = m_visibleItems.value(current, 0);
651 if (currentWidget) {
652 Q_ASSERT(!currentWidget->isCurrent());
653 currentWidget->setCurrent(true);
654 }
655
656 const QRectF viewGeometry = geometry();
657 const QRectF currentBoundingRect = itemBoundingRect(current);
658
659 if (!viewGeometry.contains(currentBoundingRect)) {
660 // Make sure that the new current item is fully visible in the view.
661 qreal newOffset = offset();
662 if (currentBoundingRect.top() < viewGeometry.top()) {
663 Q_ASSERT(scrollOrientation() == Qt::Vertical);
664 newOffset += currentBoundingRect.top() - viewGeometry.top();
665 }
666 else if ((currentBoundingRect.bottom() > viewGeometry.bottom())) {
667 Q_ASSERT(scrollOrientation() == Qt::Vertical);
668 newOffset += currentBoundingRect.bottom() - viewGeometry.bottom();
669 }
670 else if (currentBoundingRect.left() < viewGeometry.left()) {
671 if (scrollOrientation() == Qt::Horizontal) {
672 newOffset += currentBoundingRect.left() - viewGeometry.left();
673 }
674 }
675 else if ((currentBoundingRect.right() > viewGeometry.right())) {
676 if (scrollOrientation() == Qt::Horizontal) {
677 newOffset += currentBoundingRect.right() - viewGeometry.right();
678 }
679 }
680
681 emit scrollTo(newOffset);
682 }
683 }
684
685 void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
686 {
687 Q_UNUSED(previous);
688
689 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
690 while (it.hasNext()) {
691 it.next();
692 const int index = it.key();
693 KItemListWidget* widget = it.value();
694 widget->setSelected(current.contains(index));
695 }
696 }
697
698 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
699 KItemListViewAnimation::AnimationType type)
700 {
701 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
702 Q_ASSERT(itemListWidget);
703
704 switch (type) {
705 case KItemListViewAnimation::DeleteAnimation: {
706 // As we recycle the widget in this case it is important to assure that no
707 // other animation has been started. This is a convention in KItemListView and
708 // not a requirement defined by KItemListViewAnimation.
709 Q_ASSERT(!m_animation->isStarted(itemListWidget));
710
711 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
712 // by m_visibleWidgets and must be deleted manually after the animation has
713 // been finished.
714 KItemListGroupHeader* header = m_visibleGroups.value(itemListWidget);
715 if (header) {
716 m_groupHeaderCreator->recycle(header);
717 m_visibleGroups.remove(itemListWidget);
718 }
719 m_widgetCreator->recycle(itemListWidget);
720 break;
721 }
722
723 case KItemListViewAnimation::CreateAnimation:
724 case KItemListViewAnimation::MovingAnimation:
725 case KItemListViewAnimation::ResizeAnimation: {
726 const int index = itemListWidget->index();
727 const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
728 (index > m_layouter->lastVisibleIndex());
729 if (invisible && !m_animation->isStarted(itemListWidget)) {
730 recycleWidget(itemListWidget);
731 }
732 break;
733 }
734
735 default: break;
736 }
737 }
738
739 void KItemListView::slotLayoutTimerFinished()
740 {
741 m_layouter->setSize(geometry().size());
742 doLayout(Animation, 0, 0);
743 }
744
745 void KItemListView::slotRubberBandStartPosChanged()
746 {
747 update();
748 }
749
750 void KItemListView::slotRubberBandEndPosChanged()
751 {
752 triggerAutoScrolling();
753 update();
754 }
755
756 void KItemListView::slotRubberBandActivationChanged(bool active)
757 {
758 if (active) {
759 connect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandStartPosChanged()));
760 connect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandEndPosChanged()));
761 } else {
762 disconnect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandStartPosChanged()));
763 disconnect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandEndPosChanged()));
764 }
765
766 update();
767 }
768
769 void KItemListView::setController(KItemListController* controller)
770 {
771 if (m_controller != controller) {
772 KItemListController* previous = m_controller;
773 if (previous) {
774 KItemListSelectionManager* selectionManager = previous->selectionManager();
775 disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
776 disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
777 }
778
779 m_controller = controller;
780
781 if (controller) {
782 KItemListSelectionManager* selectionManager = controller->selectionManager();
783 connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
784 connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
785 }
786
787 onControllerChanged(controller, previous);
788 }
789 }
790
791 void KItemListView::setModel(KItemModelBase* model)
792 {
793 if (m_model == model) {
794 return;
795 }
796
797 KItemModelBase* previous = m_model;
798
799 if (m_model) {
800 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
801 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
802 disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
803 this, SLOT(slotItemsInserted(KItemRangeList)));
804 disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
805 this, SLOT(slotItemsRemoved(KItemRangeList)));
806 }
807
808 m_model = model;
809 m_layouter->setModel(model);
810 m_grouped = !model->groupRole().isEmpty();
811
812 if (m_model) {
813 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
814 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
815 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
816 this, SLOT(slotItemsInserted(KItemRangeList)));
817 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
818 this, SLOT(slotItemsRemoved(KItemRangeList)));
819 }
820
821 onModelChanged(model, previous);
822 }
823
824 KItemListRubberBand* KItemListView::rubberBand() const
825 {
826 return m_rubberBand;
827 }
828
829 void KItemListView::updateLayout()
830 {
831 doLayout(Animation, 0, 0);
832 update();
833 }
834
835 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
836 {
837 if (m_layoutTimer->isActive()) {
838 kDebug() << "Stopping layout timer, synchronous layout requested";
839 m_layoutTimer->stop();
840 }
841
842 if (m_model->count() < 0 || m_activeTransactions > 0) {
843 return;
844 }
845
846 applyDynamicItemSize();
847
848 const int firstVisibleIndex = m_layouter->firstVisibleIndex();
849 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
850 if (firstVisibleIndex < 0) {
851 emitOffsetChanges();
852 return;
853 }
854
855 // Do a sanity check of the offset-property: When properties of the itemlist-view have been changed
856 // it might be possible that the maximum offset got changed too. Assure that the full visible range
857 // is still shown if the maximum offset got decreased.
858 const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
859 const qreal maxOffsetToShowFullRange = maximumOffset() - visibleOffsetRange;
860 if (offset() > maxOffsetToShowFullRange) {
861 m_layouter->setOffset(qMax(qreal(0), maxOffsetToShowFullRange));
862 }
863
864 // Determine all items that are completely invisible and might be
865 // reused for items that just got (at least partly) visible.
866 // Items that do e.g. an animated moving of their position are not
867 // marked as invisible: This assures that a scrolling inside the view
868 // can be done without breaking an animation.
869 QList<int> reusableItems;
870 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
871 while (it.hasNext()) {
872 it.next();
873 KItemListWidget* widget = it.value();
874 const int index = widget->index();
875 const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
876 if (invisible && !m_animation->isStarted(widget)) {
877 widget->setVisible(false);
878 reusableItems.append(index);
879 }
880 }
881
882 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
883 // instances from invisible items are reused. If no reusable items are
884 // found then new KItemListWidget instances get created.
885 const bool animate = (hint == Animation);
886 for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
887 bool applyNewPos = true;
888 bool wasHidden = false;
889
890 const QRectF itemBounds = m_layouter->itemBoundingRect(i);
891 const QPointF newPos = itemBounds.topLeft();
892 KItemListWidget* widget = m_visibleItems.value(i);
893 if (!widget) {
894 wasHidden = true;
895 if (!reusableItems.isEmpty()) {
896 // Reuse a KItemListWidget instance from an invisible item
897 const int oldIndex = reusableItems.takeLast();
898 widget = m_visibleItems.value(oldIndex);
899 setWidgetIndex(widget, i);
900 } else {
901 // No reusable KItemListWidget instance is available, create a new one
902 widget = createWidget(i);
903 }
904 widget->resize(itemBounds.size());
905
906 if (animate && changedCount < 0) {
907 // Items have been deleted, move the created item to the
908 // imaginary old position.
909 const QRectF itemBoundingRect = m_layouter->itemBoundingRect(i - changedCount);
910 if (itemBoundingRect.isEmpty()) {
911 const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical)
912 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
913 widget->setPos(invisibleOldPos);
914 } else {
915 widget->setPos(itemBoundingRect.topLeft());
916 }
917 applyNewPos = false;
918 }
919 } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
920 applyNewPos = false;
921 }
922
923 if (animate) {
924 const bool itemsRemoved = (changedCount < 0);
925 const bool itemsInserted = (changedCount > 0);
926
927 if (itemsRemoved && (i >= changedIndex + changedCount + 1)) {
928 // The item is located after the removed items. Animate the moving of the position.
929 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
930 applyNewPos = false;
931 } else if (itemsInserted && i >= changedIndex) {
932 // The item is located after the first inserted item
933 if (i <= changedIndex + changedCount - 1) {
934 // The item is an inserted item. Animate the appearing of the item.
935 // For performance reasons no animation is done when changedCount is equal
936 // to all available items.
937 if (changedCount < m_model->count()) {
938 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
939 }
940 } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
941 // The item was already there before, so animate the moving of the position.
942 // No moving animation is done if the item is animated by a create animation: This
943 // prevents a "move animation mess" when inserting several ranges in parallel.
944 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
945 applyNewPos = false;
946 }
947 } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
948 // The size of the view might have been changed. Animate the moving of the position.
949 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
950 applyNewPos = false;
951 }
952 }
953
954 if (applyNewPos) {
955 widget->setPos(newPos);
956 }
957
958 Q_ASSERT(widget->index() == i);
959 widget->setVisible(true);
960
961 if (widget->size() != itemBounds.size()) {
962 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
963 }
964 }
965
966 // Delete invisible KItemListWidget instances that have not been reused
967 foreach (int index, reusableItems) {
968 recycleWidget(m_visibleItems.value(index));
969 }
970
971 emitOffsetChanges();
972 }
973
974 void KItemListView::emitOffsetChanges()
975 {
976 const qreal newOffset = m_layouter->offset();
977 if (m_oldOffset != newOffset) {
978 emit offsetChanged(newOffset, m_oldOffset);
979 m_oldOffset = newOffset;
980 }
981
982 const qreal newMaximumOffset = m_layouter->maximumOffset();
983 if (m_oldMaximumOffset != newMaximumOffset) {
984 emit maximumOffsetChanged(newMaximumOffset, m_oldMaximumOffset);
985 m_oldMaximumOffset = newMaximumOffset;
986 }
987 }
988
989 KItemListWidget* KItemListView::createWidget(int index)
990 {
991 KItemListWidget* widget = m_widgetCreator->create(this);
992 updateWidgetProperties(widget, index);
993 m_visibleItems.insert(index, widget);
994
995 if (m_grouped) {
996 if (m_layouter->isFirstGroupItem(index)) {
997 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
998 header->setPos(0, -50);
999 header->resize(50, 50);
1000 m_visibleGroups.insert(widget, header);
1001 }
1002 }
1003
1004 initializeItemListWidget(widget);
1005 return widget;
1006 }
1007
1008 void KItemListView::recycleWidget(KItemListWidget* widget)
1009 {
1010 if (m_grouped) {
1011 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1012 if (header) {
1013 m_groupHeaderCreator->recycle(header);
1014 m_visibleGroups.remove(widget);
1015 }
1016 }
1017
1018 m_visibleItems.remove(widget->index());
1019 m_widgetCreator->recycle(widget);
1020 }
1021
1022 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
1023 {
1024 if (m_grouped) {
1025 bool createHeader = m_layouter->isFirstGroupItem(index);
1026 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1027 if (header) {
1028 if (createHeader) {
1029 createHeader = false;
1030 } else {
1031 m_groupHeaderCreator->recycle(header);
1032 m_visibleGroups.remove(widget);
1033 }
1034 }
1035
1036 if (createHeader) {
1037 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
1038 header->setPos(0, -50);
1039 header->resize(50, 50);
1040 m_visibleGroups.insert(widget, header);
1041 }
1042 }
1043
1044 const int oldIndex = widget->index();
1045 m_visibleItems.remove(oldIndex);
1046 updateWidgetProperties(widget, index);
1047 m_visibleItems.insert(index, widget);
1048
1049 initializeItemListWidget(widget);
1050 }
1051
1052 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType)
1053 {
1054 // Calculate the first visible index and last visible index for the current size
1055 const int currentFirst = m_layouter->firstVisibleIndex();
1056 const int currentLast = m_layouter->lastVisibleIndex();
1057
1058 const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize();
1059
1060 // Calculate the first visible index and last visible index for the new size
1061 setLayouterSize(size, sizeType);
1062 const int newFirst = m_layouter->firstVisibleIndex();
1063 const int newLast = m_layouter->lastVisibleIndex();
1064
1065 if ((currentFirst != newFirst) || (currentLast != newLast)) {
1066 // At least one index has been changed. Assure that widgets for all possible
1067 // visible items get created so that a move-animation can be started later.
1068 const int maxVisibleItems = m_layouter->maximumVisibleItems();
1069 int minFirst = qMin(newFirst, currentFirst);
1070 const int maxLast = qMax(newLast, currentLast);
1071
1072 if (maxLast - minFirst + 1 < maxVisibleItems) {
1073 // Increasing the size might result in a smaller KItemListView::offset().
1074 // Decrease the first visible index in a way that at least the maximum
1075 // visible items are shown.
1076 minFirst = qMax(0, maxLast - maxVisibleItems + 1);
1077 }
1078
1079 if (maxLast - minFirst > maxVisibleItems + maxVisibleItems / 2) {
1080 // The creating of widgets is quite expensive. Assure that never more
1081 // than 50 % of the maximum visible items get created for the animations.
1082 return;
1083 }
1084
1085 setLayouterSize(currentSize, sizeType);
1086 for (int i = minFirst; i <= maxLast; ++i) {
1087 if (!m_visibleItems.contains(i)) {
1088 KItemListWidget* widget = createWidget(i);
1089 const QPointF pos = m_layouter->itemBoundingRect(i).topLeft();
1090 widget->setPos(pos);
1091 }
1092 }
1093 setLayouterSize(size, sizeType);
1094 }
1095 }
1096
1097 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
1098 {
1099 switch (sizeType) {
1100 case LayouterSize: m_layouter->setSize(size); break;
1101 case ItemSize: m_layouter->setItemSize(size); break;
1102 default: break;
1103 }
1104 }
1105
1106 bool KItemListView::markVisibleRolesSizesAsDirty()
1107 {
1108 const bool dirty = m_itemSize.isEmpty();
1109 if (dirty) {
1110 m_visibleRolesSizes.clear();
1111 m_layouter->setItemSize(QSizeF());
1112 }
1113 return dirty;
1114 }
1115
1116 void KItemListView::applyDynamicItemSize()
1117 {
1118 if (!m_itemSize.isEmpty()) {
1119 return;
1120 }
1121
1122 if (m_visibleRolesSizes.isEmpty()) {
1123 m_visibleRolesSizes = visibleRoleSizes();
1124 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
1125 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1126 }
1127 }
1128
1129 if (m_layouter->itemSize().isEmpty()) {
1130 qreal requiredWidth = 0;
1131 qreal requiredHeight = 0;
1132
1133 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
1134 while (it.hasNext()) {
1135 it.next();
1136 const QSizeF& visibleRoleSize = it.value();
1137 requiredWidth += visibleRoleSize.width();
1138 requiredHeight += visibleRoleSize.height();
1139 }
1140
1141 QSizeF dynamicItemSize = m_itemSize;
1142 if (dynamicItemSize.width() <= 0) {
1143 dynamicItemSize.setWidth(qMax(requiredWidth, size().width()));
1144 }
1145 if (dynamicItemSize.height() <= 0) {
1146 dynamicItemSize.setHeight(qMax(requiredHeight, size().height()));
1147 }
1148
1149 m_layouter->setItemSize(dynamicItemSize);
1150 }
1151 }
1152
1153 void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
1154 {
1155 widget->setVisibleRoles(m_visibleRoles);
1156 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1157 widget->setStyleOption(m_styleOption);
1158
1159 const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
1160 widget->setCurrent(index == selectionManager->currentItem());
1161
1162 if (selectionManager->hasSelection()) {
1163 const QSet<int> selectedItems = selectionManager->selectedItems();
1164 widget->setSelected(selectedItems.contains(index));
1165 } else {
1166 widget->setSelected(false);
1167 }
1168
1169 widget->setHovered(false);
1170
1171 widget->setIndex(index);
1172 widget->setData(m_model->data(index));
1173 }
1174
1175 void KItemListView::triggerAutoScrolling()
1176 {
1177 const int pos = (scrollOrientation() == Qt::Vertical) ? m_mousePos.y() : m_mousePos.x();
1178 const int inc = calculateAutoScrollingIncrement(pos, size().height());
1179 if (inc != 0) {
1180 emit scrollTo(offset() + inc);
1181 }
1182 }
1183
1184 int KItemListView::calculateAutoScrollingIncrement(int pos, int size)
1185 {
1186 int inc = 0;
1187
1188 const int minSpeed = 4;
1189 const int maxSpeed = 768;
1190 const int speedLimiter = 48;
1191 const int autoScrollBorder = 64;
1192
1193 if (pos < autoScrollBorder) {
1194 inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter;
1195 if (inc < -maxSpeed) {
1196 inc = -maxSpeed;
1197 }
1198 } else if (pos > size - autoScrollBorder) {
1199 inc = minSpeed + qAbs(pos - size + autoScrollBorder) * (pos - size + autoScrollBorder) / speedLimiter;
1200 if (inc > maxSpeed) {
1201 inc = maxSpeed;
1202 }
1203 }
1204
1205 return inc;
1206 }
1207
1208
1209 KItemListCreatorBase::~KItemListCreatorBase()
1210 {
1211 qDeleteAll(m_recycleableWidgets);
1212 qDeleteAll(m_createdWidgets);
1213 }
1214
1215 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
1216 {
1217 m_createdWidgets.insert(widget);
1218 }
1219
1220 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
1221 {
1222 Q_ASSERT(m_createdWidgets.contains(widget));
1223 m_createdWidgets.remove(widget);
1224
1225 if (m_recycleableWidgets.count() < 100) {
1226 m_recycleableWidgets.append(widget);
1227 widget->setVisible(false);
1228 } else {
1229 delete widget;
1230 }
1231 }
1232
1233 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
1234 {
1235 if (m_recycleableWidgets.isEmpty()) {
1236 return 0;
1237 }
1238
1239 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
1240 m_createdWidgets.insert(widget);
1241 return widget;
1242 }
1243
1244 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1245 {
1246 }
1247
1248 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
1249 {
1250 widget->setOpacity(1.0);
1251 pushRecycleableWidget(widget);
1252 }
1253
1254 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1255 {
1256 }
1257
1258 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
1259 {
1260 header->setOpacity(1.0);
1261 pushRecycleableWidget(header);
1262 }
1263
1264 #include "kitemlistview.moc"