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