]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
SVN_SILENT made messages (.desktop file)
[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 "kitemlistheader_p.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 namespace {
45 // Time in ms until reaching the autoscroll margin triggers
46 // an initial autoscrolling
47 const int InitialAutoScrollDelay = 700;
48
49 // Delay in ms for triggering the next autoscroll
50 const int RepeatingAutoScrollDelay = 1000 / 60;
51 }
52
53 KItemListView::KItemListView(QGraphicsWidget* parent) :
54 QGraphicsWidget(parent),
55 m_grouped(false),
56 m_activeTransactions(0),
57 m_itemSize(),
58 m_controller(0),
59 m_model(0),
60 m_visibleRoles(),
61 m_visibleRolesSizes(),
62 m_stretchedVisibleRolesSizes(),
63 m_widgetCreator(0),
64 m_groupHeaderCreator(0),
65 m_styleOption(),
66 m_visibleItems(),
67 m_visibleGroups(),
68 m_sizeHintResolver(0),
69 m_layouter(0),
70 m_animation(0),
71 m_layoutTimer(0),
72 m_oldScrollOffset(0),
73 m_oldMaximumScrollOffset(0),
74 m_oldItemOffset(0),
75 m_oldMaximumItemOffset(0),
76 m_skipAutoScrollForRubberBand(false),
77 m_rubberBand(0),
78 m_mousePos(),
79 m_autoScrollIncrement(0),
80 m_autoScrollTimer(0),
81 m_header(0),
82 m_useHeaderWidths(false)
83 {
84 setAcceptHoverEvents(true);
85
86 m_sizeHintResolver = new KItemListSizeHintResolver(this);
87
88 m_layouter = new KItemListViewLayouter(this);
89 m_layouter->setSizeHintResolver(m_sizeHintResolver);
90
91 m_animation = new KItemListViewAnimation(this);
92 connect(m_animation, SIGNAL(finished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)),
93 this, SLOT(slotAnimationFinished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)));
94
95 m_layoutTimer = new QTimer(this);
96 m_layoutTimer->setInterval(300);
97 m_layoutTimer->setSingleShot(true);
98 connect(m_layoutTimer, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished()));
99
100 m_rubberBand = new KItemListRubberBand(this);
101 connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool)));
102 }
103
104 KItemListView::~KItemListView()
105 {
106 delete m_sizeHintResolver;
107 m_sizeHintResolver = 0;
108 }
109
110 void KItemListView::setScrollOrientation(Qt::Orientation orientation)
111 {
112 const Qt::Orientation previousOrientation = m_layouter->scrollOrientation();
113 if (orientation == previousOrientation) {
114 return;
115 }
116
117 m_layouter->setScrollOrientation(orientation);
118 m_animation->setScrollOrientation(orientation);
119 m_sizeHintResolver->clearCache();
120
121 if (m_grouped) {
122 QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
123 while (it.hasNext()) {
124 it.next();
125 it.value()->setScrollOrientation(orientation);
126 }
127 }
128
129 doLayout(Animation, 0, 0);
130
131 onScrollOrientationChanged(orientation, previousOrientation);
132 emit scrollOrientationChanged(orientation, previousOrientation);
133 }
134
135 Qt::Orientation KItemListView::scrollOrientation() const
136 {
137 return m_layouter->scrollOrientation();
138 }
139
140 void KItemListView::setItemSize(const QSizeF& itemSize)
141 {
142 const QSizeF previousSize = m_itemSize;
143 if (itemSize == previousSize) {
144 return;
145 }
146
147 m_itemSize = itemSize;
148
149 const bool emptySize = itemSize.isEmpty();
150 if (emptySize) {
151 updateVisibleRolesSizes();
152 } else {
153 if (itemSize.width() < previousSize.width() || itemSize.height() < previousSize.height()) {
154 prepareLayoutForIncreasedItemCount(itemSize, ItemSize);
155 } else {
156 m_layouter->setItemSize(itemSize);
157 }
158 }
159
160 m_sizeHintResolver->clearCache();
161 doLayout(Animation, 0, 0);
162 onItemSizeChanged(itemSize, previousSize);
163 }
164
165 QSizeF KItemListView::itemSize() const
166 {
167 return m_itemSize;
168 }
169
170 void KItemListView::setScrollOffset(qreal offset)
171 {
172 if (offset < 0) {
173 offset = 0;
174 }
175
176 const qreal previousOffset = m_layouter->scrollOffset();
177 if (offset == previousOffset) {
178 return;
179 }
180
181 m_layouter->setScrollOffset(offset);
182 m_animation->setScrollOffset(offset);
183 if (!m_layoutTimer->isActive()) {
184 doLayout(NoAnimation, 0, 0);
185 }
186 onScrollOffsetChanged(offset, previousOffset);
187 }
188
189 qreal KItemListView::scrollOffset() const
190 {
191 return m_layouter->scrollOffset();
192 }
193
194 qreal KItemListView::maximumScrollOffset() const
195 {
196 return m_layouter->maximumScrollOffset();
197 }
198
199 void KItemListView::setItemOffset(qreal offset)
200 {
201 m_layouter->setItemOffset(offset);
202 if (m_header) {
203 m_header->setPos(-offset, 0);
204 }
205 if (!m_layoutTimer->isActive()) {
206 doLayout(NoAnimation, 0, 0);
207 }
208 }
209
210 qreal KItemListView::itemOffset() const
211 {
212 return m_layouter->itemOffset();
213 }
214
215 qreal KItemListView::maximumItemOffset() const
216 {
217 return m_layouter->maximumItemOffset();
218 }
219
220 void KItemListView::setVisibleRoles(const QList<QByteArray>& roles)
221 {
222 const QList<QByteArray> previousRoles = m_visibleRoles;
223 m_visibleRoles = roles;
224
225 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
226 while (it.hasNext()) {
227 it.next();
228 KItemListWidget* widget = it.value();
229 widget->setVisibleRoles(roles);
230 widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
231 }
232
233 m_sizeHintResolver->clearCache();
234 m_layouter->markAsDirty();
235
236 if (m_header) {
237 m_header->setVisibleRoles(roles);
238 m_header->setVisibleRolesWidths(headerRolesWidths());
239 m_useHeaderWidths = false;
240 }
241
242 updateVisibleRolesSizes();
243 doLayout(Animation, 0, 0);
244
245 onVisibleRolesChanged(roles, previousRoles);
246 }
247
248 QList<QByteArray> KItemListView::visibleRoles() const
249 {
250 return m_visibleRoles;
251 }
252
253 void KItemListView::setAutoScroll(bool enabled)
254 {
255 if (enabled && !m_autoScrollTimer) {
256 m_autoScrollTimer = new QTimer(this);
257 m_autoScrollTimer->setSingleShot(false);
258 connect(m_autoScrollTimer, SIGNAL(timeout()), this, SLOT(triggerAutoScrolling()));
259 m_autoScrollTimer->start(InitialAutoScrollDelay);
260 } else if (!enabled && m_autoScrollTimer) {
261 delete m_autoScrollTimer;
262 m_autoScrollTimer = 0;
263 }
264
265 }
266
267 bool KItemListView::autoScroll() const
268 {
269 return m_autoScrollTimer != 0;
270 }
271
272 KItemListController* KItemListView::controller() const
273 {
274 return m_controller;
275 }
276
277 KItemModelBase* KItemListView::model() const
278 {
279 return m_model;
280 }
281
282 void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator)
283 {
284 m_widgetCreator = widgetCreator;
285 }
286
287 KItemListWidgetCreatorBase* KItemListView::widgetCreator() const
288 {
289 return m_widgetCreator;
290 }
291
292 void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator)
293 {
294 m_groupHeaderCreator = groupHeaderCreator;
295 }
296
297 KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const
298 {
299 return m_groupHeaderCreator;
300 }
301
302 void KItemListView::setStyleOption(const KItemListStyleOption& option)
303 {
304 const KItemListStyleOption previousOption = m_styleOption;
305 m_styleOption = option;
306
307 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
308 while (it.hasNext()) {
309 it.next();
310 it.value()->setStyleOption(option);
311 }
312
313 m_sizeHintResolver->clearCache();
314 doLayout(Animation, 0, 0);
315 onStyleOptionChanged(option, previousOption);
316 }
317
318 const KItemListStyleOption& KItemListView::styleOption() const
319 {
320 return m_styleOption;
321 }
322
323 void KItemListView::setGeometry(const QRectF& rect)
324 {
325 QGraphicsWidget::setGeometry(rect);
326
327 if (!m_model) {
328 return;
329 }
330
331 if (m_model->count() > 0) {
332 prepareLayoutForIncreasedItemCount(rect.size(), LayouterSize);
333 } else {
334 m_layouter->setSize(rect.size());
335 }
336
337 if (!m_layoutTimer->isActive()) {
338 m_layoutTimer->start();
339 }
340
341 // Changing the geometry does not require to do an expensive
342 // update of the visible-roles sizes, only the stretched sizes
343 // need to be adjusted to the new size.
344 updateStretchedVisibleRolesSizes();
345 }
346
347 int KItemListView::itemAt(const QPointF& pos) const
348 {
349 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
350 while (it.hasNext()) {
351 it.next();
352
353 const KItemListWidget* widget = it.value();
354 const QPointF mappedPos = widget->mapFromItem(this, pos);
355 if (widget->contains(mappedPos)) {
356 return it.key();
357 }
358 }
359
360 return -1;
361 }
362
363 bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
364 {
365 Q_UNUSED(index);
366 Q_UNUSED(pos);
367 return false;
368 }
369
370 bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
371 {
372 const KItemListWidget* widget = m_visibleItems.value(index);
373 if (widget) {
374 const QRectF expansionToggleRect = widget->expansionToggleRect();
375 if (!expansionToggleRect.isEmpty()) {
376 const QPointF mappedPos = widget->mapFromItem(this, pos);
377 return expansionToggleRect.contains(mappedPos);
378 }
379 }
380 return false;
381 }
382
383 int KItemListView::firstVisibleIndex() const
384 {
385 return m_layouter->firstVisibleIndex();
386 }
387
388 int KItemListView::lastVisibleIndex() const
389 {
390 return m_layouter->lastVisibleIndex();
391 }
392
393 QSizeF KItemListView::itemSizeHint(int index) const
394 {
395 Q_UNUSED(index);
396 return itemSize();
397 }
398
399 QHash<QByteArray, QSizeF> KItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const
400 {
401 Q_UNUSED(itemRanges);
402 return QHash<QByteArray, QSizeF>();
403 }
404
405 QRectF KItemListView::itemRect(int index) const
406 {
407 return m_layouter->itemRect(index);
408 }
409
410 int KItemListView::itemsPerOffset() const
411 {
412 return m_layouter->itemsPerOffset();
413 }
414
415 void KItemListView::beginTransaction()
416 {
417 ++m_activeTransactions;
418 if (m_activeTransactions == 1) {
419 onTransactionBegin();
420 }
421 }
422
423 void KItemListView::endTransaction()
424 {
425 --m_activeTransactions;
426 if (m_activeTransactions < 0) {
427 m_activeTransactions = 0;
428 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
429 }
430
431 if (m_activeTransactions == 0) {
432 onTransactionEnd();
433 doLayout(Animation, 0, 0);
434 }
435 }
436
437 bool KItemListView::isTransactionActive() const
438 {
439 return m_activeTransactions > 0;
440 }
441
442
443 void KItemListView::setHeaderShown(bool show)
444 {
445
446 if (show && !m_header) {
447 m_header = new KItemListHeader(this);
448 m_header->setPos(0, 0);
449 m_header->setModel(m_model);
450 m_header->setVisibleRoles(m_visibleRoles);
451 m_header->setVisibleRolesWidths(headerRolesWidths());
452 m_header->setZValue(1);
453
454 m_useHeaderWidths = false;
455
456 connect(m_header, SIGNAL(visibleRoleWidthChanged(QByteArray,qreal,qreal)),
457 this, SLOT(slotVisibleRoleWidthChanged(QByteArray,qreal,qreal)));
458
459 m_layouter->setHeaderHeight(m_header->size().height());
460 } else if (!show && m_header) {
461 delete m_header;
462 m_header = 0;
463 m_useHeaderWidths = false;
464 m_layouter->setHeaderHeight(0);
465 }
466 }
467
468 bool KItemListView::isHeaderShown() const
469 {
470 return m_header != 0;
471 }
472
473 QPixmap KItemListView::createDragPixmap(const QSet<int>& indexes) const
474 {
475 Q_UNUSED(indexes);
476 return QPixmap();
477 }
478
479 void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
480 {
481 QGraphicsWidget::paint(painter, option, widget);
482
483 if (m_rubberBand->isActive()) {
484 QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(),
485 m_rubberBand->endPosition()).normalized();
486
487 const QPointF topLeft = rubberBandRect.topLeft();
488 if (scrollOrientation() == Qt::Vertical) {
489 rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset());
490 } else {
491 rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y());
492 }
493
494 QStyleOptionRubberBand opt;
495 opt.initFrom(widget);
496 opt.shape = QRubberBand::Rectangle;
497 opt.opaque = false;
498 opt.rect = rubberBandRect.toRect();
499 style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
500 }
501 }
502
503 void KItemListView::initializeItemListWidget(KItemListWidget* item)
504 {
505 Q_UNUSED(item);
506 }
507
508 bool KItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const
509 {
510 Q_UNUSED(changedRoles);
511 return true;
512 }
513
514 void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
515 {
516 Q_UNUSED(current);
517 Q_UNUSED(previous);
518 }
519
520 void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
521 {
522 Q_UNUSED(current);
523 Q_UNUSED(previous);
524 }
525
526 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
527 {
528 Q_UNUSED(current);
529 Q_UNUSED(previous);
530 }
531
532 void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
533 {
534 Q_UNUSED(current);
535 Q_UNUSED(previous);
536 }
537
538 void KItemListView::onScrollOffsetChanged(qreal current, qreal previous)
539 {
540 Q_UNUSED(current);
541 Q_UNUSED(previous);
542 }
543
544 void KItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
545 {
546 Q_UNUSED(current);
547 Q_UNUSED(previous);
548 }
549
550 void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
551 {
552 Q_UNUSED(current);
553 Q_UNUSED(previous);
554 }
555
556 void KItemListView::onTransactionBegin()
557 {
558 }
559
560 void KItemListView::onTransactionEnd()
561 {
562 }
563
564 bool KItemListView::event(QEvent* event)
565 {
566 // Forward all events to the controller and handle them there
567 if (m_controller && m_controller->processEvent(event, transform())) {
568 event->accept();
569 return true;
570 }
571 return QGraphicsWidget::event(event);
572 }
573
574 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
575 {
576 m_mousePos = transform().map(event->pos());
577 event->accept();
578 }
579
580 void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
581 {
582 QGraphicsWidget::mouseMoveEvent(event);
583
584 m_mousePos = transform().map(event->pos());
585 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) {
586 m_autoScrollTimer->start(InitialAutoScrollDelay);
587 }
588 }
589
590 void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
591 {
592 event->setAccepted(true);
593 setAutoScroll(true);
594 }
595
596 void KItemListView::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
597 {
598 QGraphicsWidget::dragMoveEvent(event);
599
600 m_mousePos = transform().map(event->pos());
601 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) {
602 m_autoScrollTimer->start(InitialAutoScrollDelay);
603 }
604 }
605
606 void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
607 {
608 QGraphicsWidget::dragLeaveEvent(event);
609 setAutoScroll(false);
610 }
611
612 void KItemListView::dropEvent(QGraphicsSceneDragDropEvent* event)
613 {
614 QGraphicsWidget::dropEvent(event);
615 setAutoScroll(false);
616 }
617
618 QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
619 {
620 return m_visibleItems.values();
621 }
622
623 void KItemListView::resizeEvent(QGraphicsSceneResizeEvent* event)
624 {
625 QGraphicsWidget::resizeEvent(event);
626 if (m_itemSize.isEmpty() && m_useHeaderWidths) {
627 QSizeF dynamicItemSize = m_layouter->itemSize();
628 const QSizeF newSize = event->newSize();
629
630 if (m_itemSize.width() < 0) {
631 const qreal requiredWidth = visibleRolesSizesWidthSum();
632 if (newSize.width() > requiredWidth) {
633 dynamicItemSize.setWidth(newSize.width());
634 }
635 const qreal headerWidth = qMax(newSize.width(), requiredWidth);
636 m_header->resize(headerWidth, m_header->size().height());
637 }
638
639 if (m_itemSize.height() < 0) {
640 const qreal requiredHeight = visibleRolesSizesHeightSum();
641 if (newSize.height() > requiredHeight) {
642 dynamicItemSize.setHeight(newSize.height());
643 }
644 // TODO: KItemListHeader is not prepared for vertical alignment
645 }
646
647 m_layouter->setItemSize(dynamicItemSize);
648 }
649 }
650
651 void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
652 {
653 updateVisibleRolesSizes(itemRanges);
654
655 const bool hasMultipleRanges = (itemRanges.count() > 1);
656 if (hasMultipleRanges) {
657 beginTransaction();
658 }
659
660 int previouslyInsertedCount = 0;
661 foreach (const KItemRange& range, itemRanges) {
662 // range.index is related to the model before anything has been inserted.
663 // As in each loop the current item-range gets inserted the index must
664 // be increased by the already previously inserted items.
665 const int index = range.index + previouslyInsertedCount;
666 const int count = range.count;
667 if (index < 0 || count <= 0) {
668 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
669 continue;
670 }
671 previouslyInsertedCount += count;
672
673 m_sizeHintResolver->itemsInserted(index, count);
674
675 // Determine which visible items must be moved
676 QList<int> itemsToMove;
677 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
678 while (it.hasNext()) {
679 it.next();
680 const int visibleItemIndex = it.key();
681 if (visibleItemIndex >= index) {
682 itemsToMove.append(visibleItemIndex);
683 }
684 }
685
686 // Update the indexes of all KItemListWidget instances that are located
687 // after the inserted items. It is important to adjust the indexes in the order
688 // from the highest index to the lowest index to prevent overlaps when setting the new index.
689 qSort(itemsToMove);
690 for (int i = itemsToMove.count() - 1; i >= 0; --i) {
691 KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
692 Q_ASSERT(widget);
693 setWidgetIndex(widget, widget->index() + count);
694 }
695
696 m_layouter->markAsDirty();
697 if (m_model->count() == count && maximumScrollOffset() > size().height()) {
698 kDebug() << "Scrollbar required, skipping layout";
699 const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
700 QSizeF layouterSize = m_layouter->size();
701 if (scrollOrientation() == Qt::Vertical) {
702 layouterSize.rwidth() -= scrollBarExtent;
703 } else {
704 layouterSize.rheight() -= scrollBarExtent;
705 }
706 m_layouter->setSize(layouterSize);
707 }
708
709 if (!hasMultipleRanges) {
710 doLayout(Animation, index, count);
711 }
712 }
713
714 if (m_controller) {
715 m_controller->selectionManager()->itemsInserted(itemRanges);
716 }
717
718 if (hasMultipleRanges) {
719 endTransaction();
720 }
721 }
722
723 void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
724 {
725 updateVisibleRolesSizes();
726
727 const bool hasMultipleRanges = (itemRanges.count() > 1);
728 if (hasMultipleRanges) {
729 beginTransaction();
730 }
731
732 for (int i = itemRanges.count() - 1; i >= 0; --i) {
733 const KItemRange& range = itemRanges.at(i);
734 const int index = range.index;
735 const int count = range.count;
736 if (index < 0 || count <= 0) {
737 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
738 continue;
739 }
740
741 m_sizeHintResolver->itemsRemoved(index, count);
742
743 const int firstRemovedIndex = index;
744 const int lastRemovedIndex = index + count - 1;
745 const int lastIndex = m_model->count() + count - 1;
746
747 // Remove all KItemListWidget instances that got deleted
748 for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
749 KItemListWidget* widget = m_visibleItems.value(i);
750 if (!widget) {
751 continue;
752 }
753
754 m_animation->stop(widget);
755 // Stopping the animation might lead to recycling the widget if
756 // it is invisible (see slotAnimationFinished()).
757 // Check again whether it is still visible:
758 if (!m_visibleItems.contains(i)) {
759 continue;
760 }
761
762 if (m_model->count() == 0) {
763 // For performance reasons no animation is done when all items have
764 // been removed.
765 recycleWidget(widget);
766 } else {
767 // Animate the removing of the items. Special case: When removing an item there
768 // is no valid model index available anymore. For the
769 // remove-animation the item gets removed from m_visibleItems but the widget
770 // will stay alive until the animation has been finished and will
771 // be recycled (deleted) in KItemListView::slotAnimationFinished().
772 m_visibleItems.remove(i);
773 widget->setIndex(-1);
774 m_animation->start(widget, KItemListViewAnimation::DeleteAnimation);
775 }
776 }
777
778 // Update the indexes of all KItemListWidget instances that are located
779 // after the deleted items
780 for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) {
781 KItemListWidget* widget = m_visibleItems.value(i);
782 if (widget) {
783 const int newIndex = i - count;
784 setWidgetIndex(widget, newIndex);
785 }
786 }
787
788 m_layouter->markAsDirty();
789 if (!hasMultipleRanges) {
790 doLayout(Animation, index, -count);
791 }
792 }
793
794 if (m_controller) {
795 m_controller->selectionManager()->itemsRemoved(itemRanges);
796 }
797
798 if (hasMultipleRanges) {
799 endTransaction();
800 }
801 }
802
803 void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes)
804 {
805 m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count);
806 m_layouter->markAsDirty();
807
808 if (m_controller) {
809 m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes);
810 }
811
812 const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index);
813 const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1);
814
815 for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) {
816 KItemListWidget* widget = m_visibleItems.value(index);
817 if (widget) {
818 updateWidgetProperties(widget, index);
819 if (m_grouped) {
820 updateGroupHeaderForWidget(widget);
821 }
822 initializeItemListWidget(widget);
823 }
824 }
825
826 doLayout(NoAnimation, 0, 0);
827 }
828
829 void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
830 const QSet<QByteArray>& roles)
831 {
832 const bool updateSizeHints = itemSizeHintUpdateRequired(roles);
833 if (updateSizeHints) {
834 updateVisibleRolesSizes(itemRanges);
835 }
836
837 foreach (const KItemRange& itemRange, itemRanges) {
838 const int index = itemRange.index;
839 const int count = itemRange.count;
840
841 if (updateSizeHints) {
842 m_sizeHintResolver->itemsChanged(index, count, roles);
843 m_layouter->markAsDirty();
844
845 if (!m_layoutTimer->isActive()) {
846 m_layoutTimer->start();
847 }
848 }
849
850 // Apply the changed roles to the visible item-widgets
851 const int lastIndex = index + count - 1;
852 for (int i = index; i <= lastIndex; ++i) {
853 KItemListWidget* widget = m_visibleItems.value(i);
854 if (widget) {
855 widget->setData(m_model->data(i), roles);
856 }
857 }
858
859 if (m_grouped && roles.contains(m_model->sortRole())) {
860 // The sort-role has been changed which might result
861 // in modified group headers
862 updateVisibleGroupHeaders();
863 doLayout(NoAnimation, 0, 0);
864 }
865 }
866 }
867
868 void KItemListView::slotGroupedSortingChanged(bool current)
869 {
870 m_grouped = current;
871 m_layouter->markAsDirty();
872
873 if (m_grouped) {
874 // Apply the height of the header to the layouter
875 const qreal groupHeaderHeight = m_styleOption.fontMetrics.height() +
876 m_styleOption.margin * 2;
877 m_layouter->setGroupHeaderHeight(groupHeaderHeight);
878
879 updateVisibleGroupHeaders();
880 } else {
881 // Clear all visible headers
882 QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
883 while (it.hasNext()) {
884 it.next();
885 recycleGroupHeaderForWidget(it.key());
886 }
887 Q_ASSERT(m_visibleGroups.isEmpty());
888 }
889
890 doLayout(Animation, 0, 0);
891 }
892
893 void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
894 {
895 Q_UNUSED(current);
896 Q_UNUSED(previous);
897 if (m_grouped) {
898 updateVisibleGroupHeaders();
899 doLayout(Animation, 0, 0);
900 }
901 }
902
903 void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
904 {
905 Q_UNUSED(current);
906 Q_UNUSED(previous);
907 if (m_grouped) {
908 updateVisibleGroupHeaders();
909 doLayout(Animation, 0, 0);
910 }
911 }
912
913 void KItemListView::slotCurrentChanged(int current, int previous)
914 {
915 Q_UNUSED(previous);
916
917 KItemListWidget* previousWidget = m_visibleItems.value(previous, 0);
918 if (previousWidget) {
919 Q_ASSERT(previousWidget->isCurrent());
920 previousWidget->setCurrent(false);
921 }
922
923 KItemListWidget* currentWidget = m_visibleItems.value(current, 0);
924 if (currentWidget) {
925 Q_ASSERT(!currentWidget->isCurrent());
926 currentWidget->setCurrent(true);
927 }
928
929 const QRectF viewGeometry = geometry();
930 const QRectF currentRect = itemRect(current);
931
932 if (!viewGeometry.contains(currentRect)) {
933 // Make sure that the new current item is fully visible in the view.
934 qreal newOffset = scrollOffset();
935 if (currentRect.top() < viewGeometry.top()) {
936 Q_ASSERT(scrollOrientation() == Qt::Vertical);
937 newOffset += currentRect.top() - viewGeometry.top();
938 } else if ((currentRect.bottom() > viewGeometry.bottom())) {
939 Q_ASSERT(scrollOrientation() == Qt::Vertical);
940 newOffset += currentRect.bottom() - viewGeometry.bottom();
941 } else if (currentRect.left() < viewGeometry.left()) {
942 if (scrollOrientation() == Qt::Horizontal) {
943 newOffset += currentRect.left() - viewGeometry.left();
944 }
945 } else if ((currentRect.right() > viewGeometry.right())) {
946 if (scrollOrientation() == Qt::Horizontal) {
947 newOffset += currentRect.right() - viewGeometry.right();
948 }
949 }
950
951 if (newOffset != scrollOffset()) {
952 emit scrollTo(newOffset);
953 }
954 }
955 }
956
957 void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
958 {
959 Q_UNUSED(previous);
960
961 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
962 while (it.hasNext()) {
963 it.next();
964 const int index = it.key();
965 KItemListWidget* widget = it.value();
966 widget->setSelected(current.contains(index));
967 }
968 }
969
970 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
971 KItemListViewAnimation::AnimationType type)
972 {
973 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
974 Q_ASSERT(itemListWidget);
975
976 switch (type) {
977 case KItemListViewAnimation::DeleteAnimation: {
978 // As we recycle the widget in this case it is important to assure that no
979 // other animation has been started. This is a convention in KItemListView and
980 // not a requirement defined by KItemListViewAnimation.
981 Q_ASSERT(!m_animation->isStarted(itemListWidget));
982
983 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
984 // by m_visibleWidgets and must be deleted manually after the animation has
985 // been finished.
986 recycleGroupHeaderForWidget(itemListWidget);
987 m_widgetCreator->recycle(itemListWidget);
988 break;
989 }
990
991 case KItemListViewAnimation::CreateAnimation:
992 case KItemListViewAnimation::MovingAnimation:
993 case KItemListViewAnimation::ResizeAnimation: {
994 const int index = itemListWidget->index();
995 const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
996 (index > m_layouter->lastVisibleIndex());
997 if (invisible && !m_animation->isStarted(itemListWidget)) {
998 recycleWidget(itemListWidget);
999 }
1000 break;
1001 }
1002
1003 default: break;
1004 }
1005 }
1006
1007 void KItemListView::slotLayoutTimerFinished()
1008 {
1009 m_layouter->setSize(geometry().size());
1010 doLayout(Animation, 0, 0);
1011 }
1012
1013 void KItemListView::slotRubberBandPosChanged()
1014 {
1015 update();
1016 }
1017
1018 void KItemListView::slotRubberBandActivationChanged(bool active)
1019 {
1020 if (active) {
1021 connect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1022 connect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1023 m_skipAutoScrollForRubberBand = true;
1024 } else {
1025 disconnect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1026 disconnect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1027 m_skipAutoScrollForRubberBand = false;
1028 }
1029
1030 update();
1031 }
1032
1033 void KItemListView::slotVisibleRoleWidthChanged(const QByteArray& role,
1034 qreal currentWidth,
1035 qreal previousWidth)
1036 {
1037 Q_UNUSED(previousWidth);
1038
1039 m_useHeaderWidths = true;
1040
1041 if (m_visibleRolesSizes.contains(role)) {
1042 QSizeF roleSize = m_visibleRolesSizes.value(role);
1043 roleSize.setWidth(currentWidth);
1044 m_visibleRolesSizes.insert(role, roleSize);
1045 m_stretchedVisibleRolesSizes.insert(role, roleSize);
1046
1047 // Apply the new size to the layouter
1048 QSizeF dynamicItemSize = m_itemSize;
1049 if (dynamicItemSize.width() < 0) {
1050 const qreal requiredWidth = visibleRolesSizesWidthSum();
1051 dynamicItemSize.setWidth(qMax(size().width(), requiredWidth));
1052 }
1053 if (dynamicItemSize.height() < 0) {
1054 const qreal requiredHeight = visibleRolesSizesHeightSum();
1055 dynamicItemSize.setHeight(qMax(size().height(), requiredHeight));
1056 }
1057
1058 m_layouter->setItemSize(dynamicItemSize);
1059
1060 // Update the role sizes for all visible widgets
1061 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
1062 widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
1063 }
1064
1065 doLayout(Animation, 0, 0);
1066 }
1067 }
1068
1069 void KItemListView::triggerAutoScrolling()
1070 {
1071 if (!m_autoScrollTimer) {
1072 return;
1073 }
1074
1075 int pos = 0;
1076 int visibleSize = 0;
1077 if (scrollOrientation() == Qt::Vertical) {
1078 pos = m_mousePos.y();
1079 visibleSize = size().height();
1080 } else {
1081 pos = m_mousePos.x();
1082 visibleSize = size().width();
1083 }
1084
1085 if (m_autoScrollTimer->interval() == InitialAutoScrollDelay) {
1086 m_autoScrollIncrement = 0;
1087 }
1088
1089 m_autoScrollIncrement = calculateAutoScrollingIncrement(pos, visibleSize, m_autoScrollIncrement);
1090 if (m_autoScrollIncrement == 0) {
1091 // The mouse position is not above an autoscroll margin (the autoscroll timer
1092 // will be restarted in mouseMoveEvent())
1093 m_autoScrollTimer->stop();
1094 return;
1095 }
1096
1097 if (m_rubberBand->isActive() && m_skipAutoScrollForRubberBand) {
1098 // If a rubberband selection is ongoing the autoscrolling may only get triggered
1099 // if the direction of the rubberband is similar to the autoscroll direction. This
1100 // prevents that starting to create a rubberband within the autoscroll margins starts
1101 // an autoscrolling.
1102
1103 const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small
1104 const qreal diff = (scrollOrientation() == Qt::Vertical)
1105 ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y()
1106 : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x();
1107 if (qAbs(diff) < minDiff || (m_autoScrollIncrement < 0 && diff > 0) || (m_autoScrollIncrement > 0 && diff < 0)) {
1108 // The rubberband direction is different from the scroll direction (e.g. the rubberband has
1109 // been moved up although the autoscroll direction might be down)
1110 m_autoScrollTimer->stop();
1111 return;
1112 }
1113 }
1114
1115 // As soon as the autoscrolling has been triggered at least once despite having an active rubberband,
1116 // the autoscrolling may not get skipped anymore until a new rubberband is created
1117 m_skipAutoScrollForRubberBand = false;
1118
1119 setScrollOffset(scrollOffset() + m_autoScrollIncrement);
1120
1121 // Trigger the autoscroll timer which will periodically call
1122 // triggerAutoScrolling()
1123 m_autoScrollTimer->start(RepeatingAutoScrollDelay);
1124 }
1125
1126 void KItemListView::setController(KItemListController* controller)
1127 {
1128 if (m_controller != controller) {
1129 KItemListController* previous = m_controller;
1130 if (previous) {
1131 KItemListSelectionManager* selectionManager = previous->selectionManager();
1132 disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
1133 disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
1134 }
1135
1136 m_controller = controller;
1137
1138 if (controller) {
1139 KItemListSelectionManager* selectionManager = controller->selectionManager();
1140 connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
1141 connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
1142 }
1143
1144 onControllerChanged(controller, previous);
1145 }
1146 }
1147
1148 void KItemListView::setModel(KItemModelBase* model)
1149 {
1150 if (m_model == model) {
1151 return;
1152 }
1153
1154 KItemModelBase* previous = m_model;
1155
1156 if (m_model) {
1157 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1158 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1159 disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
1160 this, SLOT(slotItemsInserted(KItemRangeList)));
1161 disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
1162 this, SLOT(slotItemsRemoved(KItemRangeList)));
1163 disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
1164 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
1165 disconnect(m_model, SIGNAL(groupedSortingChanged(bool)),
1166 this, SLOT(slotGroupedSortingChanged(bool)));
1167 disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
1168 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
1169 disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
1170 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
1171 }
1172
1173 m_model = model;
1174 m_layouter->setModel(model);
1175 m_grouped = model->groupedSorting();
1176
1177 if (m_model) {
1178 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1179 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1180 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
1181 this, SLOT(slotItemsInserted(KItemRangeList)));
1182 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
1183 this, SLOT(slotItemsRemoved(KItemRangeList)));
1184 connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
1185 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
1186 connect(m_model, SIGNAL(groupedSortingChanged(bool)),
1187 this, SLOT(slotGroupedSortingChanged(bool)));
1188 connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
1189 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
1190 connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
1191 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
1192 }
1193
1194 onModelChanged(model, previous);
1195 }
1196
1197 KItemListRubberBand* KItemListView::rubberBand() const
1198 {
1199 return m_rubberBand;
1200 }
1201
1202 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
1203 {
1204 if (m_layoutTimer->isActive()) {
1205 kDebug() << "Stopping layout timer, synchronous layout requested";
1206 m_layoutTimer->stop();
1207 }
1208
1209 if (m_model->count() < 0 || m_activeTransactions > 0) {
1210 return;
1211 }
1212
1213 const int firstVisibleIndex = m_layouter->firstVisibleIndex();
1214 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
1215 if (firstVisibleIndex < 0) {
1216 emitOffsetChanges();
1217 return;
1218 }
1219
1220 // Do a sanity check of the scroll-offset property: When properties of the itemlist-view have been changed
1221 // it might be possible that the maximum offset got changed too. Assure that the full visible range
1222 // is still shown if the maximum offset got decreased.
1223 const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
1224 const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange;
1225 if (scrollOffset() > maxOffsetToShowFullRange) {
1226 m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange));
1227 }
1228
1229 // Determine all items that are completely invisible and might be
1230 // reused for items that just got (at least partly) visible.
1231 // Items that do e.g. an animated moving of their position are not
1232 // marked as invisible: This assures that a scrolling inside the view
1233 // can be done without breaking an animation.
1234 QList<int> reusableItems;
1235 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1236 while (it.hasNext()) {
1237 it.next();
1238 KItemListWidget* widget = it.value();
1239 const int index = widget->index();
1240 const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
1241 if (invisible && !m_animation->isStarted(widget)) {
1242 widget->setVisible(false);
1243 reusableItems.append(index);
1244
1245 if (m_grouped) {
1246 recycleGroupHeaderForWidget(widget);
1247 }
1248 }
1249 }
1250
1251 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
1252 // instances from invisible items are reused. If no reusable items are
1253 // found then new KItemListWidget instances get created.
1254 const bool animate = (hint == Animation);
1255 for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
1256 bool applyNewPos = true;
1257 bool wasHidden = false;
1258
1259 const QRectF itemBounds = m_layouter->itemRect(i);
1260 const QPointF newPos = itemBounds.topLeft();
1261 KItemListWidget* widget = m_visibleItems.value(i);
1262 if (!widget) {
1263 wasHidden = true;
1264 if (!reusableItems.isEmpty()) {
1265 // Reuse a KItemListWidget instance from an invisible item
1266 const int oldIndex = reusableItems.takeLast();
1267 widget = m_visibleItems.value(oldIndex);
1268 setWidgetIndex(widget, i);
1269
1270 if (m_grouped) {
1271 updateGroupHeaderForWidget(widget);
1272 }
1273 } else {
1274 // No reusable KItemListWidget instance is available, create a new one
1275 widget = createWidget(i);
1276 }
1277 widget->resize(itemBounds.size());
1278
1279 if (animate && changedCount < 0) {
1280 // Items have been deleted, move the created item to the
1281 // imaginary old position.
1282 const QRectF itemRect = m_layouter->itemRect(i - changedCount);
1283 if (itemRect.isEmpty()) {
1284 const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical)
1285 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
1286 widget->setPos(invisibleOldPos);
1287 } else {
1288 widget->setPos(itemRect.topLeft());
1289 }
1290 applyNewPos = false;
1291 }
1292 } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
1293 applyNewPos = false;
1294 }
1295
1296 if (animate) {
1297 const bool itemsRemoved = (changedCount < 0);
1298 const bool itemsInserted = (changedCount > 0);
1299
1300 if (itemsRemoved && (i >= changedIndex + changedCount + 1)) {
1301 // The item is located after the removed items. Animate the moving of the position.
1302 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
1303 applyNewPos = false;
1304 } else if (itemsInserted && i >= changedIndex) {
1305 // The item is located after the first inserted item
1306 if (i <= changedIndex + changedCount - 1) {
1307 // The item is an inserted item. Animate the appearing of the item.
1308 // For performance reasons no animation is done when changedCount is equal
1309 // to all available items.
1310 if (changedCount < m_model->count()) {
1311 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
1312 }
1313 } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
1314 // The item was already there before, so animate the moving of the position.
1315 // No moving animation is done if the item is animated by a create animation: This
1316 // prevents a "move animation mess" when inserting several ranges in parallel.
1317 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
1318 applyNewPos = false;
1319 }
1320 } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
1321 // The size of the view might have been changed. Animate the moving of the position.
1322 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
1323 applyNewPos = false;
1324 }
1325 }
1326
1327 if (applyNewPos) {
1328 widget->setPos(newPos);
1329 }
1330
1331 Q_ASSERT(widget->index() == i);
1332 widget->setVisible(true);
1333
1334 if (widget->size() != itemBounds.size()) {
1335 if (animate) {
1336 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
1337 } else {
1338 widget->resize(itemBounds.size());
1339 }
1340 }
1341 }
1342
1343 // Delete invisible KItemListWidget instances that have not been reused
1344 foreach (int index, reusableItems) {
1345 recycleWidget(m_visibleItems.value(index));
1346 }
1347
1348 if (m_grouped) {
1349 // Update the layout of all visible group headers
1350 QHashIterator<KItemListWidget*, KItemListGroupHeader*> it(m_visibleGroups);
1351 while (it.hasNext()) {
1352 it.next();
1353 updateGroupHeaderLayout(it.key());
1354 }
1355 }
1356
1357 emitOffsetChanges();
1358 }
1359
1360 void KItemListView::emitOffsetChanges()
1361 {
1362 const qreal newScrollOffset = m_layouter->scrollOffset();
1363 if (m_oldScrollOffset != newScrollOffset) {
1364 emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
1365 m_oldScrollOffset = newScrollOffset;
1366 }
1367
1368 const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset();
1369 if (m_oldMaximumScrollOffset != newMaximumScrollOffset) {
1370 emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
1371 m_oldMaximumScrollOffset = newMaximumScrollOffset;
1372 }
1373
1374 const qreal newItemOffset = m_layouter->itemOffset();
1375 if (m_oldItemOffset != newItemOffset) {
1376 emit itemOffsetChanged(newItemOffset, m_oldItemOffset);
1377 m_oldItemOffset = newItemOffset;
1378 }
1379
1380 const qreal newMaximumItemOffset = m_layouter->maximumItemOffset();
1381 if (m_oldMaximumItemOffset != newMaximumItemOffset) {
1382 emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
1383 m_oldMaximumItemOffset = newMaximumItemOffset;
1384 }
1385 }
1386
1387 KItemListWidget* KItemListView::createWidget(int index)
1388 {
1389 KItemListWidget* widget = m_widgetCreator->create(this);
1390 widget->setFlag(QGraphicsItem::ItemStacksBehindParent);
1391
1392 updateWidgetProperties(widget, index);
1393 m_visibleItems.insert(index, widget);
1394
1395 if (m_grouped) {
1396 updateGroupHeaderForWidget(widget);
1397 }
1398
1399 initializeItemListWidget(widget);
1400 return widget;
1401 }
1402
1403 void KItemListView::recycleWidget(KItemListWidget* widget)
1404 {
1405 if (m_grouped) {
1406 recycleGroupHeaderForWidget(widget);
1407 }
1408
1409 m_visibleItems.remove(widget->index());
1410 m_widgetCreator->recycle(widget);
1411 }
1412
1413 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
1414 {
1415 const int oldIndex = widget->index();
1416 m_visibleItems.remove(oldIndex);
1417 updateWidgetProperties(widget, index);
1418 m_visibleItems.insert(index, widget);
1419
1420 initializeItemListWidget(widget);
1421 }
1422
1423 void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType)
1424 {
1425 // Calculate the first visible index and last visible index for the current size
1426 const int currentFirst = m_layouter->firstVisibleIndex();
1427 const int currentLast = m_layouter->lastVisibleIndex();
1428
1429 const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize();
1430
1431 // Calculate the first visible index and last visible index for the new size
1432 setLayouterSize(size, sizeType);
1433 const int newFirst = m_layouter->firstVisibleIndex();
1434 const int newLast = m_layouter->lastVisibleIndex();
1435
1436 if ((currentFirst != newFirst) || (currentLast != newLast)) {
1437 // At least one index has been changed. Assure that widgets for all possible
1438 // visible items get created so that a move-animation can be started later.
1439 const int maxVisibleItems = m_layouter->maximumVisibleItems();
1440 int minFirst = qMin(newFirst, currentFirst);
1441 const int maxLast = qMax(newLast, currentLast);
1442
1443 if (maxLast - minFirst + 1 < maxVisibleItems) {
1444 // Increasing the size might result in a smaller KItemListView::offset().
1445 // Decrease the first visible index in a way that at least the maximum
1446 // visible items are shown.
1447 minFirst = qMax(0, maxLast - maxVisibleItems + 1);
1448 }
1449
1450 if (maxLast - minFirst > maxVisibleItems + maxVisibleItems / 2) {
1451 // The creating of widgets is quite expensive. Assure that never more
1452 // than 50 % of the maximum visible items get created for the animations.
1453 return;
1454 }
1455
1456 setLayouterSize(currentSize, sizeType);
1457 for (int i = minFirst; i <= maxLast; ++i) {
1458 if (!m_visibleItems.contains(i)) {
1459 KItemListWidget* widget = createWidget(i);
1460 const QRectF itemRect = m_layouter->itemRect(i);
1461 widget->setPos(itemRect.topLeft());
1462 widget->resize(itemRect.size());
1463 }
1464 }
1465 setLayouterSize(size, sizeType);
1466 }
1467 }
1468
1469 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
1470 {
1471 switch (sizeType) {
1472 case LayouterSize: m_layouter->setSize(size); break;
1473 case ItemSize: m_layouter->setItemSize(size); break;
1474 default: break;
1475 }
1476 }
1477
1478 void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
1479 {
1480 widget->setVisibleRoles(m_visibleRoles);
1481 widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
1482 widget->setStyleOption(m_styleOption);
1483
1484 const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
1485 widget->setCurrent(index == selectionManager->currentItem());
1486 widget->setSelected(selectionManager->isSelected(index));
1487 widget->setHovered(false);
1488 widget->setAlternatingBackgroundColors(false);
1489 widget->setIndex(index);
1490 widget->setData(m_model->data(index));
1491 }
1492
1493 void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget)
1494 {
1495 Q_ASSERT(m_grouped);
1496
1497 const int index = widget->index();
1498 if (!m_layouter->isFirstGroupItem(index)) {
1499 // The widget does not represent the first item of a group
1500 // and hence requires no header
1501 recycleGroupHeaderForWidget(widget);
1502 return;
1503 }
1504
1505 const QList<QPair<int, QVariant> > groups = model()->groups();
1506 if (groups.isEmpty()) {
1507 return;
1508 }
1509
1510 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1511 if (!header) {
1512 header = m_groupHeaderCreator->create(this);
1513 header->setParentItem(widget);
1514 m_visibleGroups.insert(widget, header);
1515 }
1516 Q_ASSERT(header->parentItem() == widget);
1517
1518 // Determine the shown data for the header by doing a binary
1519 // search in the groups-list
1520 int min = 0;
1521 int max = groups.count() - 1;
1522 int mid = 0;
1523 do {
1524 mid = (min + max) / 2;
1525 if (index > groups.at(mid).first) {
1526 min = mid + 1;
1527 } else {
1528 max = mid - 1;
1529 }
1530 } while (groups.at(mid).first != index && min <= max);
1531
1532 header->setData(groups.at(mid).second);
1533 header->setRole(model()->sortRole());
1534 header->setStyleOption(m_styleOption);
1535 header->setScrollOrientation(scrollOrientation());
1536
1537 header->show();
1538 }
1539
1540 void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget)
1541 {
1542 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1543 Q_ASSERT(header);
1544
1545 const int index = widget->index();
1546 const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index);
1547 const QRectF itemRect = m_layouter->itemRect(index);
1548
1549 // The group-header is a child of the itemlist widget. Translate the
1550 // group header position to the relative position.
1551 const QPointF groupHeaderPos(groupHeaderRect.x() - itemRect.x(),
1552 - groupHeaderRect.height());
1553 header->setPos(groupHeaderPos);
1554 header->resize(groupHeaderRect.size());
1555 }
1556
1557 void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget)
1558 {
1559 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1560 if (header) {
1561 header->setParentItem(0);
1562 m_groupHeaderCreator->recycle(header);
1563 m_visibleGroups.remove(widget);
1564 }
1565 }
1566
1567 void KItemListView::updateVisibleGroupHeaders()
1568 {
1569 Q_ASSERT(m_grouped);
1570 m_layouter->markAsDirty();
1571
1572 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1573 while (it.hasNext()) {
1574 it.next();
1575 updateGroupHeaderForWidget(it.value());
1576 }
1577 }
1578
1579 QHash<QByteArray, qreal> KItemListView::headerRolesWidths() const
1580 {
1581 QHash<QByteArray, qreal> rolesWidths;
1582
1583 QHashIterator<QByteArray, QSizeF> it(m_stretchedVisibleRolesSizes);
1584 while (it.hasNext()) {
1585 it.next();
1586 rolesWidths.insert(it.key(), it.value().width());
1587 }
1588
1589 return rolesWidths;
1590 }
1591
1592 void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges)
1593 {
1594 if (!m_itemSize.isEmpty() || m_useHeaderWidths) {
1595 return;
1596 }
1597
1598 const int itemCount = m_model->count();
1599 int rangesItemCount = 0;
1600 foreach (const KItemRange& range, itemRanges) {
1601 rangesItemCount += range.count;
1602 }
1603
1604 if (itemCount == rangesItemCount) {
1605 m_visibleRolesSizes = visibleRolesSizes(itemRanges);
1606 if (m_header) {
1607 // Assure the the sizes are not smaller than the minimum defined by the header
1608 // TODO: Currently only implemented for a top-aligned header
1609 const qreal minHeaderRoleWidth = m_header->minimumRoleWidth();
1610 QMutableHashIterator<QByteArray, QSizeF> it (m_visibleRolesSizes);
1611 while (it.hasNext()) {
1612 it.next();
1613 const QSizeF& size = it.value();
1614 if (size.width() < minHeaderRoleWidth) {
1615 const QSizeF newSize(minHeaderRoleWidth, size.height());
1616 m_visibleRolesSizes.insert(it.key(), newSize);
1617 }
1618 }
1619 }
1620 } else {
1621 // Only a sub range of the roles need to be determined.
1622 // The chances are good that the sizes of the sub ranges
1623 // already fit into the available sizes and hence no
1624 // expensive update might be required.
1625 bool updateRequired = false;
1626
1627 const QHash<QByteArray, QSizeF> updatedSizes = visibleRolesSizes(itemRanges);
1628 QHashIterator<QByteArray, QSizeF> it(updatedSizes);
1629 while (it.hasNext()) {
1630 it.next();
1631 const QByteArray& role = it.key();
1632 const QSizeF& updatedSize = it.value();
1633 const QSizeF currentSize = m_visibleRolesSizes.value(role);
1634 if (updatedSize.width() > currentSize.width() || updatedSize.height() > currentSize.height()) {
1635 m_visibleRolesSizes.insert(role, updatedSize);
1636 updateRequired = true;
1637 }
1638 }
1639
1640 if (!updateRequired) {
1641 // All the updated sizes are smaller than the current sizes and no change
1642 // of the stretched roles-widths is required
1643 return;
1644 }
1645 }
1646
1647 updateStretchedVisibleRolesSizes();
1648 }
1649
1650 void KItemListView::updateVisibleRolesSizes()
1651 {
1652 const int itemCount = m_model->count();
1653 if (itemCount > 0) {
1654 updateVisibleRolesSizes(KItemRangeList() << KItemRange(0, itemCount));
1655 }
1656 }
1657
1658 void KItemListView::updateStretchedVisibleRolesSizes()
1659 {
1660 if (!m_itemSize.isEmpty() || m_useHeaderWidths) {
1661 return;
1662 }
1663
1664 // Calculate the maximum size of an item by considering the
1665 // visible role sizes and apply them to the layouter. If the
1666 // size does not use the available view-size it the size of the
1667 // first role will get stretched.
1668 m_stretchedVisibleRolesSizes = m_visibleRolesSizes;
1669 const QByteArray role = visibleRoles().first();
1670 QSizeF firstRoleSize = m_stretchedVisibleRolesSizes.value(role);
1671
1672 QSizeF dynamicItemSize = m_itemSize;
1673
1674 if (dynamicItemSize.width() <= 0) {
1675 const qreal requiredWidth = visibleRolesSizesWidthSum();
1676 const qreal availableWidth = size().width();
1677 if (requiredWidth < availableWidth) {
1678 // Stretch the first role to use the whole width for the item
1679 firstRoleSize.rwidth() += availableWidth - requiredWidth;
1680 m_stretchedVisibleRolesSizes.insert(role, firstRoleSize);
1681 }
1682 dynamicItemSize.setWidth(qMax(requiredWidth, availableWidth));
1683 }
1684
1685 if (dynamicItemSize.height() <= 0) {
1686 const qreal requiredHeight = visibleRolesSizesHeightSum();
1687 const qreal availableHeight = size().height();
1688 if (requiredHeight < availableHeight) {
1689 // Stretch the first role to use the whole height for the item
1690 firstRoleSize.rheight() += availableHeight - requiredHeight;
1691 m_stretchedVisibleRolesSizes.insert(role, firstRoleSize);
1692 }
1693 dynamicItemSize.setHeight(qMax(requiredHeight, availableHeight));
1694 }
1695
1696 m_layouter->setItemSize(dynamicItemSize);
1697
1698 if (m_header) {
1699 m_header->setVisibleRolesWidths(headerRolesWidths());
1700 m_header->resize(dynamicItemSize.width(), m_header->size().height());
1701 }
1702
1703 // Update the role sizes for all visible widgets
1704 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
1705 widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
1706 }
1707 }
1708
1709 qreal KItemListView::visibleRolesSizesWidthSum() const
1710 {
1711 qreal widthSum = 0;
1712 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
1713 while (it.hasNext()) {
1714 it.next();
1715 widthSum += it.value().width();
1716 }
1717 return widthSum;
1718 }
1719
1720 qreal KItemListView::visibleRolesSizesHeightSum() const
1721 {
1722 qreal heightSum = 0;
1723 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
1724 while (it.hasNext()) {
1725 it.next();
1726 heightSum += it.value().height();
1727 }
1728 return heightSum;
1729 }
1730
1731 QRectF KItemListView::headerBoundaries() const
1732 {
1733 return m_header ? m_header->geometry() : QRectF();
1734 }
1735
1736 int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc)
1737 {
1738 int inc = 0;
1739
1740 const int minSpeed = 4;
1741 const int maxSpeed = 128;
1742 const int speedLimiter = 96;
1743 const int autoScrollBorder = 64;
1744
1745 // Limit the increment that is allowed to be added in comparison to 'oldInc'.
1746 // This assures that the autoscrolling speed grows gradually.
1747 const int incLimiter = 1;
1748
1749 if (pos < autoScrollBorder) {
1750 inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter;
1751 inc = qMax(inc, -maxSpeed);
1752 inc = qMax(inc, oldInc - incLimiter);
1753 } else if (pos > range - autoScrollBorder) {
1754 inc = minSpeed + qAbs(pos - range + autoScrollBorder) * (pos - range + autoScrollBorder) / speedLimiter;
1755 inc = qMin(inc, maxSpeed);
1756 inc = qMin(inc, oldInc + incLimiter);
1757 }
1758
1759 return inc;
1760 }
1761
1762
1763
1764 KItemListCreatorBase::~KItemListCreatorBase()
1765 {
1766 qDeleteAll(m_recycleableWidgets);
1767 qDeleteAll(m_createdWidgets);
1768 }
1769
1770 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
1771 {
1772 m_createdWidgets.insert(widget);
1773 }
1774
1775 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
1776 {
1777 Q_ASSERT(m_createdWidgets.contains(widget));
1778 m_createdWidgets.remove(widget);
1779
1780 if (m_recycleableWidgets.count() < 100) {
1781 m_recycleableWidgets.append(widget);
1782 widget->setVisible(false);
1783 } else {
1784 delete widget;
1785 }
1786 }
1787
1788 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
1789 {
1790 if (m_recycleableWidgets.isEmpty()) {
1791 return 0;
1792 }
1793
1794 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
1795 m_createdWidgets.insert(widget);
1796 return widget;
1797 }
1798
1799 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
1800 {
1801 }
1802
1803 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
1804 {
1805 widget->setParentItem(0);
1806 widget->setOpacity(1.0);
1807 pushRecycleableWidget(widget);
1808 }
1809
1810 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
1811 {
1812 }
1813
1814 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
1815 {
1816 header->setOpacity(1.0);
1817 pushRecycleableWidget(header);
1818 }
1819
1820 #include "kitemlistview.moc"