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