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