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