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