]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
Respect Shift- and Control-key for the rubberband selection
[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 if (newOffset != offset()) {
682 emit scrollTo(newOffset);
683 }
684 }
685 }
686
687 void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
688 {
689 Q_UNUSED(previous);
690
691 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
692 while (it.hasNext()) {
693 it.next();
694 const int index = it.key();
695 KItemListWidget* widget = it.value();
696 widget->setSelected(current.contains(index));
697 }
698 }
699
700 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
701 KItemListViewAnimation::AnimationType type)
702 {
703 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
704 Q_ASSERT(itemListWidget);
705
706 switch (type) {
707 case KItemListViewAnimation::DeleteAnimation: {
708 // As we recycle the widget in this case it is important to assure that no
709 // other animation has been started. This is a convention in KItemListView and
710 // not a requirement defined by KItemListViewAnimation.
711 Q_ASSERT(!m_animation->isStarted(itemListWidget));
712
713 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
714 // by m_visibleWidgets and must be deleted manually after the animation has
715 // been finished.
716 KItemListGroupHeader* header = m_visibleGroups.value(itemListWidget);
717 if (header) {
718 m_groupHeaderCreator->recycle(header);
719 m_visibleGroups.remove(itemListWidget);
720 }
721 m_widgetCreator->recycle(itemListWidget);
722 break;
723 }
724
725 case KItemListViewAnimation::CreateAnimation:
726 case KItemListViewAnimation::MovingAnimation:
727 case KItemListViewAnimation::ResizeAnimation: {
728 const int index = itemListWidget->index();
729 const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
730 (index > m_layouter->lastVisibleIndex());
731 if (invisible && !m_animation->isStarted(itemListWidget)) {
732 recycleWidget(itemListWidget);
733 }
734 break;
735 }
736
737 default: break;
738 }
739 }
740
741 void KItemListView::slotLayoutTimerFinished()
742 {
743 m_layouter->setSize(geometry().size());
744 doLayout(Animation, 0, 0);
745 }
746
747 void KItemListView::slotRubberBandStartPosChanged()
748 {
749 update();
750 }
751
752 void KItemListView::slotRubberBandEndPosChanged()
753 {
754 // The autoscrolling is triggered asynchronously otherwise it
755 // might be possible to have an endless recursion: The autoscrolling
756 // might adjust the position which might result in updating the
757 // rubberband end-position.
758 QTimer::singleShot(0, this, SLOT(triggerAutoScrolling()));
759 update();
760 }
761
762 void KItemListView::slotRubberBandActivationChanged(bool active)
763 {
764 if (active) {
765 connect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandStartPosChanged()));
766 connect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandEndPosChanged()));
767 } else {
768 disconnect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandStartPosChanged()));
769 disconnect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandEndPosChanged()));
770 }
771
772 update();
773 }
774
775 void KItemListView::triggerAutoScrolling()
776 {
777 int pos = 0;
778 int visibleSize = 0;
779 if (scrollOrientation() == Qt::Vertical) {
780 pos = m_mousePos.y();
781 visibleSize = size().height();
782 } else {
783 pos = m_mousePos.x();
784 visibleSize = size().width();
785 }
786
787 const int inc = calculateAutoScrollingIncrement(pos, visibleSize);
788 if (inc == 0) {
789 // The mouse position is not above an autoscroll margin
790 return;
791 }
792
793 if (m_rubberBand->isActive()) {
794 // If a rubberband selection is ongoing the autoscrolling may only get triggered
795 // if the direction of the rubberband is similar to the autoscroll direction.
796
797 const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small
798 const qreal diff = (scrollOrientation() == Qt::Vertical)
799 ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y()
800 : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x();
801 if (qAbs(diff) < minDiff || (inc < 0 && diff > 0) || (inc > 0 && diff < 0)) {
802 // The rubberband direction is different from the scroll direction (e.g. the rubberband has
803 // been moved up although the autoscroll direction might be down)
804 return;
805 }
806 }
807
808 emit scrollTo(offset() + inc);
809 }
810
811 void KItemListView::setController(KItemListController* controller)
812 {
813 if (m_controller != controller) {
814 KItemListController* previous = m_controller;
815 if (previous) {
816 KItemListSelectionManager* selectionManager = previous->selectionManager();
817 disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
818 disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
819 }
820
821 m_controller = controller;
822
823 if (controller) {
824 KItemListSelectionManager* selectionManager = controller->selectionManager();
825 connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
826 connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
827 }
828
829 onControllerChanged(controller, previous);
830 }
831 }
832
833 void KItemListView::setModel(KItemModelBase* model)
834 {
835 if (m_model == model) {
836 return;
837 }
838
839 KItemModelBase* previous = m_model;
840
841 if (m_model) {
842 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
843 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
844 disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
845 this, SLOT(slotItemsInserted(KItemRangeList)));
846 disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
847 this, SLOT(slotItemsRemoved(KItemRangeList)));
848 }
849
850 m_model = model;
851 m_layouter->setModel(model);
852 m_grouped = !model->groupRole().isEmpty();
853
854 if (m_model) {
855 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
856 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
857 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
858 this, SLOT(slotItemsInserted(KItemRangeList)));
859 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
860 this, SLOT(slotItemsRemoved(KItemRangeList)));
861 }
862
863 onModelChanged(model, previous);
864 }
865
866 KItemListRubberBand* KItemListView::rubberBand() const
867 {
868 return m_rubberBand;
869 }
870
871 void KItemListView::updateLayout()
872 {
873 doLayout(Animation, 0, 0);
874 update();
875 }
876
877 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
878 {
879 if (m_layoutTimer->isActive()) {
880 kDebug() << "Stopping layout timer, synchronous layout requested";
881 m_layoutTimer->stop();
882 }
883
884 if (m_model->count() < 0 || m_activeTransactions > 0) {
885 return;
886 }
887
888 applyDynamicItemSize();
889
890 const int firstVisibleIndex = m_layouter->firstVisibleIndex();
891 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
892 if (firstVisibleIndex < 0) {
893 emitOffsetChanges();
894 return;
895 }
896
897 // Do a sanity check of the offset-property: When properties of the itemlist-view have been changed
898 // it might be possible that the maximum offset got changed too. Assure that the full visible range
899 // is still shown if the maximum offset got decreased.
900 const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
901 const qreal maxOffsetToShowFullRange = maximumOffset() - visibleOffsetRange;
902 if (offset() > maxOffsetToShowFullRange) {
903 m_layouter->setOffset(qMax(qreal(0), maxOffsetToShowFullRange));
904 }
905
906 // Determine all items that are completely invisible and might be
907 // reused for items that just got (at least partly) visible.
908 // Items that do e.g. an animated moving of their position are not
909 // marked as invisible: This assures that a scrolling inside the view
910 // can be done without breaking an animation.
911 QList<int> reusableItems;
912 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
913 while (it.hasNext()) {
914 it.next();
915 KItemListWidget* widget = it.value();
916 const int index = widget->index();
917 const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
918 if (invisible && !m_animation->isStarted(widget)) {
919 widget->setVisible(false);
920 reusableItems.append(index);
921 }
922 }
923
924 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
925 // instances from invisible items are reused. If no reusable items are
926 // found then new KItemListWidget instances get created.
927 const bool animate = (hint == Animation);
928 for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
929 bool applyNewPos = true;
930 bool wasHidden = false;
931
932 const QRectF itemBounds = m_layouter->itemBoundingRect(i);
933 const QPointF newPos = itemBounds.topLeft();
934 KItemListWidget* widget = m_visibleItems.value(i);
935 if (!widget) {
936 wasHidden = true;
937 if (!reusableItems.isEmpty()) {
938 // Reuse a KItemListWidget instance from an invisible item
939 const int oldIndex = reusableItems.takeLast();
940 widget = m_visibleItems.value(oldIndex);
941 setWidgetIndex(widget, i);
942 } else {
943 // No reusable KItemListWidget instance is available, create a new one
944 widget = createWidget(i);
945 }
946 widget->resize(itemBounds.size());
947
948 if (animate && changedCount < 0) {
949 // Items have been deleted, move the created item to the
950 // imaginary old position.
951 const QRectF itemBoundingRect = m_layouter->itemBoundingRect(i - changedCount);
952 if (itemBoundingRect.isEmpty()) {
953 const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical)
954 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
955 widget->setPos(invisibleOldPos);
956 } else {
957 widget->setPos(itemBoundingRect.topLeft());
958 }
959 applyNewPos = false;
960 }
961 } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
962 applyNewPos = false;
963 }
964
965 if (animate) {
966 const bool itemsRemoved = (changedCount < 0);
967 const bool itemsInserted = (changedCount > 0);
968
969 if (itemsRemoved && (i >= changedIndex + changedCount + 1)) {
970 // The item is located after the removed items. Animate the moving of the position.
971 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
972 applyNewPos = false;
973 } else if (itemsInserted && i >= changedIndex) {
974 // The item is located after the first inserted item
975 if (i <= changedIndex + changedCount - 1) {
976 // The item is an inserted item. Animate the appearing of the item.
977 // For performance reasons no animation is done when changedCount is equal
978 // to all available items.
979 if (changedCount < m_model->count()) {
980 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
981 }
982 } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
983 // The item was already there before, so animate the moving of the position.
984 // No moving animation is done if the item is animated by a create animation: This
985 // prevents a "move animation mess" when inserting several ranges in parallel.
986 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
987 applyNewPos = false;
988 }
989 } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
990 // The size of the view might have been changed. Animate the moving of the position.
991 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
992 applyNewPos = false;
993 }
994 }
995
996 if (applyNewPos) {
997 widget->setPos(newPos);
998 }
999
1000 Q_ASSERT(widget->index() == i);
1001 widget->setVisible(true);
1002
1003 if (widget->size() != itemBounds.size()) {
1004 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
1005 }
1006 }
1007
1008 // Delete invisible KItemListWidget instances that have not been reused
1009 foreach (int index, reusableItems) {
1010 recycleWidget(m_visibleItems.value(index));
1011 }
1012
1013 emitOffsetChanges();
1014 }
1015
1016 void KItemListView::emitOffsetChanges()
1017 {
1018 const qreal newOffset = m_layouter->offset();
1019 if (m_oldOffset != newOffset) {
1020 emit offsetChanged(newOffset, m_oldOffset);
1021 m_oldOffset = newOffset;
1022 }
1023
1024 const qreal newMaximumOffset = m_layouter->maximumOffset();
1025 if (m_oldMaximumOffset != newMaximumOffset) {
1026 emit maximumOffsetChanged(newMaximumOffset, m_oldMaximumOffset);
1027 m_oldMaximumOffset = newMaximumOffset;
1028 }
1029 }
1030
1031 KItemListWidget* KItemListView::createWidget(int index)
1032 {
1033 KItemListWidget* widget = m_widgetCreator->create(this);
1034 updateWidgetProperties(widget, index);
1035 m_visibleItems.insert(index, widget);
1036
1037 if (m_grouped) {
1038 if (m_layouter->isFirstGroupItem(index)) {
1039 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
1040 header->setPos(0, -50);
1041 header->resize(50, 50);
1042 m_visibleGroups.insert(widget, header);
1043 }
1044 }
1045
1046 initializeItemListWidget(widget);
1047 return widget;
1048 }
1049
1050 void KItemListView::recycleWidget(KItemListWidget* widget)
1051 {
1052 if (m_grouped) {
1053 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1054 if (header) {
1055 m_groupHeaderCreator->recycle(header);
1056 m_visibleGroups.remove(widget);
1057 }
1058 }
1059
1060 m_visibleItems.remove(widget->index());
1061 m_widgetCreator->recycle(widget);
1062 }
1063
1064 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
1065 {
1066 if (m_grouped) {
1067 bool createHeader = m_layouter->isFirstGroupItem(index);
1068 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1069 if (header) {
1070 if (createHeader) {
1071 createHeader = false;
1072 } else {
1073 m_groupHeaderCreator->recycle(header);
1074 m_visibleGroups.remove(widget);
1075 }
1076 }
1077
1078 if (createHeader) {
1079 KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
1080 header->setPos(0, -50);
1081 header->resize(50, 50);
1082 m_visibleGroups.insert(widget, header);
1083 }
1084 }
1085
1086 const int oldIndex = widget->index();
1087 m_visibleItems.remove(oldIndex);
1088 updateWidgetProperties(widget, index);
1089 m_visibleItems.insert(index, widget);
1090
1091 initializeItemListWidget(widget);
1092 }
1093
1094 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType)
1095 {
1096 // Calculate the first visible index and last visible index for the current size
1097 const int currentFirst = m_layouter->firstVisibleIndex();
1098 const int currentLast = m_layouter->lastVisibleIndex();
1099
1100 const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize();
1101
1102 // Calculate the first visible index and last visible index for the new size
1103 setLayouterSize(size, sizeType);
1104 const int newFirst = m_layouter->firstVisibleIndex();
1105 const int newLast = m_layouter->lastVisibleIndex();
1106
1107 if ((currentFirst != newFirst) || (currentLast != newLast)) {
1108 // At least one index has been changed. Assure that widgets for all possible
1109 // visible items get created so that a move-animation can be started later.
1110 const int maxVisibleItems = m_layouter->maximumVisibleItems();
1111 int minFirst = qMin(newFirst, currentFirst);
1112 const int maxLast = qMax(newLast, currentLast);
1113
1114 if (maxLast - minFirst + 1 < maxVisibleItems) {
1115 // Increasing the size might result in a smaller KItemListView::offset().
1116 // Decrease the first visible index in a way that at least the maximum
1117 // visible items are shown.
1118 minFirst = qMax(0, maxLast - maxVisibleItems + 1);
1119 }
1120
1121 if (maxLast - minFirst > maxVisibleItems + maxVisibleItems / 2) {
1122 // The creating of widgets is quite expensive. Assure that never more
1123 // than 50 % of the maximum visible items get created for the animations.
1124 return;
1125 }
1126
1127 setLayouterSize(currentSize, sizeType);
1128 for (int i = minFirst; i <= maxLast; ++i) {
1129 if (!m_visibleItems.contains(i)) {
1130 KItemListWidget* widget = createWidget(i);
1131 const QPointF pos = m_layouter->itemBoundingRect(i).topLeft();
1132 widget->setPos(pos);
1133 }
1134 }
1135 setLayouterSize(size, sizeType);
1136 }
1137 }
1138
1139 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
1140 {
1141 switch (sizeType) {
1142 case LayouterSize: m_layouter->setSize(size); break;
1143 case ItemSize: m_layouter->setItemSize(size); break;
1144 default: break;
1145 }
1146 }
1147
1148 bool KItemListView::markVisibleRolesSizesAsDirty()
1149 {
1150 const bool dirty = m_itemSize.isEmpty();
1151 if (dirty) {
1152 m_visibleRolesSizes.clear();
1153 m_layouter->setItemSize(QSizeF());
1154 }
1155 return dirty;
1156 }
1157
1158 void KItemListView::applyDynamicItemSize()
1159 {
1160 if (!m_itemSize.isEmpty()) {
1161 return;
1162 }
1163
1164 if (m_visibleRolesSizes.isEmpty()) {
1165 m_visibleRolesSizes = visibleRoleSizes();
1166 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
1167 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1168 }
1169 }
1170
1171 if (m_layouter->itemSize().isEmpty()) {
1172 qreal requiredWidth = 0;
1173 qreal requiredHeight = 0;
1174
1175 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
1176 while (it.hasNext()) {
1177 it.next();
1178 const QSizeF& visibleRoleSize = it.value();
1179 requiredWidth += visibleRoleSize.width();
1180 requiredHeight += visibleRoleSize.height();
1181 }
1182
1183 QSizeF dynamicItemSize = m_itemSize;
1184 if (dynamicItemSize.width() <= 0) {
1185 dynamicItemSize.setWidth(qMax(requiredWidth, size().width()));
1186 }
1187 if (dynamicItemSize.height() <= 0) {
1188 dynamicItemSize.setHeight(qMax(requiredHeight, size().height()));
1189 }
1190
1191 m_layouter->setItemSize(dynamicItemSize);
1192 }
1193 }
1194
1195 void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
1196 {
1197 widget->setVisibleRoles(m_visibleRoles);
1198 widget->setVisibleRolesSizes(m_visibleRolesSizes);
1199 widget->setStyleOption(m_styleOption);
1200
1201 const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
1202 widget->setCurrent(index == selectionManager->currentItem());
1203
1204 if (selectionManager->hasSelection()) {
1205 const QSet<int> selectedItems = selectionManager->selectedItems();
1206 widget->setSelected(selectedItems.contains(index));
1207 } else {
1208 widget->setSelected(false);
1209 }
1210
1211 widget->setHovered(false);
1212
1213 widget->setIndex(index);
1214 widget->setData(m_model->data(index));
1215 }
1216
1217 int KItemListView::calculateAutoScrollingIncrement(int pos, int size)
1218 {
1219 int inc = 0;
1220
1221 const int minSpeed = 4;
1222 const int maxSpeed = 768;
1223 const int speedLimiter = 48;
1224 const int autoScrollBorder = 64;
1225
1226 if (pos < autoScrollBorder) {
1227 inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter;
1228 if (inc < -maxSpeed) {
1229 inc = -maxSpeed;
1230 }
1231 } else if (pos > size - autoScrollBorder) {
1232 inc = minSpeed + qAbs(pos - size + autoScrollBorder) * (pos - size + autoScrollBorder) / speedLimiter;
1233 if (inc > maxSpeed) {
1234 inc = maxSpeed;
1235 }
1236 }
1237
1238 return inc;
1239 }
1240
1241
1242 KItemListCreatorBase::~KItemListCreatorBase()
1243 {
1244 qDeleteAll(m_recycleableWidgets);
1245 qDeleteAll(m_createdWidgets);
1246 }
1247
1248 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
1249 {
1250 m_createdWidgets.insert(widget);
1251 }
1252
1253 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
1254 {
1255 Q_ASSERT(m_createdWidgets.contains(widget));
1256 m_createdWidgets.remove(widget);
1257
1258 if (m_recycleableWidgets.count() < 100) {
1259 m_recycleableWidgets.append(widget);
1260 widget->setVisible(false);
1261 } else {
1262 delete widget;
1263 }
1264 }
1265
1266 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
1267 {
1268 if (m_recycleableWidgets.isEmpty()) {
1269 return 0;
1270 }
1271
1272 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
1273 m_createdWidgets.insert(widget);
1274 return widget;
1275 }
1276
1277 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1278 {
1279 }
1280
1281 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
1282 {
1283 widget->setOpacity(1.0);
1284 pushRecycleableWidget(widget);
1285 }
1286
1287 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1288 {
1289 }
1290
1291 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
1292 {
1293 header->setOpacity(1.0);
1294 pushRecycleableWidget(header);
1295 }
1296
1297 #include "kitemlistview.moc"