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