]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistview.cpp
Allow custom sorting of details-view columns
[dolphin.git] / src / kitemviews / kitemlistview.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * Based on the Itemviews NG project from Trolltech Labs: *
5 * http://qt.gitorious.org/qt-labs/itemviews-ng *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
21 ***************************************************************************/
22
23 #include "kitemlistview.h"
24
25 #include "kitemlistcontroller.h"
26 #include "kitemlistheader_p.h"
27 #include "kitemlistrubberband_p.h"
28 #include "kitemlistselectionmanager.h"
29 #include "kitemlistsizehintresolver_p.h"
30 #include "kitemlistviewlayouter_p.h"
31 #include "kitemlistviewanimation_p.h"
32 #include "kitemlistwidget.h"
33
34 #include <KDebug>
35
36 #include <QCursor>
37 #include <QGraphicsSceneMouseEvent>
38 #include <QPainter>
39 #include <QPropertyAnimation>
40 #include <QStyle>
41 #include <QStyleOptionRubberBand>
42 #include <QTimer>
43
44 namespace {
45 // Time in ms until reaching the autoscroll margin triggers
46 // an initial autoscrolling
47 const int InitialAutoScrollDelay = 700;
48
49 // Delay in ms for triggering the next autoscroll
50 const int RepeatingAutoScrollDelay = 1000 / 60;
51 }
52
53 KItemListView::KItemListView(QGraphicsWidget* parent) :
54 QGraphicsWidget(parent),
55 m_enabledSelectionToggles(false),
56 m_grouped(false),
57 m_supportsItemExpanding(false),
58 m_activeTransactions(0),
59 m_endTransactionAnimationHint(Animation),
60 m_itemSize(),
61 m_controller(0),
62 m_model(0),
63 m_visibleRoles(),
64 m_visibleRolesSizes(),
65 m_stretchedVisibleRolesSizes(),
66 m_widgetCreator(0),
67 m_groupHeaderCreator(0),
68 m_styleOption(),
69 m_visibleItems(),
70 m_visibleGroups(),
71 m_visibleCells(),
72 m_sizeHintResolver(0),
73 m_layouter(0),
74 m_animation(0),
75 m_layoutTimer(0),
76 m_oldScrollOffset(0),
77 m_oldMaximumScrollOffset(0),
78 m_oldItemOffset(0),
79 m_oldMaximumItemOffset(0),
80 m_skipAutoScrollForRubberBand(false),
81 m_rubberBand(0),
82 m_mousePos(),
83 m_autoScrollIncrement(0),
84 m_autoScrollTimer(0),
85 m_header(0),
86 m_useHeaderWidths(false)
87 {
88 setAcceptHoverEvents(true);
89
90 m_sizeHintResolver = new KItemListSizeHintResolver(this);
91
92 m_layouter = new KItemListViewLayouter(this);
93 m_layouter->setSizeHintResolver(m_sizeHintResolver);
94
95 m_animation = new KItemListViewAnimation(this);
96 connect(m_animation, SIGNAL(finished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)),
97 this, SLOT(slotAnimationFinished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)));
98
99 m_layoutTimer = new QTimer(this);
100 m_layoutTimer->setInterval(300);
101 m_layoutTimer->setSingleShot(true);
102 connect(m_layoutTimer, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished()));
103
104 m_rubberBand = new KItemListRubberBand(this);
105 connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool)));
106 }
107
108 KItemListView::~KItemListView()
109 {
110 delete m_sizeHintResolver;
111 m_sizeHintResolver = 0;
112 }
113
114 void KItemListView::setScrollOrientation(Qt::Orientation orientation)
115 {
116 const Qt::Orientation previousOrientation = m_layouter->scrollOrientation();
117 if (orientation == previousOrientation) {
118 return;
119 }
120
121 m_layouter->setScrollOrientation(orientation);
122 m_animation->setScrollOrientation(orientation);
123 m_sizeHintResolver->clearCache();
124
125 if (m_grouped) {
126 QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
127 while (it.hasNext()) {
128 it.next();
129 it.value()->setScrollOrientation(orientation);
130 }
131 updateGroupHeaderHeight();
132
133 }
134
135 doLayout(NoAnimation);
136
137 onScrollOrientationChanged(orientation, previousOrientation);
138 emit scrollOrientationChanged(orientation, previousOrientation);
139 }
140
141 Qt::Orientation KItemListView::scrollOrientation() const
142 {
143 return m_layouter->scrollOrientation();
144 }
145
146 void KItemListView::setItemSize(const QSizeF& itemSize)
147 {
148 const QSizeF previousSize = m_itemSize;
149 if (itemSize == previousSize) {
150 return;
151 }
152
153 // Skip animations when the number of rows or columns
154 // are changed in the grid layout. Although the animation
155 // engine can handle this usecase, it looks obtrusive.
156 const bool animate = !changesItemGridLayout(m_layouter->size(),
157 itemSize,
158 m_layouter->itemMargin());
159
160 const bool updateAlternateBackgrounds = (m_visibleRoles.count() > 1) &&
161 (( m_itemSize.isEmpty() && !itemSize.isEmpty()) ||
162 (!m_itemSize.isEmpty() && itemSize.isEmpty()));
163
164 m_itemSize = itemSize;
165
166 if (updateAlternateBackgrounds) {
167 // For an empty item size alternate backgrounds are drawn if more than
168 // one role is shown. Assure that the backgrounds for visible items are
169 // updated when changing the size in this context.
170 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
171 while (it.hasNext()) {
172 it.next();
173 updateAlternateBackgroundForWidget(it.value());
174 }
175 }
176
177 if (itemSize.isEmpty()) {
178 updateVisibleRolesSizes();
179 } else {
180 m_layouter->setItemSize(itemSize);
181 }
182
183 m_sizeHintResolver->clearCache();
184 doLayout(animate ? Animation : NoAnimation);
185 onItemSizeChanged(itemSize, previousSize);
186 }
187
188 QSizeF KItemListView::itemSize() const
189 {
190 return m_itemSize;
191 }
192
193 void KItemListView::setScrollOffset(qreal offset)
194 {
195 if (offset < 0) {
196 offset = 0;
197 }
198
199 const qreal previousOffset = m_layouter->scrollOffset();
200 if (offset == previousOffset) {
201 return;
202 }
203
204 m_layouter->setScrollOffset(offset);
205 m_animation->setScrollOffset(offset);
206
207 // Don't check whether the m_layoutTimer is active: Changing the
208 // scroll offset must always trigger a synchronous layout, otherwise
209 // the smooth-scrolling might get jerky.
210 doLayout(NoAnimation);
211 onScrollOffsetChanged(offset, previousOffset);
212 }
213
214 qreal KItemListView::scrollOffset() const
215 {
216 return m_layouter->scrollOffset();
217 }
218
219 qreal KItemListView::maximumScrollOffset() const
220 {
221 return m_layouter->maximumScrollOffset();
222 }
223
224 void KItemListView::setItemOffset(qreal offset)
225 {
226 if (m_layouter->itemOffset() == offset) {
227 return;
228 }
229
230 m_layouter->setItemOffset(offset);
231 if (m_header) {
232 m_header->setPos(-offset, 0);
233 }
234
235 // Don't check whether the m_layoutTimer is active: Changing the
236 // item offset must always trigger a synchronous layout, otherwise
237 // the smooth-scrolling might get jerky.
238 doLayout(NoAnimation);
239 }
240
241 qreal KItemListView::itemOffset() const
242 {
243 return m_layouter->itemOffset();
244 }
245
246 qreal KItemListView::maximumItemOffset() const
247 {
248 return m_layouter->maximumItemOffset();
249 }
250
251 void KItemListView::setVisibleRoles(const QList<QByteArray>& roles)
252 {
253 const QList<QByteArray> previousRoles = m_visibleRoles;
254 m_visibleRoles = roles;
255
256 const bool updateAlternateBackgrounds = m_itemSize.isEmpty() &&
257 ((roles.count() > 1 && previousRoles.count() <= 1) ||
258 (roles.count() <= 1 && previousRoles.count() > 1));
259
260 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
261 while (it.hasNext()) {
262 it.next();
263 KItemListWidget* widget = it.value();
264 widget->setVisibleRoles(roles);
265 widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
266 if (updateAlternateBackgrounds) {
267 updateAlternateBackgroundForWidget(widget);
268 }
269 }
270
271 m_sizeHintResolver->clearCache();
272 m_layouter->markAsDirty();
273
274 if (m_header) {
275 m_header->setVisibleRoles(roles);
276 m_header->setVisibleRolesWidths(headerRolesWidths());
277 m_useHeaderWidths = false;
278 }
279
280 updateVisibleRolesSizes();
281 doLayout(NoAnimation);
282
283 onVisibleRolesChanged(roles, previousRoles);
284 }
285
286 QList<QByteArray> KItemListView::visibleRoles() const
287 {
288 return m_visibleRoles;
289 }
290
291 void KItemListView::setAutoScroll(bool enabled)
292 {
293 if (enabled && !m_autoScrollTimer) {
294 m_autoScrollTimer = new QTimer(this);
295 m_autoScrollTimer->setSingleShot(true);
296 connect(m_autoScrollTimer, SIGNAL(timeout()), this, SLOT(triggerAutoScrolling()));
297 m_autoScrollTimer->start(InitialAutoScrollDelay);
298 } else if (!enabled && m_autoScrollTimer) {
299 delete m_autoScrollTimer;
300 m_autoScrollTimer = 0;
301 }
302
303 }
304
305 bool KItemListView::autoScroll() const
306 {
307 return m_autoScrollTimer != 0;
308 }
309
310 void KItemListView::setEnabledSelectionToggles(bool enabled)
311 {
312 if (m_enabledSelectionToggles != enabled) {
313 m_enabledSelectionToggles = enabled;
314
315 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
316 while (it.hasNext()) {
317 it.next();
318 it.value()->setEnabledSelectionToggle(enabled);
319 }
320 }
321 }
322
323 bool KItemListView::enabledSelectionToggles() const
324 {
325 return m_enabledSelectionToggles;
326 }
327
328 KItemListController* KItemListView::controller() const
329 {
330 return m_controller;
331 }
332
333 KItemModelBase* KItemListView::model() const
334 {
335 return m_model;
336 }
337
338 void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator)
339 {
340 m_widgetCreator = widgetCreator;
341 }
342
343 KItemListWidgetCreatorBase* KItemListView::widgetCreator() const
344 {
345 return m_widgetCreator;
346 }
347
348 void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator)
349 {
350 m_groupHeaderCreator = groupHeaderCreator;
351 }
352
353 KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const
354 {
355 return m_groupHeaderCreator;
356 }
357
358 void KItemListView::setStyleOption(const KItemListStyleOption& option)
359 {
360 const KItemListStyleOption previousOption = m_styleOption;
361 m_styleOption = option;
362
363 bool animate = true;
364 const QSizeF margin(option.horizontalMargin, option.verticalMargin);
365 if (margin != m_layouter->itemMargin()) {
366 // Skip animations when the number of rows or columns
367 // are changed in the grid layout. Although the animation
368 // engine can handle this usecase, it looks obtrusive.
369 animate = !changesItemGridLayout(m_layouter->size(),
370 m_layouter->itemSize(),
371 margin);
372 m_layouter->setItemMargin(margin);
373 }
374
375 if (m_grouped) {
376 updateGroupHeaderHeight();
377 }
378
379 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
380 while (it.hasNext()) {
381 it.next();
382 it.value()->setStyleOption(option);
383 }
384
385 m_sizeHintResolver->clearCache();
386 doLayout(animate ? Animation : NoAnimation);
387
388 onStyleOptionChanged(option, previousOption);
389 }
390
391 const KItemListStyleOption& KItemListView::styleOption() const
392 {
393 return m_styleOption;
394 }
395
396 void KItemListView::setGeometry(const QRectF& rect)
397 {
398 QGraphicsWidget::setGeometry(rect);
399
400 if (!m_model) {
401 return;
402 }
403
404 const QSizeF newSize = rect.size();
405 if (m_itemSize.isEmpty()) {
406 // The item size is dynamic:
407 // Changing the geometry does not require to do an expensive
408 // update of the visible-roles sizes, only the stretched sizes
409 // need to be adjusted to the new size.
410 updateStretchedVisibleRolesSizes();
411
412 if (m_useHeaderWidths) {
413 QSizeF dynamicItemSize = m_layouter->itemSize();
414
415 if (m_itemSize.width() < 0) {
416 const qreal requiredWidth = visibleRolesSizesWidthSum();
417 if (newSize.width() > requiredWidth) {
418 dynamicItemSize.setWidth(newSize.width());
419 }
420 const qreal headerWidth = qMax(newSize.width(), requiredWidth);
421 m_header->resize(headerWidth, m_header->size().height());
422 }
423
424 if (m_itemSize.height() < 0) {
425 const qreal requiredHeight = visibleRolesSizesHeightSum();
426 if (newSize.height() > requiredHeight) {
427 dynamicItemSize.setHeight(newSize.height());
428 }
429 // TODO: KItemListHeader is not prepared for vertical alignment
430 }
431
432 m_layouter->setItemSize(dynamicItemSize);
433 }
434
435 // Triggering a synchronous layout is fine from a performance point of view,
436 // as with dynamic item sizes no moving animation must be done.
437 m_layouter->setSize(newSize);
438 doLayout(Animation);
439 } else {
440 const bool animate = !changesItemGridLayout(newSize,
441 m_layouter->itemSize(),
442 m_layouter->itemMargin());
443 m_layouter->setSize(newSize);
444
445 if (animate) {
446 // Trigger an asynchronous relayout with m_layoutTimer to prevent
447 // performance bottlenecks. If the timer is exceeded, an animated layout
448 // will be triggered.
449 if (!m_layoutTimer->isActive()) {
450 m_layoutTimer->start();
451 }
452 } else {
453 m_layoutTimer->stop();
454 doLayout(NoAnimation);
455 }
456 }
457 }
458
459 int KItemListView::itemAt(const QPointF& pos) const
460 {
461 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
462 while (it.hasNext()) {
463 it.next();
464
465 const KItemListWidget* widget = it.value();
466 const QPointF mappedPos = widget->mapFromItem(this, pos);
467 if (widget->contains(mappedPos)) {
468 return it.key();
469 }
470 }
471
472 return -1;
473 }
474
475 bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
476 {
477 if (!m_enabledSelectionToggles) {
478 return false;
479 }
480
481 const KItemListWidget* widget = m_visibleItems.value(index);
482 if (widget) {
483 const QRectF selectionToggleRect = widget->selectionToggleRect();
484 if (!selectionToggleRect.isEmpty()) {
485 const QPointF mappedPos = widget->mapFromItem(this, pos);
486 return selectionToggleRect.contains(mappedPos);
487 }
488 }
489 return false;
490 }
491
492 bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
493 {
494 const KItemListWidget* widget = m_visibleItems.value(index);
495 if (widget) {
496 const QRectF expansionToggleRect = widget->expansionToggleRect();
497 if (!expansionToggleRect.isEmpty()) {
498 const QPointF mappedPos = widget->mapFromItem(this, pos);
499 return expansionToggleRect.contains(mappedPos);
500 }
501 }
502 return false;
503 }
504
505 int KItemListView::firstVisibleIndex() const
506 {
507 return m_layouter->firstVisibleIndex();
508 }
509
510 int KItemListView::lastVisibleIndex() const
511 {
512 return m_layouter->lastVisibleIndex();
513 }
514
515 QSizeF KItemListView::itemSizeHint(int index) const
516 {
517 Q_UNUSED(index);
518 return itemSize();
519 }
520
521 QHash<QByteArray, QSizeF> KItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const
522 {
523 Q_UNUSED(itemRanges);
524 return QHash<QByteArray, QSizeF>();
525 }
526
527 bool KItemListView::supportsItemExpanding() const
528 {
529 return m_supportsItemExpanding;
530 }
531
532 QRectF KItemListView::itemRect(int index) const
533 {
534 return m_layouter->itemRect(index);
535 }
536
537 QRectF KItemListView::itemContextRect(int index) const
538 {
539 QRectF contextRect;
540
541 const KItemListWidget* widget = m_visibleItems.value(index);
542 if (widget) {
543 contextRect = widget->iconRect() | widget->textRect();
544 contextRect.translate(itemRect(index).topLeft());
545 }
546
547 return contextRect;
548 }
549
550 void KItemListView::scrollToItem(int index)
551 {
552 QRectF viewGeometry = geometry();
553 if (m_header) {
554 const qreal headerHeight = m_header->size().height();
555 viewGeometry.adjust(0, headerHeight, 0, 0);
556 }
557 const QRectF currentRect = itemRect(index);
558
559 if (!viewGeometry.contains(currentRect)) {
560 qreal newOffset = scrollOffset();
561 if (scrollOrientation() == Qt::Vertical) {
562 if (currentRect.top() < viewGeometry.top()) {
563 newOffset += currentRect.top() - viewGeometry.top();
564 } else if (currentRect.bottom() > viewGeometry.bottom()) {
565 newOffset += currentRect.bottom() - viewGeometry.bottom();
566 }
567 } else {
568 if (currentRect.left() < viewGeometry.left()) {
569 newOffset += currentRect.left() - viewGeometry.left();
570 } else if (currentRect.right() > viewGeometry.right()) {
571 newOffset += currentRect.right() - viewGeometry.right();
572 }
573 }
574
575 if (newOffset != scrollOffset()) {
576 emit scrollTo(newOffset);
577 }
578 }
579 }
580
581 void KItemListView::beginTransaction()
582 {
583 ++m_activeTransactions;
584 if (m_activeTransactions == 1) {
585 onTransactionBegin();
586 }
587 }
588
589 void KItemListView::endTransaction()
590 {
591 --m_activeTransactions;
592 if (m_activeTransactions < 0) {
593 m_activeTransactions = 0;
594 kWarning() << "Mismatch between beginTransaction()/endTransaction()";
595 }
596
597 if (m_activeTransactions == 0) {
598 onTransactionEnd();
599 doLayout(m_endTransactionAnimationHint);
600 m_endTransactionAnimationHint = Animation;
601 }
602 }
603
604 bool KItemListView::isTransactionActive() const
605 {
606 return m_activeTransactions > 0;
607 }
608
609 void KItemListView::setHeaderShown(bool show)
610 {
611
612 if (show && !m_header) {
613 m_header = new KItemListHeader(this);
614 m_header->setPos(0, 0);
615 m_header->setModel(m_model);
616 m_header->setVisibleRoles(m_visibleRoles);
617 m_header->setVisibleRolesWidths(headerRolesWidths());
618 m_header->setZValue(1);
619
620 connect(m_header, SIGNAL(visibleRoleWidthChanged(QByteArray,qreal,qreal)),
621 this, SLOT(slotVisibleRoleWidthChanged(QByteArray,qreal,qreal)));
622 connect(m_header, SIGNAL(visibleRoleMoved(QByteArray,int,int)),
623 this, SLOT(slotVisibleRoleMoved(QByteArray,int,int)));
624 connect(m_header, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
625 this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
626 connect(m_header, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
627 this, SIGNAL(sortRoleChanged(QByteArray,QByteArray)));
628
629 m_useHeaderWidths = false;
630
631 m_layouter->setHeaderHeight(m_header->size().height());
632 } else if (!show && m_header) {
633 delete m_header;
634 m_header = 0;
635 m_useHeaderWidths = false;
636 m_layouter->setHeaderHeight(0);
637 }
638 }
639
640 bool KItemListView::isHeaderShown() const
641 {
642 return m_header != 0;
643 }
644
645 QPixmap KItemListView::createDragPixmap(const QSet<int>& indexes) const
646 {
647 Q_UNUSED(indexes);
648 return QPixmap();
649 }
650
651 void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
652 {
653 QGraphicsWidget::paint(painter, option, widget);
654
655 if (m_rubberBand->isActive()) {
656 QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(),
657 m_rubberBand->endPosition()).normalized();
658
659 const QPointF topLeft = rubberBandRect.topLeft();
660 if (scrollOrientation() == Qt::Vertical) {
661 rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset());
662 } else {
663 rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y());
664 }
665
666 QStyleOptionRubberBand opt;
667 opt.initFrom(widget);
668 opt.shape = QRubberBand::Rectangle;
669 opt.opaque = false;
670 opt.rect = rubberBandRect.toRect();
671 style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
672 }
673 }
674
675 void KItemListView::initializeItemListWidget(KItemListWidget* item)
676 {
677 Q_UNUSED(item);
678 }
679
680 bool KItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const
681 {
682 Q_UNUSED(changedRoles);
683 return true;
684 }
685
686 void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
687 {
688 Q_UNUSED(current);
689 Q_UNUSED(previous);
690 }
691
692 void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
693 {
694 Q_UNUSED(current);
695 Q_UNUSED(previous);
696 }
697
698 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
699 {
700 Q_UNUSED(current);
701 Q_UNUSED(previous);
702 }
703
704 void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
705 {
706 Q_UNUSED(current);
707 Q_UNUSED(previous);
708 }
709
710 void KItemListView::onScrollOffsetChanged(qreal current, qreal previous)
711 {
712 Q_UNUSED(current);
713 Q_UNUSED(previous);
714 }
715
716 void KItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
717 {
718 Q_UNUSED(current);
719 Q_UNUSED(previous);
720 }
721
722 void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
723 {
724 Q_UNUSED(current);
725 Q_UNUSED(previous);
726 }
727
728 void KItemListView::onTransactionBegin()
729 {
730 }
731
732 void KItemListView::onTransactionEnd()
733 {
734 }
735
736 bool KItemListView::event(QEvent* event)
737 {
738 // Forward all events to the controller and handle them there
739 if (m_controller && m_controller->processEvent(event, transform())) {
740 event->accept();
741 return true;
742 }
743 return QGraphicsWidget::event(event);
744 }
745
746 void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
747 {
748 m_mousePos = transform().map(event->pos());
749 event->accept();
750 }
751
752 void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
753 {
754 QGraphicsWidget::mouseMoveEvent(event);
755
756 m_mousePos = transform().map(event->pos());
757 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) {
758 m_autoScrollTimer->start(InitialAutoScrollDelay);
759 }
760 }
761
762 void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
763 {
764 event->setAccepted(true);
765 setAutoScroll(true);
766 }
767
768 void KItemListView::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
769 {
770 QGraphicsWidget::dragMoveEvent(event);
771
772 m_mousePos = transform().map(event->pos());
773 if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) {
774 m_autoScrollTimer->start(InitialAutoScrollDelay);
775 }
776 }
777
778 void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
779 {
780 QGraphicsWidget::dragLeaveEvent(event);
781 setAutoScroll(false);
782 }
783
784 void KItemListView::dropEvent(QGraphicsSceneDragDropEvent* event)
785 {
786 QGraphicsWidget::dropEvent(event);
787 setAutoScroll(false);
788 }
789
790 QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
791 {
792 return m_visibleItems.values();
793 }
794
795 void KItemListView::setSupportsItemExpanding(bool supportsExpanding)
796 {
797 if (m_supportsItemExpanding != supportsExpanding) {
798 m_supportsItemExpanding = supportsExpanding;
799 updateSiblingsInformation();
800 }
801 }
802
803 void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
804 {
805 updateVisibleRolesSizes(itemRanges);
806
807 const bool hasMultipleRanges = (itemRanges.count() > 1);
808 if (hasMultipleRanges) {
809 beginTransaction();
810 }
811
812 m_layouter->markAsDirty();
813
814 int previouslyInsertedCount = 0;
815 foreach (const KItemRange& range, itemRanges) {
816 // range.index is related to the model before anything has been inserted.
817 // As in each loop the current item-range gets inserted the index must
818 // be increased by the already previously inserted items.
819 const int index = range.index + previouslyInsertedCount;
820 const int count = range.count;
821 if (index < 0 || count <= 0) {
822 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
823 continue;
824 }
825 previouslyInsertedCount += count;
826
827 m_sizeHintResolver->itemsInserted(index, count);
828
829 // Determine which visible items must be moved
830 QList<int> itemsToMove;
831 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
832 while (it.hasNext()) {
833 it.next();
834 const int visibleItemIndex = it.key();
835 if (visibleItemIndex >= index) {
836 itemsToMove.append(visibleItemIndex);
837 }
838 }
839
840 // Update the indexes of all KItemListWidget instances that are located
841 // after the inserted items. It is important to adjust the indexes in the order
842 // from the highest index to the lowest index to prevent overlaps when setting the new index.
843 qSort(itemsToMove);
844 for (int i = itemsToMove.count() - 1; i >= 0; --i) {
845 KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
846 Q_ASSERT(widget);
847 const int newIndex = widget->index() + count;
848 if (hasMultipleRanges) {
849 setWidgetIndex(widget, newIndex);
850 } else {
851 // Try to animate the moving of the item
852 moveWidgetToIndex(widget, newIndex);
853 }
854 }
855
856 if (m_model->count() == count && m_activeTransactions == 0) {
857 // Check whether a scrollbar is required to show the inserted items. In this case
858 // the size of the layouter will be decreased before calling doLayout(): This prevents
859 // an unnecessary temporary animation due to the geometry change of the inserted scrollbar.
860 const bool verticalScrollOrientation = (scrollOrientation() == Qt::Vertical);
861 const bool decreaseLayouterSize = ( verticalScrollOrientation && maximumScrollOffset() > size().height()) ||
862 (!verticalScrollOrientation && maximumScrollOffset() > size().width());
863 if (decreaseLayouterSize) {
864 const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
865 QSizeF layouterSize = m_layouter->size();
866 if (verticalScrollOrientation) {
867 layouterSize.rwidth() -= scrollBarExtent;
868 } else {
869 layouterSize.rheight() -= scrollBarExtent;
870 }
871 m_layouter->setSize(layouterSize);
872 }
873 }
874
875 if (!hasMultipleRanges) {
876 doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count);
877 updateSiblingsInformation();
878 }
879 }
880
881 if (m_controller) {
882 m_controller->selectionManager()->itemsInserted(itemRanges);
883 }
884
885 if (hasMultipleRanges) {
886 #ifndef QT_NO_DEBUG
887 // Important: Don't read any m_layouter-property inside the for-loop in case if
888 // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is
889 // updated in each loop-cycle and has only a consistent state after the loop.
890 Q_ASSERT(m_layouter->isDirty());
891 #endif
892 m_endTransactionAnimationHint = NoAnimation;
893 endTransaction();
894 updateSiblingsInformation();
895 }
896 }
897
898 void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
899 {
900 updateVisibleRolesSizes();
901
902 const bool hasMultipleRanges = (itemRanges.count() > 1);
903 if (hasMultipleRanges) {
904 beginTransaction();
905 }
906
907 m_layouter->markAsDirty();
908
909 for (int i = itemRanges.count() - 1; i >= 0; --i) {
910 const KItemRange& range = itemRanges.at(i);
911 const int index = range.index;
912 const int count = range.count;
913 if (index < 0 || count <= 0) {
914 kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
915 continue;
916 }
917
918 m_sizeHintResolver->itemsRemoved(index, count);
919
920 const int firstRemovedIndex = index;
921 const int lastRemovedIndex = index + count - 1;
922 const int lastIndex = m_model->count() + count - 1;
923
924 // Remove all KItemListWidget instances that got deleted
925 for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
926 KItemListWidget* widget = m_visibleItems.value(i);
927 if (!widget) {
928 continue;
929 }
930
931 m_animation->stop(widget);
932 // Stopping the animation might lead to recycling the widget if
933 // it is invisible (see slotAnimationFinished()).
934 // Check again whether it is still visible:
935 if (!m_visibleItems.contains(i)) {
936 continue;
937 }
938
939 if (m_model->count() == 0 || hasMultipleRanges || !animateChangedItemCount(count)) {
940 // Remove the widget without animation
941 recycleWidget(widget);
942 } else {
943 // Animate the removing of the items. Special case: When removing an item there
944 // is no valid model index available anymore. For the
945 // remove-animation the item gets removed from m_visibleItems but the widget
946 // will stay alive until the animation has been finished and will
947 // be recycled (deleted) in KItemListView::slotAnimationFinished().
948 m_visibleItems.remove(i);
949 widget->setIndex(-1);
950 m_animation->start(widget, KItemListViewAnimation::DeleteAnimation);
951 }
952 }
953
954 // Update the indexes of all KItemListWidget instances that are located
955 // after the deleted items
956 for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) {
957 KItemListWidget* widget = m_visibleItems.value(i);
958 if (widget) {
959 const int newIndex = i - count;
960 if (hasMultipleRanges) {
961 setWidgetIndex(widget, newIndex);
962 } else {
963 // Try to animate the moving of the item
964 moveWidgetToIndex(widget, newIndex);
965 }
966 }
967 }
968
969 if (!hasMultipleRanges) {
970 // The decrease-layout-size optimization in KItemListView::slotItemsInserted()
971 // assumes an updated geometry. If items are removed during an active transaction,
972 // the transaction will be temporary deactivated so that doLayout() triggers a
973 // geometry update if necessary.
974 const int activeTransactions = m_activeTransactions;
975 m_activeTransactions = 0;
976 doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count);
977 m_activeTransactions = activeTransactions;
978 updateSiblingsInformation();
979 }
980 }
981
982 if (m_controller) {
983 m_controller->selectionManager()->itemsRemoved(itemRanges);
984 }
985
986 if (hasMultipleRanges) {
987 #ifndef QT_NO_DEBUG
988 // Important: Don't read any m_layouter-property inside the for-loop in case if
989 // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is
990 // updated in each loop-cycle and has only a consistent state after the loop.
991 Q_ASSERT(m_layouter->isDirty());
992 #endif
993 m_endTransactionAnimationHint = NoAnimation;
994 endTransaction();
995 updateSiblingsInformation();
996 }
997 }
998
999 void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes)
1000 {
1001 m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count);
1002 m_layouter->markAsDirty();
1003
1004 if (m_controller) {
1005 m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes);
1006 }
1007
1008 const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index);
1009 const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1);
1010
1011 for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) {
1012 KItemListWidget* widget = m_visibleItems.value(index);
1013 if (widget) {
1014 updateWidgetProperties(widget, index);
1015 initializeItemListWidget(widget);
1016 }
1017 }
1018
1019 doLayout(NoAnimation);
1020 updateSiblingsInformation();
1021 }
1022
1023 void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
1024 const QSet<QByteArray>& roles)
1025 {
1026 const bool updateSizeHints = itemSizeHintUpdateRequired(roles);
1027 if (updateSizeHints) {
1028 updateVisibleRolesSizes(itemRanges);
1029 }
1030
1031 foreach (const KItemRange& itemRange, itemRanges) {
1032 const int index = itemRange.index;
1033 const int count = itemRange.count;
1034
1035 if (updateSizeHints) {
1036 m_sizeHintResolver->itemsChanged(index, count, roles);
1037 m_layouter->markAsDirty();
1038
1039 if (!m_layoutTimer->isActive()) {
1040 m_layoutTimer->start();
1041 }
1042 }
1043
1044 // Apply the changed roles to the visible item-widgets
1045 const int lastIndex = index + count - 1;
1046 for (int i = index; i <= lastIndex; ++i) {
1047 KItemListWidget* widget = m_visibleItems.value(i);
1048 if (widget) {
1049 widget->setData(m_model->data(i), roles);
1050 }
1051 }
1052
1053 if (m_grouped && roles.contains(m_model->sortRole())) {
1054 // The sort-role has been changed which might result
1055 // in modified group headers
1056 updateVisibleGroupHeaders();
1057 doLayout(NoAnimation);
1058 }
1059 }
1060 }
1061
1062 void KItemListView::slotGroupedSortingChanged(bool current)
1063 {
1064 m_grouped = current;
1065 m_layouter->markAsDirty();
1066
1067 if (m_grouped) {
1068 updateGroupHeaderHeight();
1069 } else {
1070 // Clear all visible headers
1071 QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
1072 while (it.hasNext()) {
1073 it.next();
1074 recycleGroupHeaderForWidget(it.key());
1075 }
1076 Q_ASSERT(m_visibleGroups.isEmpty());
1077 }
1078
1079 if (useAlternateBackgrounds()) {
1080 // Changing the group mode requires to update the alternate backgrounds
1081 // as with the enabled group mode the altering is done on base of the first
1082 // group item.
1083 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1084 while (it.hasNext()) {
1085 it.next();
1086 updateAlternateBackgroundForWidget(it.value());
1087 }
1088 }
1089
1090 updateSiblingsInformation();
1091 doLayout(NoAnimation);
1092 }
1093
1094 void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
1095 {
1096 Q_UNUSED(current);
1097 Q_UNUSED(previous);
1098 if (m_grouped) {
1099 updateVisibleGroupHeaders();
1100 doLayout(NoAnimation);
1101 }
1102 }
1103
1104 void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
1105 {
1106 Q_UNUSED(current);
1107 Q_UNUSED(previous);
1108 if (m_grouped) {
1109 updateVisibleGroupHeaders();
1110 doLayout(NoAnimation);
1111 }
1112 }
1113
1114 void KItemListView::slotCurrentChanged(int current, int previous)
1115 {
1116 Q_UNUSED(previous);
1117
1118 KItemListWidget* previousWidget = m_visibleItems.value(previous, 0);
1119 if (previousWidget) {
1120 previousWidget->setCurrent(false);
1121 }
1122
1123 KItemListWidget* currentWidget = m_visibleItems.value(current, 0);
1124 if (currentWidget) {
1125 currentWidget->setCurrent(true);
1126 }
1127 }
1128
1129 void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
1130 {
1131 Q_UNUSED(previous);
1132
1133 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1134 while (it.hasNext()) {
1135 it.next();
1136 const int index = it.key();
1137 KItemListWidget* widget = it.value();
1138 widget->setSelected(current.contains(index));
1139 }
1140 }
1141
1142 void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
1143 KItemListViewAnimation::AnimationType type)
1144 {
1145 KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
1146 Q_ASSERT(itemListWidget);
1147
1148 switch (type) {
1149 case KItemListViewAnimation::DeleteAnimation: {
1150 // As we recycle the widget in this case it is important to assure that no
1151 // other animation has been started. This is a convention in KItemListView and
1152 // not a requirement defined by KItemListViewAnimation.
1153 Q_ASSERT(!m_animation->isStarted(itemListWidget));
1154
1155 // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
1156 // by m_visibleWidgets and must be deleted manually after the animation has
1157 // been finished.
1158 recycleGroupHeaderForWidget(itemListWidget);
1159 m_widgetCreator->recycle(itemListWidget);
1160 break;
1161 }
1162
1163 case KItemListViewAnimation::CreateAnimation:
1164 case KItemListViewAnimation::MovingAnimation:
1165 case KItemListViewAnimation::ResizeAnimation: {
1166 const int index = itemListWidget->index();
1167 const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
1168 (index > m_layouter->lastVisibleIndex());
1169 if (invisible && !m_animation->isStarted(itemListWidget)) {
1170 recycleWidget(itemListWidget);
1171 }
1172 break;
1173 }
1174
1175 default: break;
1176 }
1177 }
1178
1179 void KItemListView::slotLayoutTimerFinished()
1180 {
1181 m_layouter->setSize(geometry().size());
1182 doLayout(Animation);
1183 }
1184
1185 void KItemListView::slotRubberBandPosChanged()
1186 {
1187 update();
1188 }
1189
1190 void KItemListView::slotRubberBandActivationChanged(bool active)
1191 {
1192 if (active) {
1193 connect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1194 connect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1195 m_skipAutoScrollForRubberBand = true;
1196 } else {
1197 disconnect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1198 disconnect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
1199 m_skipAutoScrollForRubberBand = false;
1200 }
1201
1202 update();
1203 }
1204
1205 void KItemListView::slotVisibleRoleWidthChanged(const QByteArray& role,
1206 qreal currentWidth,
1207 qreal previousWidth)
1208 {
1209 Q_UNUSED(previousWidth);
1210
1211 m_useHeaderWidths = true;
1212
1213 if (m_visibleRolesSizes.contains(role)) {
1214 QSizeF roleSize = m_visibleRolesSizes.value(role);
1215 roleSize.setWidth(currentWidth);
1216 m_visibleRolesSizes.insert(role, roleSize);
1217 m_stretchedVisibleRolesSizes.insert(role, roleSize);
1218
1219 // Apply the new size to the layouter
1220 QSizeF dynamicItemSize = m_itemSize;
1221 if (dynamicItemSize.width() < 0) {
1222 const qreal requiredWidth = visibleRolesSizesWidthSum();
1223 dynamicItemSize.setWidth(qMax(size().width(), requiredWidth));
1224 }
1225 if (dynamicItemSize.height() < 0) {
1226 const qreal requiredHeight = visibleRolesSizesHeightSum();
1227 dynamicItemSize.setHeight(qMax(size().height(), requiredHeight));
1228 }
1229
1230 m_layouter->setItemSize(dynamicItemSize);
1231
1232 // Update the role sizes for all visible widgets
1233 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1234 while (it.hasNext()) {
1235 it.next();
1236 it.value()->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
1237 }
1238 doLayout(NoAnimation);
1239 }
1240 }
1241
1242 void KItemListView::slotVisibleRoleMoved(const QByteArray& role,
1243 int currentIndex,
1244 int previousIndex)
1245 {
1246 Q_ASSERT(m_visibleRoles[previousIndex] == role);
1247
1248 const QList<QByteArray> previous = m_visibleRoles;
1249
1250 QList<QByteArray> current = m_visibleRoles;
1251 current.removeAt(previousIndex);
1252 current.insert(currentIndex, role);
1253
1254 setVisibleRoles(current);
1255
1256 emit visibleRolesChanged(current, previous);
1257 }
1258
1259 void KItemListView::triggerAutoScrolling()
1260 {
1261 if (!m_autoScrollTimer) {
1262 return;
1263 }
1264
1265 int pos = 0;
1266 int visibleSize = 0;
1267 if (scrollOrientation() == Qt::Vertical) {
1268 pos = m_mousePos.y();
1269 visibleSize = size().height();
1270 } else {
1271 pos = m_mousePos.x();
1272 visibleSize = size().width();
1273 }
1274
1275 if (m_autoScrollTimer->interval() == InitialAutoScrollDelay) {
1276 m_autoScrollIncrement = 0;
1277 }
1278
1279 m_autoScrollIncrement = calculateAutoScrollingIncrement(pos, visibleSize, m_autoScrollIncrement);
1280 if (m_autoScrollIncrement == 0) {
1281 // The mouse position is not above an autoscroll margin (the autoscroll timer
1282 // will be restarted in mouseMoveEvent())
1283 m_autoScrollTimer->stop();
1284 return;
1285 }
1286
1287 if (m_rubberBand->isActive() && m_skipAutoScrollForRubberBand) {
1288 // If a rubberband selection is ongoing the autoscrolling may only get triggered
1289 // if the direction of the rubberband is similar to the autoscroll direction. This
1290 // prevents that starting to create a rubberband within the autoscroll margins starts
1291 // an autoscrolling.
1292
1293 const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small
1294 const qreal diff = (scrollOrientation() == Qt::Vertical)
1295 ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y()
1296 : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x();
1297 if (qAbs(diff) < minDiff || (m_autoScrollIncrement < 0 && diff > 0) || (m_autoScrollIncrement > 0 && diff < 0)) {
1298 // The rubberband direction is different from the scroll direction (e.g. the rubberband has
1299 // been moved up although the autoscroll direction might be down)
1300 m_autoScrollTimer->stop();
1301 return;
1302 }
1303 }
1304
1305 // As soon as the autoscrolling has been triggered at least once despite having an active rubberband,
1306 // the autoscrolling may not get skipped anymore until a new rubberband is created
1307 m_skipAutoScrollForRubberBand = false;
1308
1309 const qreal maxVisibleOffset = qMax(qreal(0), maximumScrollOffset() - visibleSize);
1310 const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset);
1311 setScrollOffset(newScrollOffset);
1312
1313 // Trigger the autoscroll timer which will periodically call
1314 // triggerAutoScrolling()
1315 m_autoScrollTimer->start(RepeatingAutoScrollDelay);
1316 }
1317
1318 void KItemListView::slotGeometryOfGroupHeaderParentChanged()
1319 {
1320 KItemListWidget* widget = qobject_cast<KItemListWidget*>(sender());
1321 Q_ASSERT(widget);
1322 KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
1323 Q_ASSERT(groupHeader);
1324 updateGroupHeaderLayout(widget);
1325 }
1326
1327 void KItemListView::setController(KItemListController* controller)
1328 {
1329 if (m_controller != controller) {
1330 KItemListController* previous = m_controller;
1331 if (previous) {
1332 KItemListSelectionManager* selectionManager = previous->selectionManager();
1333 disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
1334 disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
1335 }
1336
1337 m_controller = controller;
1338
1339 if (controller) {
1340 KItemListSelectionManager* selectionManager = controller->selectionManager();
1341 connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
1342 connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
1343 }
1344
1345 onControllerChanged(controller, previous);
1346 }
1347 }
1348
1349 void KItemListView::setModel(KItemModelBase* model)
1350 {
1351 if (m_model == model) {
1352 return;
1353 }
1354
1355 KItemModelBase* previous = m_model;
1356
1357 if (m_model) {
1358 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1359 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1360 disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
1361 this, SLOT(slotItemsInserted(KItemRangeList)));
1362 disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
1363 this, SLOT(slotItemsRemoved(KItemRangeList)));
1364 disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
1365 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
1366 disconnect(m_model, SIGNAL(groupedSortingChanged(bool)),
1367 this, SLOT(slotGroupedSortingChanged(bool)));
1368 disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
1369 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
1370 disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
1371 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
1372 }
1373
1374 m_model = model;
1375 m_layouter->setModel(model);
1376 m_grouped = model->groupedSorting();
1377
1378 if (m_model) {
1379 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1380 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1381 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
1382 this, SLOT(slotItemsInserted(KItemRangeList)));
1383 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
1384 this, SLOT(slotItemsRemoved(KItemRangeList)));
1385 connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
1386 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
1387 connect(m_model, SIGNAL(groupedSortingChanged(bool)),
1388 this, SLOT(slotGroupedSortingChanged(bool)));
1389 connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
1390 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
1391 connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
1392 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
1393 }
1394
1395 onModelChanged(model, previous);
1396 }
1397
1398 KItemListRubberBand* KItemListView::rubberBand() const
1399 {
1400 return m_rubberBand;
1401 }
1402
1403 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
1404 {
1405 if (m_layoutTimer->isActive()) {
1406 m_layoutTimer->stop();
1407 }
1408
1409 if (m_activeTransactions > 0) {
1410 if (hint == NoAnimation) {
1411 // As soon as at least one property change should be done without animation,
1412 // the whole transaction will be marked as not animated.
1413 m_endTransactionAnimationHint = NoAnimation;
1414 }
1415 return;
1416 }
1417
1418 if (!m_model || m_model->count() < 0) {
1419 return;
1420 }
1421
1422 int firstVisibleIndex = m_layouter->firstVisibleIndex();
1423 if (firstVisibleIndex < 0) {
1424 emitOffsetChanges();
1425 return;
1426 }
1427
1428 // Do a sanity check of the scroll-offset property: When properties of the itemlist-view have been changed
1429 // it might be possible that the maximum offset got changed too. Assure that the full visible range
1430 // is still shown if the maximum offset got decreased.
1431 const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
1432 const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange;
1433 if (scrollOffset() > maxOffsetToShowFullRange) {
1434 m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange));
1435 firstVisibleIndex = m_layouter->firstVisibleIndex();
1436 }
1437
1438 const int lastVisibleIndex = m_layouter->lastVisibleIndex();
1439
1440 int firstSibblingIndex = -1;
1441 int lastSibblingIndex = -1;
1442 const bool supportsExpanding = supportsItemExpanding();
1443
1444 QList<int> reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint);
1445
1446 // Assure that for each visible item a KItemListWidget is available. KItemListWidget
1447 // instances from invisible items are reused. If no reusable items are
1448 // found then new KItemListWidget instances get created.
1449 const bool animate = (hint == Animation);
1450 for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
1451 bool applyNewPos = true;
1452 bool wasHidden = false;
1453
1454 const QRectF itemBounds = m_layouter->itemRect(i);
1455 const QPointF newPos = itemBounds.topLeft();
1456 KItemListWidget* widget = m_visibleItems.value(i);
1457 if (!widget) {
1458 wasHidden = true;
1459 if (!reusableItems.isEmpty()) {
1460 // Reuse a KItemListWidget instance from an invisible item
1461 const int oldIndex = reusableItems.takeLast();
1462 widget = m_visibleItems.value(oldIndex);
1463 setWidgetIndex(widget, i);
1464 updateWidgetProperties(widget, i);
1465 initializeItemListWidget(widget);
1466 } else {
1467 // No reusable KItemListWidget instance is available, create a new one
1468 widget = createWidget(i);
1469 }
1470 widget->resize(itemBounds.size());
1471
1472 if (animate && changedCount < 0) {
1473 // Items have been deleted, move the created item to the
1474 // imaginary old position. They will get animated to the new position
1475 // later.
1476 const QRectF itemRect = m_layouter->itemRect(i - changedCount);
1477 if (itemRect.isEmpty()) {
1478 const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical)
1479 ? QPointF(0, size().height()) : QPointF(size().width(), 0);
1480 widget->setPos(invisibleOldPos);
1481 } else {
1482 widget->setPos(itemRect.topLeft());
1483 }
1484 applyNewPos = false;
1485 }
1486
1487 if (supportsExpanding && changedCount == 0) {
1488 if (firstSibblingIndex < 0) {
1489 firstSibblingIndex = i;
1490 }
1491 lastSibblingIndex = i;
1492 }
1493 }
1494
1495 if (animate) {
1496 const bool itemsRemoved = (changedCount < 0);
1497 const bool itemsInserted = (changedCount > 0);
1498 if (itemsRemoved && (i >= changedIndex + changedCount + 1)) {
1499 // The item is located after the removed items. Animate the moving of the position.
1500 applyNewPos = !moveWidget(widget, newPos);
1501 } else if (itemsInserted && i >= changedIndex) {
1502 // The item is located after the first inserted item
1503 if (i <= changedIndex + changedCount - 1) {
1504 // The item is an inserted item. Animate the appearing of the item.
1505 // For performance reasons no animation is done when changedCount is equal
1506 // to all available items.
1507 if (changedCount < m_model->count()) {
1508 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
1509 }
1510 } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
1511 // The item was already there before, so animate the moving of the position.
1512 // No moving animation is done if the item is animated by a create animation: This
1513 // prevents a "move animation mess" when inserting several ranges in parallel.
1514 applyNewPos = !moveWidget(widget, newPos);
1515 }
1516 } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
1517 // The size of the view might have been changed. Animate the moving of the position.
1518 applyNewPos = !moveWidget(widget, newPos);
1519 }
1520 } else {
1521 m_animation->stop(widget);
1522 }
1523
1524 if (applyNewPos) {
1525 widget->setPos(newPos);
1526 }
1527
1528 Q_ASSERT(widget->index() == i);
1529 widget->setVisible(true);
1530
1531 if (widget->size() != itemBounds.size()) {
1532 // Resize the widget for the item to the changed size.
1533 if (animate) {
1534 // If a dynamic item size is used then no animation is done in the direction
1535 // of the dynamic size.
1536 if (m_itemSize.width() <= 0) {
1537 // The width is dynamic, apply the new width without animation.
1538 widget->resize(itemBounds.width(), widget->size().height());
1539 } else if (m_itemSize.height() <= 0) {
1540 // The height is dynamic, apply the new height without animation.
1541 widget->resize(widget->size().width(), itemBounds.height());
1542 }
1543 m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
1544 } else {
1545 widget->resize(itemBounds.size());
1546 }
1547 }
1548
1549 // Updating the cell-information must be done as last step: The decision whether the
1550 // moving-animation should be started at all is based on the previous cell-information.
1551 const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i));
1552 m_visibleCells.insert(i, cell);
1553 }
1554
1555 // Delete invisible KItemListWidget instances that have not been reused
1556 foreach (int index, reusableItems) {
1557 recycleWidget(m_visibleItems.value(index));
1558 }
1559
1560 if (supportsExpanding && firstSibblingIndex >= 0) {
1561 Q_ASSERT(lastSibblingIndex >= 0);
1562 updateSiblingsInformation(firstSibblingIndex, lastSibblingIndex);
1563 }
1564
1565 if (m_grouped) {
1566 // Update the layout of all visible group headers
1567 QHashIterator<KItemListWidget*, KItemListGroupHeader*> it(m_visibleGroups);
1568 while (it.hasNext()) {
1569 it.next();
1570 updateGroupHeaderLayout(it.key());
1571 }
1572 }
1573
1574 emitOffsetChanges();
1575 }
1576
1577 QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex,
1578 int lastVisibleIndex,
1579 LayoutAnimationHint hint)
1580 {
1581 // Determine all items that are completely invisible and might be
1582 // reused for items that just got (at least partly) visible. If the
1583 // animation hint is set to 'Animation' items that do e.g. an animated
1584 // moving of their position are not marked as invisible: This assures
1585 // that a scrolling inside the view can be done without breaking an animation.
1586
1587 QList<int> items;
1588
1589 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1590 while (it.hasNext()) {
1591 it.next();
1592
1593 KItemListWidget* widget = it.value();
1594 const int index = widget->index();
1595 const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
1596
1597 if (invisible) {
1598 if (m_animation->isStarted(widget)) {
1599 if (hint == NoAnimation) {
1600 // Stopping the animation will call KItemListView::slotAnimationFinished()
1601 // and the widget will be recycled if necessary there.
1602 m_animation->stop(widget);
1603 }
1604 } else {
1605 widget->setVisible(false);
1606 items.append(index);
1607
1608 if (m_grouped) {
1609 recycleGroupHeaderForWidget(widget);
1610 }
1611 }
1612 }
1613 }
1614
1615 return items;
1616 }
1617
1618 bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos)
1619 {
1620 if (widget->pos() == newPos) {
1621 return false;
1622 }
1623
1624 bool startMovingAnim = false;
1625
1626 // When having a grid the moving-animation should only be started, if it is done within
1627 // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation.
1628 // Otherwise instead of a moving-animation a create-animation on the new position will be used
1629 // instead. This is done to prevent overlapping (and confusing) moving-animations.
1630 const int index = widget->index();
1631 const Cell cell = m_visibleCells.value(index);
1632 if (cell.column >= 0 && cell.row >= 0) {
1633 if (scrollOrientation() == Qt::Vertical) {
1634 startMovingAnim = (cell.row == m_layouter->itemRow(index));
1635 } else {
1636 startMovingAnim = (cell.column == m_layouter->itemColumn(index));
1637 }
1638 }
1639
1640 if (startMovingAnim) {
1641 m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
1642 return true;
1643 }
1644
1645 m_animation->stop(widget);
1646 m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
1647 return false;
1648 }
1649
1650 void KItemListView::emitOffsetChanges()
1651 {
1652 const qreal newScrollOffset = m_layouter->scrollOffset();
1653 if (m_oldScrollOffset != newScrollOffset) {
1654 emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
1655 m_oldScrollOffset = newScrollOffset;
1656 }
1657
1658 const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset();
1659 if (m_oldMaximumScrollOffset != newMaximumScrollOffset) {
1660 emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
1661 m_oldMaximumScrollOffset = newMaximumScrollOffset;
1662 }
1663
1664 const qreal newItemOffset = m_layouter->itemOffset();
1665 if (m_oldItemOffset != newItemOffset) {
1666 emit itemOffsetChanged(newItemOffset, m_oldItemOffset);
1667 m_oldItemOffset = newItemOffset;
1668 }
1669
1670 const qreal newMaximumItemOffset = m_layouter->maximumItemOffset();
1671 if (m_oldMaximumItemOffset != newMaximumItemOffset) {
1672 emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
1673 m_oldMaximumItemOffset = newMaximumItemOffset;
1674 }
1675 }
1676
1677 KItemListWidget* KItemListView::createWidget(int index)
1678 {
1679 KItemListWidget* widget = m_widgetCreator->create(this);
1680 widget->setFlag(QGraphicsItem::ItemStacksBehindParent);
1681
1682 m_visibleItems.insert(index, widget);
1683 m_visibleCells.insert(index, Cell());
1684 updateWidgetProperties(widget, index);
1685 initializeItemListWidget(widget);
1686 return widget;
1687 }
1688
1689 void KItemListView::recycleWidget(KItemListWidget* widget)
1690 {
1691 if (m_grouped) {
1692 recycleGroupHeaderForWidget(widget);
1693 }
1694
1695 const int index = widget->index();
1696 m_visibleItems.remove(index);
1697 m_visibleCells.remove(index);
1698
1699 m_widgetCreator->recycle(widget);
1700 }
1701
1702 void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
1703 {
1704 const int oldIndex = widget->index();
1705 m_visibleItems.remove(oldIndex);
1706 m_visibleCells.remove(oldIndex);
1707
1708 m_visibleItems.insert(index, widget);
1709 m_visibleCells.insert(index, Cell());
1710
1711 widget->setIndex(index);
1712 }
1713
1714 void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int index)
1715 {
1716 const int oldIndex = widget->index();
1717 const Cell oldCell = m_visibleCells.value(oldIndex);
1718
1719 setWidgetIndex(widget, index);
1720
1721 const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index));
1722 const bool vertical = (scrollOrientation() == Qt::Vertical);
1723 const bool updateCell = (vertical && oldCell.row == newCell.row) ||
1724 (!vertical && oldCell.column == newCell.column);
1725 if (updateCell) {
1726 m_visibleCells.insert(index, newCell);
1727 }
1728 }
1729
1730 void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
1731 {
1732 switch (sizeType) {
1733 case LayouterSize: m_layouter->setSize(size); break;
1734 case ItemSize: m_layouter->setItemSize(size); break;
1735 default: break;
1736 }
1737 }
1738
1739 void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index)
1740 {
1741 widget->setVisibleRoles(m_visibleRoles);
1742 widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
1743 widget->setStyleOption(m_styleOption);
1744
1745 const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
1746 widget->setCurrent(index == selectionManager->currentItem());
1747 widget->setSelected(selectionManager->isSelected(index));
1748 widget->setHovered(false);
1749 widget->setEnabledSelectionToggle(enabledSelectionToggles());
1750 widget->setIndex(index);
1751 widget->setData(m_model->data(index));
1752 widget->setSiblingsInformation(QBitArray());
1753 updateAlternateBackgroundForWidget(widget);
1754
1755 if (m_grouped) {
1756 updateGroupHeaderForWidget(widget);
1757 }
1758 }
1759
1760 void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget)
1761 {
1762 Q_ASSERT(m_grouped);
1763
1764 const int index = widget->index();
1765 if (!m_layouter->isFirstGroupItem(index)) {
1766 // The widget does not represent the first item of a group
1767 // and hence requires no header
1768 recycleGroupHeaderForWidget(widget);
1769 return;
1770 }
1771
1772 const QList<QPair<int, QVariant> > groups = model()->groups();
1773 if (groups.isEmpty()) {
1774 return;
1775 }
1776
1777 KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
1778 if (!groupHeader) {
1779 groupHeader = m_groupHeaderCreator->create(this);
1780 groupHeader->setParentItem(widget);
1781 m_visibleGroups.insert(widget, groupHeader);
1782 connect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged()));
1783 }
1784 Q_ASSERT(groupHeader->parentItem() == widget);
1785
1786 const int groupIndex = groupIndexForItem(index);
1787 Q_ASSERT(groupIndex >= 0);
1788 groupHeader->setData(groups.at(groupIndex).second);
1789 groupHeader->setRole(model()->sortRole());
1790 groupHeader->setStyleOption(m_styleOption);
1791 groupHeader->setScrollOrientation(scrollOrientation());
1792 groupHeader->setItemIndex(index);
1793
1794 groupHeader->show();
1795 }
1796
1797 void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget)
1798 {
1799 KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget);
1800 Q_ASSERT(groupHeader);
1801
1802 const int index = widget->index();
1803 const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index);
1804 const QRectF itemRect = m_layouter->itemRect(index);
1805
1806 // The group-header is a child of the itemlist widget. Translate the
1807 // group header position to the relative position.
1808 if (scrollOrientation() == Qt::Vertical) {
1809 // In the vertical scroll orientation the group header should always span
1810 // the whole width no matter which temporary position the parent widget
1811 // has. In this case the x-position and width will be adjusted manually.
1812 groupHeader->setPos(-widget->x(), -groupHeaderRect.height());
1813 groupHeader->resize(size().width(), groupHeaderRect.size().height());
1814 } else {
1815 groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y());
1816 groupHeader->resize(groupHeaderRect.size());
1817 }
1818 }
1819
1820 void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget)
1821 {
1822 KItemListGroupHeader* header = m_visibleGroups.value(widget);
1823 if (header) {
1824 header->setParentItem(0);
1825 m_groupHeaderCreator->recycle(header);
1826 m_visibleGroups.remove(widget);
1827 disconnect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged()));
1828 }
1829 }
1830
1831 void KItemListView::updateVisibleGroupHeaders()
1832 {
1833 Q_ASSERT(m_grouped);
1834 m_layouter->markAsDirty();
1835
1836 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
1837 while (it.hasNext()) {
1838 it.next();
1839 updateGroupHeaderForWidget(it.value());
1840 }
1841 }
1842
1843 int KItemListView::groupIndexForItem(int index) const
1844 {
1845 Q_ASSERT(m_grouped);
1846
1847 const QList<QPair<int, QVariant> > groups = model()->groups();
1848 if (groups.isEmpty()) {
1849 return -1;
1850 }
1851
1852 int min = 0;
1853 int max = groups.count() - 1;
1854 int mid = 0;
1855 do {
1856 mid = (min + max) / 2;
1857 if (index > groups[mid].first) {
1858 min = mid + 1;
1859 } else {
1860 max = mid - 1;
1861 }
1862 } while (groups[mid].first != index && min <= max);
1863
1864 if (min > max) {
1865 while (groups[mid].first > index && mid > 0) {
1866 --mid;
1867 }
1868 }
1869
1870 return mid;
1871 }
1872
1873 void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget)
1874 {
1875 bool enabled = useAlternateBackgrounds();
1876 if (enabled) {
1877 const int index = widget->index();
1878 enabled = (index & 0x1) > 0;
1879 if (m_grouped) {
1880 const int groupIndex = groupIndexForItem(index);
1881 if (groupIndex >= 0) {
1882 const QList<QPair<int, QVariant> > groups = model()->groups();
1883 const int indexOfFirstGroupItem = groups[groupIndex].first;
1884 const int relativeIndex = index - indexOfFirstGroupItem;
1885 enabled = (relativeIndex & 0x1) > 0;
1886 }
1887 }
1888 }
1889 widget->setAlternateBackground(enabled);
1890 }
1891
1892 bool KItemListView::useAlternateBackgrounds() const
1893 {
1894 return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
1895 }
1896
1897 QHash<QByteArray, qreal> KItemListView::headerRolesWidths() const
1898 {
1899 QHash<QByteArray, qreal> rolesWidths;
1900
1901 QHashIterator<QByteArray, QSizeF> it(m_stretchedVisibleRolesSizes);
1902 while (it.hasNext()) {
1903 it.next();
1904 rolesWidths.insert(it.key(), it.value().width());
1905 }
1906
1907 return rolesWidths;
1908 }
1909
1910 void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges)
1911 {
1912 if (!m_itemSize.isEmpty() || m_useHeaderWidths) {
1913 return;
1914 }
1915
1916 const int itemCount = m_model->count();
1917 int rangesItemCount = 0;
1918 foreach (const KItemRange& range, itemRanges) {
1919 rangesItemCount += range.count;
1920 }
1921
1922 if (itemCount == rangesItemCount) {
1923 m_visibleRolesSizes = visibleRolesSizes(itemRanges);
1924 if (m_header) {
1925 // Assure the the sizes are not smaller than the minimum defined by the header
1926 // TODO: Currently only implemented for a top-aligned header
1927 const qreal minHeaderRoleWidth = m_header->minimumRoleWidth();
1928 QMutableHashIterator<QByteArray, QSizeF> it (m_visibleRolesSizes);
1929 while (it.hasNext()) {
1930 it.next();
1931 const QSizeF& size = it.value();
1932 if (size.width() < minHeaderRoleWidth) {
1933 const QSizeF newSize(minHeaderRoleWidth, size.height());
1934 m_visibleRolesSizes.insert(it.key(), newSize);
1935 }
1936 }
1937 }
1938 } else {
1939 // Only a sub range of the roles need to be determined.
1940 // The chances are good that the sizes of the sub ranges
1941 // already fit into the available sizes and hence no
1942 // expensive update might be required.
1943 bool updateRequired = false;
1944
1945 const QHash<QByteArray, QSizeF> updatedSizes = visibleRolesSizes(itemRanges);
1946 QHashIterator<QByteArray, QSizeF> it(updatedSizes);
1947 while (it.hasNext()) {
1948 it.next();
1949 const QByteArray& role = it.key();
1950 const QSizeF& updatedSize = it.value();
1951 const QSizeF currentSize = m_visibleRolesSizes.value(role);
1952 if (updatedSize.width() > currentSize.width() || updatedSize.height() > currentSize.height()) {
1953 m_visibleRolesSizes.insert(role, updatedSize);
1954 updateRequired = true;
1955 }
1956 }
1957
1958 if (!updateRequired) {
1959 // All the updated sizes are smaller than the current sizes and no change
1960 // of the stretched roles-widths is required
1961 return;
1962 }
1963 }
1964
1965 updateStretchedVisibleRolesSizes();
1966 }
1967
1968 void KItemListView::updateVisibleRolesSizes()
1969 {
1970 if (!m_model) {
1971 return;
1972 }
1973
1974 const int itemCount = m_model->count();
1975 if (itemCount > 0) {
1976 updateVisibleRolesSizes(KItemRangeList() << KItemRange(0, itemCount));
1977 }
1978 }
1979
1980 void KItemListView::updateStretchedVisibleRolesSizes()
1981 {
1982 if (!m_itemSize.isEmpty() || m_useHeaderWidths || m_visibleRoles.isEmpty()) {
1983 return;
1984 }
1985
1986 // Calculate the maximum size of an item by considering the
1987 // visible role sizes and apply them to the layouter. If the
1988 // size does not use the available view-size it the size of the
1989 // first role will get stretched.
1990 m_stretchedVisibleRolesSizes = m_visibleRolesSizes;
1991 const QByteArray role = m_visibleRoles.first();
1992 QSizeF firstRoleSize = m_stretchedVisibleRolesSizes.value(role);
1993
1994 QSizeF dynamicItemSize = m_itemSize;
1995
1996 if (dynamicItemSize.width() <= 0) {
1997 const qreal requiredWidth = visibleRolesSizesWidthSum();
1998 const qreal availableWidth = size().width();
1999 if (requiredWidth < availableWidth) {
2000 // Stretch the first role to use the whole width for the item
2001 firstRoleSize.rwidth() += availableWidth - requiredWidth;
2002 m_stretchedVisibleRolesSizes.insert(role, firstRoleSize);
2003 }
2004 dynamicItemSize.setWidth(qMax(requiredWidth, availableWidth));
2005 }
2006
2007 if (dynamicItemSize.height() <= 0) {
2008 const qreal requiredHeight = visibleRolesSizesHeightSum();
2009 const qreal availableHeight = size().height();
2010 if (requiredHeight < availableHeight) {
2011 // Stretch the first role to use the whole height for the item
2012 firstRoleSize.rheight() += availableHeight - requiredHeight;
2013 m_stretchedVisibleRolesSizes.insert(role, firstRoleSize);
2014 }
2015 dynamicItemSize.setHeight(qMax(requiredHeight, availableHeight));
2016 }
2017
2018 m_layouter->setItemSize(dynamicItemSize);
2019
2020 if (m_header) {
2021 m_header->setVisibleRolesWidths(headerRolesWidths());
2022 m_header->resize(dynamicItemSize.width(), m_header->size().height());
2023 }
2024
2025 // Update the role sizes for all visible widgets
2026 QHashIterator<int, KItemListWidget*> it(m_visibleItems);
2027 while (it.hasNext()) {
2028 it.next();
2029 it.value()->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
2030 }
2031 }
2032
2033 qreal KItemListView::visibleRolesSizesWidthSum() const
2034 {
2035 qreal widthSum = 0;
2036 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
2037 while (it.hasNext()) {
2038 it.next();
2039 widthSum += it.value().width();
2040 }
2041 return widthSum;
2042 }
2043
2044 qreal KItemListView::visibleRolesSizesHeightSum() const
2045 {
2046 qreal heightSum = 0;
2047 QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
2048 while (it.hasNext()) {
2049 it.next();
2050 heightSum += it.value().height();
2051 }
2052 return heightSum;
2053 }
2054
2055 QRectF KItemListView::headerBoundaries() const
2056 {
2057 return m_header ? m_header->geometry() : QRectF();
2058 }
2059
2060 bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize,
2061 const QSizeF& newItemSize,
2062 const QSizeF& newItemMargin) const
2063 {
2064 if (newItemSize.isEmpty() || newGridSize.isEmpty()) {
2065 return false;
2066 }
2067
2068 if (m_layouter->scrollOrientation() == Qt::Vertical) {
2069 const qreal itemWidth = m_layouter->itemSize().width();
2070 if (itemWidth > 0) {
2071 const int newColumnCount = itemsPerSize(newGridSize.width(),
2072 newItemSize.width(),
2073 newItemMargin.width());
2074 if (m_model->count() > newColumnCount) {
2075 const int oldColumnCount = itemsPerSize(m_layouter->size().width(),
2076 itemWidth,
2077 m_layouter->itemMargin().width());
2078 return oldColumnCount != newColumnCount;
2079 }
2080 }
2081 } else {
2082 const qreal itemHeight = m_layouter->itemSize().height();
2083 if (itemHeight > 0) {
2084 const int newRowCount = itemsPerSize(newGridSize.height(),
2085 newItemSize.height(),
2086 newItemMargin.height());
2087 if (m_model->count() > newRowCount) {
2088 const int oldRowCount = itemsPerSize(m_layouter->size().height(),
2089 itemHeight,
2090 m_layouter->itemMargin().height());
2091 return oldRowCount != newRowCount;
2092 }
2093 }
2094 }
2095
2096 return false;
2097 }
2098
2099 bool KItemListView::animateChangedItemCount(int changedItemCount) const
2100 {
2101 if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) {
2102 return false;
2103 }
2104
2105 const int maximum = (scrollOrientation() == Qt::Vertical)
2106 ? m_layouter->size().width() / m_layouter->itemSize().width()
2107 : m_layouter->size().height() / m_layouter->itemSize().height();
2108 // Only animate if up to 2/3 of a row or column are inserted or removed
2109 return changedItemCount <= maximum * 2 / 3;
2110 }
2111
2112
2113 bool KItemListView::scrollBarRequired(const QSizeF& size) const
2114 {
2115 const QSizeF oldSize = m_layouter->size();
2116
2117 m_layouter->setSize(size);
2118 const qreal maxOffset = m_layouter->maximumScrollOffset();
2119 m_layouter->setSize(oldSize);
2120
2121 return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height()
2122 : maxOffset > size.width();
2123 }
2124
2125 void KItemListView::updateGroupHeaderHeight()
2126 {
2127 qreal groupHeaderHeight = m_styleOption.fontMetrics.height();
2128 qreal groupHeaderMargin = 0;
2129
2130 if (scrollOrientation() == Qt::Horizontal) {
2131 // The vertical margin above and below the header should be
2132 // equal to the horizontal margin, not the vertical margin
2133 // from m_styleOption.
2134 groupHeaderHeight += 2 * m_styleOption.horizontalMargin;
2135 groupHeaderMargin = m_styleOption.horizontalMargin;
2136 } else if (m_itemSize.isEmpty()){
2137 groupHeaderHeight += 4 * m_styleOption.padding;
2138 groupHeaderMargin = m_styleOption.iconSize / 2;
2139 } else {
2140 groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin;
2141 groupHeaderMargin = m_styleOption.iconSize / 4;
2142 }
2143 m_layouter->setGroupHeaderHeight(groupHeaderHeight);
2144 m_layouter->setGroupHeaderMargin(groupHeaderMargin);
2145
2146 updateVisibleGroupHeaders();
2147 }
2148
2149 void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex)
2150 {
2151 if (!supportsItemExpanding() || !m_model) {
2152 return;
2153 }
2154
2155 if (firstIndex < 0 || lastIndex < 0) {
2156 firstIndex = m_layouter->firstVisibleIndex();
2157 lastIndex = m_layouter->lastVisibleIndex();
2158 } else {
2159 const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() &&
2160 lastIndex >= m_layouter->firstVisibleIndex());
2161 if (!isRangeVisible) {
2162 return;
2163 }
2164 }
2165
2166 int previousParents = 0;
2167 QBitArray previousSiblings;
2168
2169 // The rootIndex describes the first index where the siblings get
2170 // calculated from. For the calculation the upper most parent item
2171 // is required. For performance reasons it is checked first whether
2172 // the visible items before or after the current range already
2173 // contain a siblings information which can be used as base.
2174 int rootIndex = firstIndex;
2175
2176 KItemListWidget* widget = m_visibleItems.value(firstIndex - 1);
2177 if (!widget) {
2178 // There is no visible widget before the range, check whether there
2179 // is one after the range:
2180 widget = m_visibleItems.value(lastIndex + 1);
2181 if (widget) {
2182 // The sibling information of the widget may only be used if
2183 // all items of the range have the same number of parents.
2184 const int parents = m_model->expandedParentsCount(lastIndex + 1);
2185 for (int i = lastIndex; i >= firstIndex; --i) {
2186 if (m_model->expandedParentsCount(i) != parents) {
2187 widget = 0;
2188 break;
2189 }
2190 }
2191 }
2192 }
2193
2194 if (widget) {
2195 // Performance optimization: Use the sibling information of the visible
2196 // widget beside the given range.
2197 previousSiblings = widget->siblingsInformation();
2198 if (previousSiblings.isEmpty()) {
2199 return;
2200 }
2201 previousParents = previousSiblings.count() - 1;
2202 previousSiblings.truncate(previousParents);
2203 } else {
2204 // Potentially slow path: Go back to the upper most parent of firstIndex
2205 // to be able to calculate the initial value for the siblings.
2206 while (rootIndex > 0 && m_model->expandedParentsCount(rootIndex) > 0) {
2207 --rootIndex;
2208 }
2209 }
2210
2211 Q_ASSERT(previousParents >= 0);
2212 for (int i = rootIndex; i <= lastIndex; ++i) {
2213 // Update the parent-siblings in case if the current item represents
2214 // a child or an upper parent.
2215 const int currentParents = m_model->expandedParentsCount(i);
2216 Q_ASSERT(currentParents >= 0);
2217 if (previousParents < currentParents) {
2218 previousParents = currentParents;
2219 previousSiblings.resize(currentParents);
2220 previousSiblings.setBit(currentParents - 1, hasSiblingSuccessor(i - 1));
2221 } else if (previousParents > currentParents) {
2222 previousParents = currentParents;
2223 previousSiblings.truncate(currentParents);
2224 }
2225
2226 if (i >= firstIndex) {
2227 // The index represents a visible item. Apply the parent-siblings
2228 // and update the sibling of the current item.
2229 KItemListWidget* widget = m_visibleItems.value(i);
2230 if (!widget) {
2231 continue;
2232 }
2233
2234 QBitArray siblings = previousSiblings;
2235 siblings.resize(siblings.count() + 1);
2236 siblings.setBit(siblings.count() - 1, hasSiblingSuccessor(i));
2237
2238 widget->setSiblingsInformation(siblings);
2239 }
2240 }
2241 }
2242
2243 bool KItemListView::hasSiblingSuccessor(int index) const
2244 {
2245 bool hasSuccessor = false;
2246 const int parentsCount = m_model->expandedParentsCount(index);
2247 int successorIndex = index + 1;
2248
2249 // Search the next sibling
2250 const int itemCount = m_model->count();
2251 while (successorIndex < itemCount) {
2252 const int currentParentsCount = m_model->expandedParentsCount(successorIndex);
2253 if (currentParentsCount == parentsCount) {
2254 hasSuccessor = true;
2255 break;
2256 } else if (currentParentsCount < parentsCount) {
2257 break;
2258 }
2259 ++successorIndex;
2260 }
2261
2262 if (m_grouped && hasSuccessor) {
2263 // If the sibling is part of another group, don't mark it as
2264 // successor as the group header is between the sibling connections.
2265 for (int i = index + 1; i <= successorIndex; ++i) {
2266 if (m_layouter->isFirstGroupItem(i)) {
2267 hasSuccessor = false;
2268 break;
2269 }
2270 }
2271 }
2272
2273 return hasSuccessor;
2274 }
2275
2276 int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc)
2277 {
2278 int inc = 0;
2279
2280 const int minSpeed = 4;
2281 const int maxSpeed = 128;
2282 const int speedLimiter = 96;
2283 const int autoScrollBorder = 64;
2284
2285 // Limit the increment that is allowed to be added in comparison to 'oldInc'.
2286 // This assures that the autoscrolling speed grows gradually.
2287 const int incLimiter = 1;
2288
2289 if (pos < autoScrollBorder) {
2290 inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter;
2291 inc = qMax(inc, -maxSpeed);
2292 inc = qMax(inc, oldInc - incLimiter);
2293 } else if (pos > range - autoScrollBorder) {
2294 inc = minSpeed + qAbs(pos - range + autoScrollBorder) * (pos - range + autoScrollBorder) / speedLimiter;
2295 inc = qMin(inc, maxSpeed);
2296 inc = qMin(inc, oldInc + incLimiter);
2297 }
2298
2299 return inc;
2300 }
2301
2302 int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin)
2303 {
2304 const qreal availableSize = size - itemMargin;
2305 const int count = availableSize / (itemSize + itemMargin);
2306 return count;
2307 }
2308
2309
2310
2311 KItemListCreatorBase::~KItemListCreatorBase()
2312 {
2313 qDeleteAll(m_recycleableWidgets);
2314 qDeleteAll(m_createdWidgets);
2315 }
2316
2317 void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
2318 {
2319 m_createdWidgets.insert(widget);
2320 }
2321
2322 void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
2323 {
2324 Q_ASSERT(m_createdWidgets.contains(widget));
2325 m_createdWidgets.remove(widget);
2326
2327 if (m_recycleableWidgets.count() < 100) {
2328 m_recycleableWidgets.append(widget);
2329 widget->setVisible(false);
2330 } else {
2331 delete widget;
2332 }
2333 }
2334
2335 QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
2336 {
2337 if (m_recycleableWidgets.isEmpty()) {
2338 return 0;
2339 }
2340
2341 QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
2342 m_createdWidgets.insert(widget);
2343 return widget;
2344 }
2345
2346 KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
2347 {
2348 }
2349
2350 void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
2351 {
2352 widget->setParentItem(0);
2353 widget->setOpacity(1.0);
2354 pushRecycleableWidget(widget);
2355 }
2356
2357 KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
2358 {
2359 }
2360
2361 void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
2362 {
2363 header->setOpacity(1.0);
2364 pushRecycleableWidget(header);
2365 }
2366
2367 #include "kitemlistview.moc"